Bezpečná session v PHP

Bezpečná session v PHP
« kdy: 16. 03. 2020, 23:51:06 »
Ahoj.
Chystám se napsat webík v php do kterého se budou přihlašovat uživatelé. Chci se zeptat jak udělat bezpečně logování a obecně správu uživatelské session.
Upřímně jsem v tom nový, takže možná někde plácnu nějakou pitomost nicméně.
a) je bezpečné používat php sessions? Nebo už se používá něco jiného?
b) nechci moc používat "jakési" nadstavby a frameworky, bojím se jich - že přestanou být podporovány, zaniknou a já to pak budu muset celé předělávat. Takže ideálně používat jen čisté php.
c) jak je to s https? Kdesi jsem četl (ale už nemůžu najít) že je lepší místo sessions používat rovnou https a pak vše včetně hesla posílat šifrovaně a session neřešit.

Takže jsem v pasti :-)

Dík za nakopnutí. Pokud by jste mne odkázali na nějaký relativně aktuální jednoduchý postup či příklad jak na to, byl bych moc rád. (A nebo na něco starého s tím, že je to stále aktuální a bezpečný postup :-) )

PS: Jde mi fakt jen o nastínění základů - jak udělat sesion či jak si pak zřídit přes let's encrypt certifikát už si pořeším sám. Jde o to, poradit mi v pár základních bodech jak správně udělat bezpečný web s přihlašováním uživatelů.
« Poslední změna: 17. 03. 2020, 08:23:32 od Petr Krčmář »


Re:bezpečná session v php
« Odpověď #1 kdy: 17. 03. 2020, 08:09:28 »
Pokud o věci nic nevíte, naopak nějakou nadstavbu použijte, pokud možno přesně podle návodu. Je pravděpodobné, že alespoň některé věci kolem bezpečnosti má vyřešené správně. Když to budete dělat sám, nebudete mít ošetřené nic.

PHP session je jen řešení na straně serveru, které na základě nějakého identifikátoru poslaného z prohlížeče pozná, o kterou session se jedná a její data vám zpřístupní. To, co se mění, je způsob, jak bezpečně dostat ten identifikátor z prohlížeče na server, ne samotná serverová implementace. Ale pokud bude veškerá komunikace šifrovaná v rámci HTTPS, použijte standardní cestu předávání session identifikátoru pomocí cookie.

HTTPS slouží „jen“ k šifrování spojení, identifikaci session pomocí něj nezajistíte.

Nicméně bez alespoň základních znalostí o bezpečnosti ten web stejně neuděláte bezpečný. Nejde o to vědět pár bodů, jak některé věci udělat bezpečně. Kdyby to mělo být bezpečné, musíte o každé části aplikace vědět, jaké jsou tam potenciální hrozby. K tomu je potřeba znát co nejvíce možných hrozeb a umět vyhodnotit, které se vás týkají.

Re:Bezpečná session v PHP
« Odpověď #2 kdy: 17. 03. 2020, 09:16:12 »
PHP sessions se dají používat zcela bezpečně, jen musíte vědět, která data se do ní hodí a jak s nimi pracovat. To není na rozepsání do diskuse. Spousta internetových rad je nedobrých, nedomyšlených.

