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.
; 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
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.
; 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ů. 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. 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í.
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.
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_cookiesause_trans_sid = 0tomu 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.
- 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.