# Защита на сесиите във PHP: сигурност на сесиите на практика

> Сигурност на сесиите във PHP стъпка по стъпка: стойности в php.ini, нов ID на сесията след входа, флагове на бисквитките и мярката за PHP2Shell.

Източник: https://phpsos.de/bg/ratgeber/session-sicherheit-php/  
Актуалност: 25.10.2026  
Доставчик: PHP SOS, Спешна помощ за PHP и сигурност на сървъри. Незабавна помощ по WhatsApp: https://wa.me/491736494914?text=%D0%97%D0%B4%D1%80%D0%B0%D0%B2%D0%B5%D0%B9%D1%82%D0%B5%2C%20PHP%20SOS%2C%20%D0%B8%D0%BC%D0%B0%D0%BC%D0%B5%20%D0%BD%D1%83%D0%B6%D0%B4%D0%B0%20%D0%BE%D1%82%20%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%20%D1%81%20%D0%BD%D0%B0%D1%88%D0%B8%D1%8F%20PHP%20%D1%81%D1%8A%D1%80%D0%B2%D1%8A%D1%80.%20%D0%A4%D0%B8%D1%80%D0%BC%D0%B0%3A%20%E2%80%A6%20%D0%A3%D0%B5%D0%B1%D1%81%D0%B0%D0%B9%D1%82%3A%20%E2%80%A6

**Накратко:** За сигурността на сесиите във PHP са важни четири неща: сигурни настройки на бисквитките, нов ID на сесията след всяко влизане, разделени и защитени места за съхранение и кратки срокове. Заради PHP2Shell освен това `session.upload_progress.enabled=0` трябва да е зададена на страната на сървъра, докато няма причина за тази функция.

## Защо сесиите са ценна цел

Сесията помни, че потребителят е влязъл. Технически браузърът получава за това случаен ID на сесията в бисквитка, а сървърът съхранява съответните данни. Който знае този ID, е за приложението същият потребител, без парола и без втори фактор.

За операторите това означава: слабо управление на сесиите може да обезсмисли защитата на входа, дори паролите и двуфакторното удостоверяване да са реализирани чисто. Особено критично е това при административни зони, клиентски акаунти и портали с лични данни.

Типичните рискове са прихващането на ID на сесията (Session Hijacking), подхвърлянето на известен ID преди входа (Session Fixation) и прекалено дълго валидни сесии на споделени компютри.

## Сигурни основни настройки в php.ini

Най-важните стойности могат да се зададат централно в `php.ini` или за всеки уебсайт във FPM пула. В пула използвайте `php_admin_value` или `php_admin_flag`, за да не може приложението да презапише стойностите.

```ini
; Основно заздравяване на сесиите
session.use_strict_mode   = 1
session.use_only_cookies  = 1
session.use_trans_sid     = 0
session.cookie_httponly   = 1
session.cookie_secure     = 1
session.cookie_samesite   = "Lax"
session.cookie_lifetime   = 0
session.gc_maxlifetime    = 1800
session.name              = "APPSESSID"

; Съобщение на php.net за PHP2Shell, след това рестартирайте всички worker процеси
session.upload_progress.enabled = 0
```

`use_strict_mode` гарантира, че PHP не приема зададени от браузъра, непознати ID. `cookie_httponly` пречи на JavaScript да чете ID, което ограничава последствията от Cross-Site Scripting. `cookie_secure` изпраща бисквитката само през HTTPS. `SameSite=Lax` затруднява Cross-Site заявки с валидна сесия; за чисто административни зони може да подхожда `Strict`.

Срокът от 1800 секунди е примерна стойност. Определете го според нуждата от защита: по-кратък за администриране и клиентски данни, по-дълъг само там, където е функционално необходимо.

Имайте предвид, че `gc_maxlifetime` определя само от кога старите данни на сесиите могат да се изтриват. При някои дистрибуции файловете на сесиите не се почистват от самия PHP, а от редовна системна задача. Проверете тогава коя стойност използва тази задача, за да действа Вашата настройка наистина. За излизането след неактивност освен това не разчитайте само на функцията за почистване, а проверявайте момента на последното действие в самото приложение, както е показано в следващия пример.

## Подновяване на ID на сесията след входа

Най-важното правило в кода е ID на сесията да се генерира наново при всяка промяна на правата, т.е. при вход, при преминаване в административен режим и при изход. Следният пример показва чист основен модел с `password_verify()`:

```php
<?php
declare(strict_types=1);

session_start();

function anmelden(PDO $db, string $email, string $passwort): bool
{
    $stmt = $db->prepare('SELECT id, passwort_hash FROM benutzer WHERE email = :email');
    $stmt->execute(['email' => $email]);
    $nutzer = $stmt->fetch(PDO::FETCH_ASSOC);

    if (!$nutzer || !password_verify($passwort, $nutzer['passwort_hash'])) {
        return false; // едно и също съобщение за грешен адрес и грешна парола
    }

    session_regenerate_id(true);           // нов ID, старият се отхвърля
    $_SESSION['benutzer_id']   = (int) $nutzer['id'];
    $_SESSION['letzte_aktion'] = time();
    return true;
}

function pruefeLeerlauf(int $sekunden = 900): void
{
    if (isset($_SESSION['letzte_aktion']) && time() - $_SESSION['letzte_aktion'] > $sekunden) {
        abmelden();
        return;
    }
    $_SESSION['letzte_aktion'] = time();
}

function abmelden(): void
{
    $_SESSION = [];
    $p = session_get_cookie_params();
    setcookie(session_name(), '', [
        'expires'  => time() - 3600,
        'path'     => $p['path'],
        'domain'   => $p['domain'],
        'secure'   => $p['secure'],
        'httponly' => $p['httponly'],
        'samesite' => $p['samesite'],
    ]);
    session_destroy();
}
```