Alternativou k PHP sessions je JWT - Json Web Tokens (https://en.wikipedia.org/wiki/JSON_Web_Token). Výhodou je, že si data nese prohlížeč a nejsou uložena na serveru. Díky tomu můžete postavit víc serveru a mezi nimi přepínat / balancovat provoz. U klasické PHP session musíte řešit, aby do ní viděly všechny servery, u JWT nikoliv.

Ať už PHP session nebo JWT, v obou případech je nejčastější začátečnickou chybou ukládání příliš mnoho dat do session a málo do úložiště aplikace (databáze). Do session by mělo přijít co nejméně dat, jen ta, která se vztahují k dané relaci. Ostatní se mají ukládat aplikačně. Příkladem je např. košík na eshopu - ten nemá v session co dělat, protože potřebujete pravidelně ověřovat dostupnost zboží, změny cen a potřebujete, aby byl stejný košík vidět po přihlášení z různých počítačů. Do session naopak patří informace o tom, kdo je přihlášený (id / hash uživatele) nebo např. informace o naposledy navštívených produktech (kvůli navigaci zpět).

Doporučil bych vzít framework, který toto řeší. Z mojí zkušenosti, vyhnul bych se Nette (nedodělané, nestálý vývoj, špadná dokumentace), z těch větších mi přijde dobré Symfony. Učitě bych to neprogramoval sám. Sice o Vašich chybách nebude nikdo cizí vědět, ale uděláte spoustu i začátečnických chby. I vlastní framework budete muset neustále vyvíjet a upravovat - a zkušenost praví, že je jednodušší počítat s pravidelnými updaty cizího frameworku, než neustále měnit něco svého. S obojím je hodně práce, dnes už nic nefunguje tak, že jednou uděláte a pak na to roky nemusíte sáhnout.

Re:Bezpečná session v PHP
« Odpověď #3 kdy: 17. 03. 2020, 09:40:04 »
Alternativou k PHP sessions je JWT - Json Web Tokens (https://en.wikipedia.org/wiki/JSON_Web_Token). Výhodou je, že si data nese prohlížeč a nejsou uložena na serveru. Díky tomu můžete postavit víc serveru a mezi nimi přepínat / balancovat provoz. U klasické PHP session musíte řešit, aby do ní viděly všechny servery, u JWT nikoliv.
To je omyl. JWT i cookies mohou nést data i pouhý identifikátor session, v tom se nijak neliší.

HTTP je nestavový protokol, tj. z hlediska serveru je každý požadavek od klienta nový, jako by klienta nikdy před tím neviděl. Což se dá řešit dvěma způsoby – buď klient s každým požadavkem pošle data, která server potřebuje (a která požadavek často nějak svážou s předchozími požadavky – třeba identifikací uživatele), nebo klient posílá jenom nějakou identifikaci a data jsou uložená ne serveru (tomu se právě říká session).

Klasická PHP session je udělaná tak, že data jsou uložená na serveru a klient v cookie posílá jenom jednoznačný identifikátor session. Klidně ale může pomocí cookie předávat i jiné údaje, třeba identifikátor uživatele. A pokud potřebných údajů nebude mnoho, mohou se všechny předávat pomocí cookie a žádná session na serveru nemusí být.

JWT je jenom jiný (modernější) způsob předávání dat mezi prohlížečem a serverem. JWT se může předávat i pomocí cookies (i když to není obvyklé, spíš se používají HTTP hlavičky nebo URL). Předností JWT oproti dříve používaným způsobům je to, že data v JWT mohou být podepsaná (a mohou být i šifrovaná). Nebo-li klient nemůže manipulovat s tím, co mu poslal server. Například pokud byste vytvářel identifikátory session ze sekvence, a posílal to ID nepodepsané, může útočník klidně vzít svoje ID, odečíst jedničku a poslat takto upravené ID – a v tu ránu bude mít session úplně někoho jiného a bude vůči serveru vystupovat jeho jménem. Proto jsou ve skutečnosti identifikátory session generované náhodně, aby nebylo možné uhodnout cizí identifikátor session. Dá se to ale řešit i přes JWT –  pak může být identifikátor klidně generován ze sekvence, protože útočník sice může odvodit identifikátor jiné session, ale nepodaří se mu JWT správně podepsat. Na serveru pak selže validace podpisu JWT a server ví, že JWT nemůže věřit (a vrátí klientovi nějakou chybovou zprávu).

Re:Bezpečná session v PHP
« Odpověď #4 kdy: 17. 03. 2020, 09:44:23 »
Alternativou k PHP sessions je JWT - Json Web Tokens (https://en.wikipedia.org/wiki/JSON_Web_Token). Výhodou je, že si data nese prohlížeč a nejsou uložena na serveru. Díky tomu můžete postavit víc serveru a mezi nimi přepínat / balancovat provoz. U klasické PHP session musíte řešit, aby do ní viděly všechny servery, u JWT nikoliv.
To je omyl. JWT i cookies mohou nést data i pouhý identifikátor session, v tom se nijak neliší.

Ano, přesně tak, to se nevylučuje. Systém, který v session / JWT přenáší jen identifikátor (např. uživatele), je podle mě nejlepší. Vzniká tím nejméně problémů v aplikaci. Zejména když se mění struktura dat, která ukládáme v session. Já jsem zmiňoval třeba nákupní košík - tam se může v čase struktura rozšiřovat (např. o pole dostupnosti, akční ceně, ...). Pak je potřeba v aplikaci zavést verzování struktury, nebo se spoléhat checky v aplikaci (aby chybějící pole v session doplnila). To už je rovnou jednodušší to ukládat ve struktuře do databáze.

Sessions (i JWT) jsou v praxi nadužívané i na data, na která se to nehodí.


Re:Bezpečná session v PHP
« Odpověď #5 kdy: 17. 03. 2020, 13:29:19 »
Super. Díky za nápovědu.
Ještě se zeptám. Ošetřuje se nějak situaci kdy má klient vypnuté cookies? Jak se pak chová server? Na základě čeho ověří že se jedná o daného uživatele.

Když to shrnu do bodů
a) na serveru je stránka s logováním
b) uživatel načte stránku + zadá jméno a heslo
c) odešle formulář (pomocí post?)
d) na serveru z post získám jméno + heslo (nevím jestli je tohle bezpečné nebo už tady je něco špatně?). Heslo se nějak musí dostat z formuláře klienta na můj server. Nevím jak je toto bezpečné.
e) na serveru ověřím uživatele a pokud je ok, pošlu mu sessionid (náhodně generované kvůli únosu a nebo sekvenčně pokud použiju jwt)
f) klient má id uložené v cookie? a při každém dotazu jej z cookie načte a pošle mi jej?


