# Zabezpečení relací v PHP: bezpečnost session v praxi

> Bezpečnost session v PHP krok za krokem: bezpečné hodnoty php.ini, nové ID session po přihlášení, příznaky cookies a upozornění k PHP2Shell u uploadů.

Zdroj: https://phpsos.de/cs/ratgeber/session-sicherheit-php/  
Stav: 25.10.2026  
Poskytovatel: PHP SOS, Nouzová pomoc pro PHP a bezpečnost serverů. Okamžitá pomoc přes WhatsApp: https://wa.me/491736494914?text=Dobr%C3%BD%20den%2C%20PHP%20SOS%2C%20pot%C5%99ebujeme%20pomoc%20s%20na%C5%A1%C3%ADm%20PHP%20serverem.%20Firma%3A%20%E2%80%A6%20Web%3A%20%E2%80%A6

**Stručně:** Pro bezpečnost session v PHP se počítají čtyři věci: bezpečná nastavení cookies, nové ID session po každém přihlášení, oddělená a chráněná místa uložení a krátká platnost. Kvůli PHP2Shell by mělo být na straně serveru navíc nastaveno `session.upload_progress.enabled=0`, dokud není pro tuto funkci důvod.

## Proč jsou session lákavým cílem

Session si pamatuje, že je uživatel přihlášen. Technicky dostane prohlížeč náhodné ID session v cookie a server ukládá příslušná data. Kdo toto ID zná, je pro aplikaci stejným uživatelem, bez hesla a bez druhého faktoru.

Pro provozovatele to znamená: slabá správa session může zrušit ochranu přihlášení, i když jsou hesla a dvoufázové ověření řádně provedeny. Zvlášť kritické je to u administrace, zákaznických účtů a portálů s osobními údaji.

Typická rizika jsou zachycení ID session (session hijacking), podstrčení známého ID před přihlášením (session fixation) a příliš dlouho platné relace na sdílených počítačích.

## Bezpečná základní nastavení v php.ini

Nejdůležitější hodnoty lze nastavit centrálně v `php.ini` nebo pro každý web v poolu FPM. V poolu použijte `php_admin_value` nebo `php_admin_flag`, aby aplikace hodnoty nemohla přepsat.

```ini
; Základní zabezpečení session
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"

; Upozornění php.net k PHP2Shell, poté restartovat všechny workery
session.upload_progress.enabled = 0
```

`use_strict_mode` zajišťuje, že PHP nepřevezme neznámá ID zadaná prohlížečem. `cookie_httponly` brání tomu, aby ID četl JavaScript, což omezuje následky cross-site scriptingu. `cookie_secure` posílá cookie jen přes HTTPS. `SameSite=Lax` ztěžuje požadavky mezi weby s platnou relací; pro čistě administrační oblasti může být vhodné `Strict`.

Doba platnosti 1800 sekund je příkladová hodnota. Stanovte ji podle potřeby ochrany: kratší pro správu a údaje zákazníků, delší jen tam, kde je to z odborného hlediska nutné.

Mějte na paměti, že `gc_maxlifetime` pouze určuje, od kdy smějí být stará data session smazána. U některých distribucí soubory session neuklízí samo PHP, ale pravidelná systémová úloha. Ověřte pak, jakou hodnotu tato úloha používá, aby vaše nastavení skutečně zabralo. Pro odhlášení při nečinnosti se navíc nespoléhejte jen na úklidovou funkci, ale kontrolujte čas poslední akce v samotné aplikaci, jak ukazuje následující příklad.

## Obnova ID session po přihlášení

Nejdůležitějším pravidlem v kódu je vytvořit ID session znovu při každé změně oprávnění, tedy při přihlášení, při přechodu do režimu správce a při odhlášení. Následující příklad ukazuje čistý základní vzor s `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; // stejné hlášení pro špatnou adresu i špatné heslo
    }

    session_regenerate_id(true);           // nové ID, staré zahodit
    $_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();
}
```

Formuláře, které cokoli mění, navíc chraňte tokenem CSRF. Vytvoří se pomocí `bin2hex(random_bytes(32))`, uloží se do session a při odeslání se porovná pomocí `hash_equals()`.

## Místa uložení a oddělení klientů

Ve výchozím nastavení ukládá PHP session jako soubory do společného adresáře. Na serveru s více weby to může znamenat, že kompromitovaný web přečte session jiného. Proto ke každému webu patří vlastní pool s vlastním uživatelem a vlastní cestou pro session.

```ini
; v poolu FPM příslušného webu
php_admin_value[session.save_path] = /var/lib/php/sessions/kunde-a
```

