Bezpečnost a expirace sessions v PHP

9.5.2017 (aktualizováno 30.8.2018) Pavel Windows, PHP, Linux

Techniky, jak nastavit session v PHP aby se zabránilo případným útokům. Ať už pomocí ini_set nebo vlastního řešení a zdůvodnění, proč expirace session někdy zabere dny a jak tuto nepříjemnost vyřešit.

Bezpečnost a expirace sessions v PHP

Bezpečnost je velmi důležitá a v poslední době čím dál více. Přesto v základním nastavení jsou session v PHP náchylné k různým útokům. Kromě jiného, platnost session může být mnohonásobně delší, než je nastavena.

UPDATE 30.8.2018 - Oprava podmínek viz komentář od Richarda - Díky

Nastavení chování session

Veškeré nastavení chování session je nastaveno v php.ini. To lze naštěstí na většině hostingů přepsat a je to dobře. Zde je seznam, co je dobré nastavit pro lepší zabezpečení.

// ==== Kód provést před zavoláním session_start() ====
// Nastaví název cookie, pouze kosmetická úprava
ini_set("session.name", "BlogSid");
// Cesta v cookie, na kterých adresách je viditelná
ini_set("session.cookie_path", "/");
// Cookie není možné přečíst v JavaSciprtu
ini_set("session.cookie_httponly", true);
// Session ID akceptuje pouze v cookies, nikoli v URL
ini_set("session.use_only_cookies", true);
// Při obdržení SID, které neexistuje, je vygenerováno nové
ini_set("session.use_strict_mode", true);
// Zákázání transparentního SID
ini_set("session.use_trans_sid", false); 
// Cookie se SID je odeslána pouze po zabezpečeném připojení
ini_set("session.cookie_secure", true);
// Místo ukládání session souborů
ini_set("session.save_path", "path/to/folder");

Popis nastavení

Většina nastavení je zřejmá. Vlastnost session.name pouze změní název cookie z defaultního PHPSESSID, nijak nezvýší bezpečnost. Pokud web běží na adrese example.com/mujweb/ je dobré nastavit cookie_path na /mujweb/, protože stránky, které nezačínají touto cestou, session cookie neuvidí. Pokud web běží na HTTPS, lze nastavit u cookie, aby ji prohlížeč neodesílal po nezabezpečeném připojení. To lze pomocí vlastnosti session.cookie_secure.

Důležité je nastavit session.cookie_httponly na true. Tímto lze zabránit krádeži SessionID (SID) pomocí XSS, protože škodlivý JavaScriptový kód nemá k této cookie přístup. Další důležitou vlastností k nastavení je session.use_only_cookies, session.use_trans_sid a session.use_strict_mode, která zabezpečuje proti útoku Session fixation.

Defaultní hodnoty session.save_path je složka /tmp. Pokud tato složka je sdílená mezi všemi weby na hostingu, lze takto jednoduše získat SID a případná data v $_SESSION. Přepsáním hodnoty lze přesunout soubory session někam na bezpečnější místo.

Session fixation attack a ochrana

Útok spočívá v odeslání uživateli odkazu na stránku, která přijímá SID také pomocí GET/POST metody. Server poté na základě tohoto SID vytvoří session a útočník má přístup, protože ví, jaké SID má oběť. Při nastavení session.use_only_cookies na true budou hodnoty SID v GET/POST parametrech ignorovány. Detailněji je popsán útok na Wikipedii.

Programová ochrana proti session fixation

Pokud se přeci jen útočníkovi povede dostat do prohlížeče cookie se SID, například pomocí XSS, výše zmíněné nastavení nepomůže. Je možné trochu ochranu zvýšit ještě v kódu. Po každém přihlášení a poté i v určitých časových intervalech přegenerovat SID pomocí funkce session_regenerate_id(). Pokud útočník SID podstrčí a uživatel se přihlásí, bude mu vygenerováno SID nové a útočníkovi staré SID k ničemu.

$sessionRegenerateID = 600; // 10 minut
if (!isset($_SESSION['regenerate']) ||
        $_SESSION['regenerate'] + $sessionRegenerateID < time()) {
    session_regenerate_id(true); // true -> vymaže starou session
    $_SESSION['regenerate'] = time();
}

Expirace session

Někdy se stane, že je uživatel odhlášen po půl hodině neaktivity, někdy až po 2 dnech. Způsobeno je to chováním garbage collectoru v session. PHP přečte soubor a naplní proměnnou $_SESSION. Ten již ale může být zastaralý, existuje ale proto, že je GC doposud nesmazal. GC se totiž spouští při každém zavolání session_start() pouze s pravděpodobností session.gc_probabilitysession.gc_divisor, ta je defaultně 1/100.

Problémy s file systémem FAT

PHP určuje jestli je soubor zastaralý pomocí vlastnosti atime (access time), neboli kdy bylo k souboru naposled přistoupeno. File systém FAT ale tuto vlastnost nemá a využívá se vlastnost mtime (modified time). Pokud se ale do $_SESSION nic nezapíše, soubor se nepřepíše a může být smazán mnohem dříve. Lze tedy do proměnné ukládat aktuální čas k přinucení přepsání souboru, nebo nastavit session.lazy_write na false. 

Jak na vlastní expiraci

Pokud je žádoucí, aby byl uživatel vždy odhlášen po určité době, nelze se pouze na session spoléhat. Lze to lehce vyřešit programově a zapisovat si poslední čas aktivity a pokud je moc starý, vymazat obsah $_SESSION. Zde se nedoporučuje používat session_desotry().

$sessionLifetime = 1800; // 30 minut
ini_set("session.gc_maxlifetime", $sessionLifetime);
if (!isset($_SESSION['atime']) ||
        $_SESSION['atime'] + $sessionLifetime < time()){
    $_SESSION = []; // Vymazat vše
}else{
    $_SESSION['atime'] = time();
}

Máte zkušenosti s krádeží SessionID, nebo jsem zapomněl na něco důležité? Podělte se v komentářích. Úvodní obrázek je z Freepik a FlatIcon.

Přidat komentář

Právě odpovídáte na existující komentář. Zrušit

Komentáře

Erik

2.2.2018 16:08

Díky za super články k bezpečnosti ;)

Odpovědět

Richard

29.8.2018 20:49

Ahoj, neměla by se proměnná s délkou času k expiraci/regeneraci SESSION v podmínkách přičítat k uloženému času a to porovnat s aktuálním časem? Takhle se to odčítá a pokaždé bude ta podmínka pravdivá.

Odpovědět

Pavel

https://www.kutac.cz/blog/ 30.8.2018 15:00

Dobrý postřeh. Samozřejmě máš pravdu, musí se to přičítat. Díky za komentář, chybu opravím ;-)

Novinky z blogu

Školení - Bezpečnost PHP aplikací

Letos jsem si nadělil Vánoční dáreček o něco dříve. V termínu 11. - 12. prosince jsem se zúčastnil školení od Michala Špačka na téma Bezpečnost PHP aplikací. A jaké že to...

Další články