Pokud bych se vykašlal na cookie můžu mít to id uložené jen někde v paměti ? Nepotřebuju aby session zůstávala třeba 10 hod aktivní. Nedělám eshop, takže pokud uživatel zavře tab a otevře jiný nový, klidně jej donutím znova se přihlásit. Nebude to zas až tak častá aktivita (předpokládám).

Taky jsem četl, že pro bezpečnější web je dobré u kritických operací sessionid zahodit, vygenerovat nové a nechat uživatele znova ověřit (pro jistotu, aby bylo jisté že se nejedná o podvrh). Takže pokud bych narazil na jějakou kritickou část (například změna hesla), můžu to klidně udlěat i takto.

Re:Bezpečná session v PHP
« Odpověď #6 kdy: 17. 03. 2020, 13:39:20 »
To co popisujete je klasické poor man's řešení, oblíbené v kuchařkách na internetu a pramenící tak někdy z devadesátých let.

Dnešní ezpečnost:
1. heslo zpracovat už na straně browseru; osolit a vygenerovat hash; případně lze ještě ze serveru poskytnout pubkey, browser data ještě zašifruje a rozšifruje je až server (na toto slouží lépe JTW); rozhodně neposílat postem heslo
2. po ověření zaznamenat do DB id uživatele a čas přihlášení (kvůli timeoutu)
3. do session uložit zašifrovanou informaci o přihlášení (id z předchozí tabulky; výhodné je používat UUID namísto SERIALU)
4. s každým požadavkem na server aktualizovat v tabulce čas posledního přístupu
5. hlídat timeout (po X hodinách od posledního přístupu už nepovolit přístup "přihlášenému" uživateli)
6. do session neukládat žádnou jinou významnou informaci, maximálně balast typu "poslední navštívená stránka", "produkty které jsem shlédl", ... ale i to raději ukládat do DB a sanitizovat