Adresář by měl patřit uživateli poolu a mít práva `0700`. Jak weby od sebe dále oddělit, popisuje příručka [open_basedir a izolace jednotlivých webů](https://phpsos.de/cs/ratgeber/open-basedir/). Pokud se session ukládají do Redis nebo do databáze, platí stejné zásady: vlastní přístup pro každou aplikaci, žádný otevřený síťový přístup.

## Bezpečnost session v PHP a PHP2Shell

Zranitelnosti PHP2Shell CVE-2026-109618 a CVE-2026-109619 leží v samotném PHP. Jako jedno z přechodných opatření uvádí php.net nastavit `session.upload_progress.enabled=0` v php.ini, v poolu FPM nebo v konfiguraci serveru a poté restartovat všechny workery. Výslovně nikoli přes `ini_set()` nebo `.user.ini`.

Dne 23. 10. 2026 následovalo upozornění k [CVE-2026-115560](https://phpsos.de/cs/php2shell/cve-2026-115560/). Možné spuštění kódu tam existuje jen při neobvyklé kombinaci serializace session, přepnutí přes `ini_set()` před `session_start()` a aktivního upload progress. Nápravou je totéž nastavení, do dalšího řádného bugfix vydání.

> **Důležité:**
> Toto nastavení je přechodné opatření, nikoli úplná ochrana. Rozhodující zůstává aktualizace na opravenou verzi PHP a následný [restart všech workerů PHP](https://phpsos.de/cs/php2shell/php-fpm-neustart/).

Po změně ověřte, jaká hodnota skutečně platí. Příkazová řádka často ukazuje jinou konfiguraci než webový server; vypovídající je chráněná, poté zase smazaná kontrolní stránka nebo samotná konfigurace poolu.

## Typické chyby u session v aplikacích PHP

Mnohé slabiny nevznikají chybějícími nastaveními, ale způsobem, jak aplikace session používají. Na tyto vzory se u vlastního kódu a u dodavatelů cíleně ptejte:

- **Příliš mnoho v session:** Hesla, úplné platební údaje ani velké soubory dat do session nepatří. Ukládejte jen to, co je potřeba pro přiřazení, například ID uživatele a role.
- **Práva jen ze session:** Kdo je správce, by se mělo u kritických akcí ověřovat proti databázi. Jinak zůstanou odebraná práva účinná až do vypršení relace.
- **ID session v adrese:** ID v URL skončí v lozích, historii prohlížeče a údajích Referer. `use_only_cookies` a `use_trans_sid = 0` tomu zabrání.
- **Žádný konec relace:** Pokud chybí timeout při nečinnosti, zůstane správce na sdíleném počítači přihlášen neomezeně dlouho.
- **HTTPS za proxy chybně rozpoznáno:** Pokud šifrování končí na load balanceru, musí to aplikace vědět. Jinak může nastavovat cookies bez příznaku `Secure`.

Po bezpečnostním incidentu navíc patří k postupu zahodit všechny stávající session. Nejjednodušeji to uděláte smazáním obsahu adresáře session nebo změnou názvu session. Všichni uživatelé se pak musí znovu přihlásit a ukradené relace jsou bezcenné.

## Jak konkrétně pomáhá PHP SOS?

- Kontrolujeme nastavení session, příznaky cookies a místa uložení na všech webech serveru a dokumentujeme skutečně platné hodnoty.
- Přechodná opatření k PHP2Shell nastavujeme pro každý pool, řízeně restartujeme workery a doložíme výsledek.
- Weby oddělujeme samostatnými pooly a cestami pro session v rámci [zabezpečení serveru](https://phpsos.de/cs/leistungen/server-haertung/).
- U vlastních vývojů kontrolujeme v kódu přihlášení, odhlášení a změnu session a doporučujeme konkrétní úpravy.


## Časté dotazy

**Co je session hijacking?**  
Při session hijackingu útočník převezme relaci přihlášeného uživatele tím, že získá jeho ID session. S ID je pro aplikaci tímto uživatelem, aniž by znal heslo. Bezpečné příznaky cookies a krátká platnost to výrazně ztěžují.

**Proč se musí ID session po přihlášení vytvořit znovu?**  
Pokud zůstane ID před přihlášením i po něm stejné, mohlo by dál platit dříve známé nebo podstrčené ID. session_regenerate_id(true) vytvoří nové ID a staré zahodí.

**Mohu session.upload_progress.enabled vypnout přes ini_set()?**  
Ne, podle php.net to nestačí. Nastavení patří do php.ini, poolu FPM nebo konfigurace serveru, nikoli do ini_set() nebo .user.ini. Poté je nutné restartovat všechny workery PHP.

**Kam by se měly ukládat soubory session?**  
Do adresáře mimo webroot, který smí číst jen uživatel příslušného poolu PHP. Na serverech s více weby by měl mít každý web vlastní adresář pro session.

## Zdroje

- [OWASP Cheat Sheet Series (Session Management)](https://cheatsheetseries.owasp.org/)
- [php.net: bezpečnostní oznámení k PHP2Shell](https://www.php.net/)
- [PHP 8 ChangeLog](https://www.php.net/ChangeLog-8.php)