Допълнително защитавайте формулярите, които променят нещо, с CSRF токен. Той се генерира с `bin2hex(random_bytes(32))`, записва се в сесията и при изпращане се сравнява с `hash_equals()`.

## Места за съхранение и разделяне на клиентите

По подразбиране PHP съхранява сесиите като файлове в обща директория. На сървър с няколко уебсайта това може да означава, че компрометиран уебсайт може да чете сесиите на друг. Затова към всеки уебсайт принадлежи собствен пул със собствен потребител и собствен път за сесиите.

```ini
; във FPM пула на съответния уебсайт
php_admin_value[session.save_path] = /var/lib/php/sessions/kunde-a
```

Директорията трябва да принадлежи на потребителя на пула и да има права `0700`. Как допълнително да изолирате уебсайтовете един от друг, описва наръчникът [open_basedir и изолация за всеки уебсайт](https://phpsos.de/bg/ratgeber/open-basedir/). Ако сесиите се съхраняват в Redis или в база данни, важат същите принципи: отделен достъп за всяко приложение, никакъв отворен мрежов достъп.

## Сигурност на сесиите във PHP и PHP2Shell

Уязвимостите на PHP2Shell CVE-2026-109618 и CVE-2026-109619 са в самия PHP. Като една от преходните мерки php.net посочва `session.upload_progress.enabled=0` да се зададе в php.ini, във FPM пула или в конфигурацията на сървъра и след това всички worker процеси да се рестартират. Изрично не чрез `ini_set()` или `.user.ini`.

На 23.10.2026 последва съобщение за [CVE-2026-115560](https://phpsos.de/bg/php2shell/cve-2026-115560/). Възможно изпълнение на код там съществува само при необичайна комбинация от сериализация на сесиите, превключване чрез `ini_set()` преди `session_start()` и активен upload progress. Мярката е същата настройка, до следващата редовна версия с поправки.

> **Важно:**
> Тази настройка е преходна мярка, а не пълна защита. Решаваща остава актуализацията до поправена версия на PHP и последващият [рестарт на всички PHP worker процеси](https://phpsos.de/bg/php2shell/php-fpm-neustart/).

След промяната проверете коя стойност действително важи. Командният ред често показва друга конфигурация от уеб сървъра; показателна е защитена, после отново изтрита страница за проверка или самата конфигурация на пула.

## Типични грешки със сесиите в PHP приложения

Много слабости възникват не от липсващи настройки, а от начина, по който приложенията използват сесиите. Тези модели трябва да запитате целенасочено в собствения си код и при доставчиците на услуги:

- **Твърде много в сесията:** Пароли, пълни платежни данни или големи масиви от данни не принадлежат в сесията. Съхранявайте само необходимото за съпоставянето, например ID на потребителя и роли.
- **Права само от сесията:** Който е администратор, трябва да се проверява спрямо базата данни при критични действия. Иначе отнетите права остават в сила до изтичането на сесията.
- **ID на сесията в адреса:** ID в URL попадат в логове, история на браузъра и данни за Referer. `use_only_cookies` и `use_trans_sid = 0` го предотвратяват.
- **Липсващ край на сесията:** Без timeout при неактивност администратор на споделен компютър остава влязъл неограничено.
- **HTTPS зад прокси е разпознат грешно:** Ако криптирането завършва при load balancer-а, приложението трябва да го знае. Иначе то евентуално задава бисквитки без флага `Secure`.

След инцидент със сигурността към мерките спада и отхвърлянето на всички съществуващи сесии. Най-лесно става, като изтриете съдържанието на директорията за сесии или смените името на сесията. Всички потребители тогава трябва да влязат наново, а откраднатите сесии са безполезни.

## Как конкретно помага PHP SOS?

- Проверяваме настройките на сесиите, флаговете на бисквитките и местата за съхранение на всички уебсайтове на сървъра и документираме действително ефективните стойности.
- Въвеждаме преходните мерки за PHP2Shell за всеки пул, рестартираме контролирано worker процесите и доказваме резултата.
- Разделяме уебсайтовете със собствени пулове и пътища за сесиите в рамките на [заздравяването на сървъра](https://phpsos.de/bg/leistungen/server-haertung/).
- За собствени разработки проверяваме вход, изход и смяна на сесията в кода и препоръчваме конкретни промени.


## Често задавани въпроси

**Какво е Session Hijacking?**  
При Session Hijacking атакуващ превзема сесията на влязъл потребител, като се сдобие с неговия ID на сесията. С ID той е за приложението този потребител, без да знае паролата. Сигурни флагове на бисквитките и кратки срокове го затрудняват значително.

**Защо ID на сесията трябва да се генерира наново след входа?**  
Ако ID остава същото преди и след входа, предварително известен или подхвърлен ID би могъл да продължи да важи след влизането. session_regenerate_id(true) генерира нов ID и отхвърля стария.

**Мога ли да изключа session.upload_progress.enabled чрез ini_set()?**  
Не, според php.net това не е достатъчно. Настройката принадлежи в php.ini, във FPM пула или в конфигурацията на сървъра, не в ini_set() или .user.ini. След това всички PHP worker процеси трябва да се рестартират.

**Къде да се съхраняват файловете на сесиите?**  
В директория извън webroot, която може да чете само потребителят на съответния PHP пул. На сървъри с няколко уебсайта всеки уебсайт трябва да има собствена директория за сесии.

## Източници

- [OWASP Cheat Sheet Series (Session Management)](https://cheatsheetseries.owasp.org/)
- [php.net: Съобщения за сигурност относно PHP2Shell](https://www.php.net/)
- [PHP 8 ChangeLog](https://www.php.net/ChangeLog-8.php)