Re:Bezpečná session v PHP
« Odpověď #7 kdy: 17. 03. 2020, 14:11:11 »
Super. Díky za nápovědu.
Ještě se zeptám. Ošetřuje se nějak situaci kdy má klient vypnuté cookies? Jak se pak chová server? Na základě čeho ověří že se jedná o daného uživatele.

Když to shrnu do bodů
a) na serveru je stránka s logováním
b) uživatel načte stránku + zadá jméno a heslo
c) odešle formulář (pomocí post?)
d) na serveru z post získám jméno + heslo (nevím jestli je tohle bezpečné nebo už tady je něco špatně?). Heslo se nějak musí dostat z formuláře klienta na můj server. Nevím jak je toto bezpečné.
e) na serveru ověřím uživatele a pokud je ok, pošlu mu sessionid (náhodně generované kvůli únosu a nebo sekvenčně pokud použiju jwt)
f) klient má id uložené v cookie? a při každém dotazu jej z cookie načte a pošle mi jej?


Pokud bych se vykašlal na cookie můžu mít to id uložené jen někde v paměti ? Nepotřebuju aby session zůstávala třeba 10 hod aktivní. Nedělám eshop, takže pokud uživatel zavře tab a otevře jiný nový, klidně jej donutím znova se přihlásit. Nebude to zas až tak častá aktivita (předpokládám).

Taky jsem četl, že pro bezpečnější web je dobré u kritických operací sessionid zahodit, vygenerovat nové a nechat uživatele znova ověřit (pro jistotu, aby bylo jisté že se nejedná o podvrh). Takže pokud bych narazil na jějakou kritickou část (například změna hesla), můžu to klidně udlěat i takto.

Pokud chcete použít PHP, je tohle obvyklý postup. To, že se v d) posílá z klienta jméno a heslo je dnes absolutně nejrozšířenější postup. Posílá se to šifrovaně přes HTTPS, takže z tohohle pohledu to nevadí. Problém je, že heslo zná server, ale to dnes skoro nikdo neřeší. A posílá se to metodou POST, kdybyste to poslal GETem, zůstane to v logu a bude to vidět v referreru.

Klient posílá session ID jako cookies – vy se o to nemusíte starat, cookie nastavíte a klient ji pak automaticky posílá s každým požadavkem.

Session ID můžete mít v prohlížeči uložený v paměti, ale stejně musíte zařídit, aby se s každým požadavkem odeslal na server. Cookies je to nejsnazší řešení, tam se nemusíte o nic starat, jenom ji poprvé nastavíte na serveru a dál se o nic nestaráte. Resp. i o to nastavení si zařídí použitý framework nebo PHP.

S opakovaným ověřením uživatele bych byl hodně opatrný. To má smysl u banky, když potvrzujete převedení peněz, ale jinak bude opakované zadání hesla uživatele hodně otravovat. Používá se to nanejvýš u té změny hesla (a i u té je to podle mne sporné).

Ukládání posledního přístupu do DB bych v tuto chvíli neřešil, čas posledního přístupu můžete mít v session. Resp. o platnost session se vám bude starat už framework nebo snad přímo PHP. Z vaší strany je potřeba akorát session invalidovat/zrušit v okamžiku, kdy se uživatel odhlásí.

Re:Bezpečná session v PHP
« Odpověď #8 kdy: 17. 03. 2020, 14:13:09 »

Doporučil bych vzít framework, který toto řeší. Z mojí zkušenosti, vyhnul bych se Nette (nedodělané, nestálý vývoj, špadná dokumentace), z těch větších mi přijde dobré Symfony. Učitě bych to neprogramoval sám. Sice o Vašich chybách nebude nikdo cizí vědět, ale uděláte spoustu i začátečnických chby. I vlastní framework budete muset neustále vyvíjet a upravovat - a zkušenost praví, že je jednodušší počítat s pravidelnými updaty cizího frameworku, než neustále měnit něco svého. S obojím je hodně práce, dnes už nic nefunguje tak, že jednou uděláte a pak na to roky nemusíte sáhnout.

O Nette koukám nevíš nic, má dobrou dokumentaci, dokonce i v češtině. Nette doporučuju, hllavní výhoda je velká česká komunita. Pokud se ti nrlíbí něco na dokumentaci nebo v kodu, pošli pull request nebo věcnou issue na GitHub. Hanět umí každý a přiložit ruku k dílu už míň lidí, smutné. Já to dělám obráceně - když se mi něco nelíbí, nenadává, ale snažím se přispět.

Re:Bezpečná session v PHP
« Odpověď #9 kdy: 17. 03. 2020, 14:19:25 »
O Nette koukám nevíš nic, má dobrou dokumentaci, dokonce i v češtině. Nette doporučuju, hllavní výhoda je velká česká komunita. Pokud se ti nrlíbí něco na dokumentaci nebo v kodu, pošli pull request nebo věcnou issue na GitHub. Hanět umí každý a přiložit ruku k dílu už míň lidí, smutné. Já to dělám obráceně - když se mi něco nelíbí, nenadává, ale snažím se přispět.

U nás máme 80 % projektů na Nette a utíkáme od něj, protože trpí spoustou neduhů. Ne tolik technických, jako spíš v neuspořádanosti vývoje, ve vezrování a kompatibilitě komponent atd., které nikdo nespravuje. V produkci to přináší spoustu nákladů navíc. Nedokumentovaných funkcí je, že by člověk až brečel. Radím z velké praxe. Potřebuji vyvíjet to, co mám za úkol a ne dlouze dohledávat a na konci dohledávání zjistit, že to v celé Nette komunitě nikdo neřeší. Dokumentaci si porovnejte Symfony a Nette, knowledge base taky. David a Goliáš.

Re:Bezpečná session v PHP
« Odpověď #10 kdy: 17. 03. 2020, 14:31:51 »
Díky za odpovědi.

Ještě dva dotazy.

- Je v nějakém frameworku pro PHP JWT použitelně zpracované?
(než jsem položil dotaz, sleduju že jsem nechtěně vyrobil bitvu... tzn buď nette nebo sympfony)

- Pokud bych se rozhodl nepoužívat framework, tak v php existuje funkce
password_hash(string $password , int $algo ...)
Ta zdá se umožní volit jak šifrovací algoritmus, tak přidat sůl. Je to ok? Nebo děláte hash jinak?

Re:Bezpečná session v PHP
« Odpověď #11 kdy: 17. 03. 2020, 14:34:04 »
To co popisujete je klasické poor man's řešení, oblíbené v kuchařkách na internetu a pramenící tak někdy z devadesátých let.

Dnešní ezpečnost:
1. heslo zpracovat už na straně browseru; osolit a vygenerovat hash; případně lze ještě ze serveru poskytnout pubkey, browser data ještě zašifruje a rozšifruje je až server (na toto slouží lépe JTW); rozhodně neposílat postem heslo
2. po ověření zaznamenat do DB id uživatele a čas přihlášení (kvůli timeoutu)
3. do session uložit zašifrovanou informaci o přihlášení (id z předchozí tabulky; výhodné je používat UUID namísto SERIALU)
4. s každým požadavkem na server aktualizovat v tabulce čas posledního přístupu
5. hlídat timeout (po X hodinách od posledního přístupu už nepovolit přístup "přihlášenému" uživateli)
6. do session neukládat žádnou jinou významnou informaci, maximálně balast typu "poslední navštívená stránka", "produkty které jsem shlédl", ... ale i to raději ukládat do DB a sanitizovat

Co je špatně na $_SESSION „z devadesátkých let“ pokud používám vynucené https? IMHO je to pro řadu případů zcela dostačující řešení. Spíš bych si dal pozor na SQL injection při zadávání jména a hesla.

Re:Bezpečná session v PHP
« Odpověď #12 kdy: 17. 03. 2020, 14:39:21 »
Co je špatně na $_SESSION „z devadesátkých let“ pokud používám vynucené https? IMHO je to pro řadu případů zcela dostačující řešení. Spíš bych si dal pozor na SQL injection při zadávání jména a hesla.

Posílat heslo po HTTPS považuji za zbytečné, když může práci provést klient. Aplikace heslo nemusí nikdy znát a nikdy obdržet. Sníží se tím riziko MITM útoku na HTTPS. V devadesátých letech byla ještě spousta užití a prohlížečů, které JS implementovaly všelijak, takže se vše provádělo server side, mělo to racionální důvod. Dnes ten důvod už neexistuje a jede se jen ze setrvačnosti. Pan kolega se ptal na bezpečnost, tak k tomu směřovala odpověď.

Na SQL injections je opravdu potřeba si dát pozor, vždy a v každém případě!

Re:Bezpečná session v PHP
« Odpověď #13 kdy: 17. 03. 2020, 15:03:20 »
- Je v nějakém frameworku pro PHP JWT použitelně zpracované?
Určitě ano, ale pro vaše potřeby to je kanón na vrabce. JWT se používá pro SPA aplikace, kde máte nějaké API (třeba REST nebo GraphQL), kterým komunikuje webová aplikace se serverem. A server můžou tvořit třeba mikroservisy. Pokud chcete udělat pár formulářů v PHP, nic takového nepotřebujete.

- Pokud bych se rozhodl nepoužívat framework, tak v php existuje funkce
password_hash(string $password , int $algo ...)
Ta zdá se umožní volit jak šifrovací algoritmus, tak přidat sůl. Je to ok? Nebo děláte hash jinak?
Ano, je to OK. Naopak se to nesnažte dělat sám – pravděpodobnost, že to uděláte správně, když o tom nic nevíte, je skoro nulová.

Co je špatně na $_SESSION „z devadesátkých let“ pokud používám vynucené https? IMHO je to pro řadu případů zcela dostačující řešení. Spíš bych si dal pozor na SQL injection při zadávání jména a hesla.
Špatně na tom samozřejmě není nic, pro tazatele je to nejlepší řešení.

Posílat heslo po HTTPS považuji za zbytečné, když může práci provést klient.
To je hezká teorie. Jenže ten, kdo se tím může řídit, protože na to má dostatečné znalosti, se nebude takhle ptát na Rootu. Pro maw abi by to ale byla cesta do pekel, pro něj je nejlepší použít to, co používají všichni – takže v PHP klasickou $_SESSION, přihlašování přes formulář a POST, hashování hesla přes password_hash(). Resp. použít nějaký framework, který bude zapouzdřovat práci s databází (především kvůli hrozbě SQL injection) – přičemž je možné, že ten framework už bude mít i nějakou svou podporu pro přihlašování uživatelů (která ale interně bude používat výše uvedené).

Re:Bezpečná session v PHP
« Odpověď #14 kdy: 17. 03. 2020, 15:11:24 »
To je hezká teorie. Jenže ten, kdo se tím může řídit, protože na to má dostatečné znalosti, se nebude takhle ptát na Rootu. Pro maw abi by to ale byla cesta do pekel, pro něj je nejlepší použít to, co používají všichni – takže v PHP klasickou $_SESSION, přihlašování přes formulář a POST, hashování hesla přes password_hash()

Předpokládám, když se ptá, že chce získat přehled, jaké jsou možnosti. To, co popisujete, je možná nejpoužívanější, ale dnes už je jasné, že je to překonané a budoucnost si s tím nevystačí. Rozhodnutí je samozřejmě na tazateli, nemůžeme tu posoudit o jak citlivá data jde, jestli hrozí riziko finančních škod atd.