ugrás a tartalomhoz

Re: A PHP munkamenet-kezelés buktatói

Tyrael · 2012. Jan. 4. (Sze), 22.00

inf3rno év végi blogpostja kapcsán szeretném kifejteni, hogy szerintem mi a baj a PHP munkamenet-kezelésével, és min kellene változtatni.

A PHP munkamenet-kezelésének egyik fő problémája, hogy az implementáció nagyon sok helyen feltételezi, hogy a munkamenet egy globális, egypéldányos erőforrás. Ennek köszönhető például, hogy a session_encode() és session_decode() függvények csak a $_SESSION szuperglobális tömbből, illetve tömbbe tudnak dolgozni, s ennek javítása sem túl egyszerű, lásd az ide vonatkozó hibajegyet.

Ez ugye azt jelenti, hogy egy tetszőleges munkamenet ki- vagy becsomagolásához el kell menteni a $_SESSION aktuális állapotát, majd a művelet után kiolvasni és visszaállítani az eredeti állapotot.

Na de miért lehet arra szükség, hogy egyik sessionből egy másik sessiont kelljen piszkálni?

Maga az igény lehet valós, inf3rno is hozott fel egy példát, illetve magam is találkoztam olyan szituációval, ahol bele kellett kukkantani, vagy éppen írni egy másik munkamenetbe:

  • A bejelentett hibák okának feltárásakor nagyon hasznos tud lenni, ha magunkra ölthetjük a hibát jelző felhasználó személyiségét, az összes munkamenet-változóval együtt.
  • Egy másik esetben egy csak a sessionben tárolt információ alapján kellett statisztikát készíteni (hányan használnak egy adott funkciót).
  • Megint másik esetben felmerült, hogy egy hiba kapcsán helytelen érték került be bizonyos felhasználók munkamenetébe (tegyük fel, nem létező rekordra tároltunk le neki egy referenciát), itt megtehettük volna, hogy szoftverből lekezeljük ezt az esetet, vagy megvárjuk, míg kipörögnek az érintett munkamenetek, esetleg töröljük mindet, ezáltal kiléptetünk mindenkit, de szebb megoldásnak tűnt végigjárni az összeset, és csak azokat javítani-törölni, amelyek érintettek.

Itt jön képbe a következő (két) probléma:

  • A PHP alapértelmezett munkamenet-kezelője egy a serialize()-hoz nagyon hasonló, de mégis eltérő formában tárolja az adatokat (történeti okokból, ami a hibajegyben említett, elsőre nem nyilvánvaló megkötésekkel jár…), emiatt PHP-ben csak a fent említett függvényekkel lehet kibontani, hacsak nem a formátum újraimplementálásával (a függvények dokumentációjában a megjegyzésekben mások már megtették ezt).
  • A session_set_save_handler() híváson keresztül definiált munkamenet-kezelőben a read() / write() hívások karakterláncokkal dolgoznak, ezért nincs egyszerű lehetőség a függvények cseréjére.

Ez azt jelenti, hogy ugyanazzal a blob adattal kell dolgozni saját kezelő megadásakor is, amit a session_encode() éppen használ (amit egyébként felül lehet definiálni, de csak C kiterjesztésen keresztül, lásd session.serialize_handler).

Az eredeti probléma megoldásaként felmerülhet még megoldásként a kérdéses munkamenetek egy kérésen belül egyidejű helyett egymás utáni betöltése, azonban ennek is megvannak a hátulütői: a PHP ugyanazzal a névvel több sütit fog kiküldeni, ami nem túl szerencsés, mivel ha minden más paraméter (tartomány, elérési út) azonos, akkor nem megjósolható, hogy adott böngésző melyiket fogja visszaküldeni a következő kérésnél.

További bajnak tartom, hogy a session, ezen belül is a session_set_save_handler() dokumentációja nem tesz említést arról a fontos tényről, hogy az alapértelmezett (és minden normális) munkamenet-kezelő indításkor kizárólagosan zárolja az állományokat, megakadályozva ezzel, hogy két párhuzamos kérés inkonzisztens, vagy éppen teljesen olvashatatlan állapotba hozza az adatokat.

Nem hatalmas gond, de személy szerint nem szeretem, hogy az alapértelmezett kezelő session fixationre sebezhető azon egyszerű okból kifolyólag, hogy a kliens tetszőleges azonosítót kérhet a szervertől (tehát indíthat olyan azonosítóval munkamenetet, ami a munkamenet-kezelő szerint nem létezik még). Többek között erre a biztonsági problémára próbál megoldási javaslatot kínálni a tavaly év végén létrehozott Strict Sessions RFC, további információkért lásd a kapcsolódó szálat a levelezőlistán.

Személy szerint nagyon remélem, hogy a következő (5.5?) verzióban sikerül egy sokkal jobb, rugalmasabb és biztonságosabb infrastruktúrát adni a felhasználók kezébe, és egyúttal megszabadulni a múlt káros örökségeinek egy részétől.

 
1

a PHP ugyanazzal a névvel

tgr · 2012. Jan. 5. (Cs), 08.35
a PHP ugyanazzal a névvel több sütit fog kiküldeni, ami nem túl szerencsés, mivel ha minden más paraméter (tartomány, elérési út) azonos, akkor nem megjósolható, hogy adott böngésző melyiket fogja visszaküldeni a következő kérésnél.


Egyébként ha nem azonos, akkor se, ebbe épp mostanában futottam bele: path-nál előírja a nememlékszemmelyik RFC, hogy a specifikusabb sütit kell visszaküldeni, de domainnél nem, és pl. a Firefox nem is azt teszi, hanem a később beállított sütit használja, ami remek lehetőség session fixation-re, ha a támadó és a céldomain ugyanazon másodszintű domainnév alatt van.
4

igen, ebbe nem akartam

Tyrael · 2012. Jan. 5. (Cs), 10.25
igen, ebbe nem akartam belemenni, a suti tamogatas kulon cikket erne meg.
vannak nagyon vicces ticketek a bugzillaban, ahol pl. a firefox fejlesztok fociznak heteken keresztul, hogy most hogy is kellene mukodnie, mert az eredeti A RFC-ben nem volt benne hogy mi a teendo, az ezt kiegeszito B RFC-ben benne volt, majd a B RFC levalto C RFC megint nem rendelkezik rola, es a tobbi browser meg egy masik modon implementalta mint a B RFC, mert az A RFC-t a netscape draftja alapjan csinaltak, de az IE maskepp tamogatta a featuret, mint a B RFC, ezert a tobbi browser inkabb ot kovette.

ha megtalalom kikeresem ezt a bugot, mert biztos nem sikerult tokeletesen visszaadnom a sztorit.

update: megtalaltam az emlitett bugot: ime

Tyrael
2

Session lock - hogyan?

tisch.david · 2012. Jan. 5. (Cs), 09.16
Szia Tyrael!

Lenne egy kapcsolódó kérdésem, örülök, hogy szóba hoztad!
... az alapértelmezett (és minden normális) munkamenet-kezelő indításkor kizárólagosan zárolja az állományokat ...
Én saját, MySQL tábla alapú session kezelést használok, és pont a fenti kitétellel van problémám. Szerettem volna az egész session kezelést (adott session feléledése egy kattintás hatására, kérés kiszolgálása, session elmentése) egy tranzakcióba zárni és lockolni, hogy két párhuzamos kérés ne csaphassa agyon egymás hatását. Ezzel együtt a kérés kiszolgálása közben is vannak olyan események, melyek összetartoznak, így azokat külön is szerettem volna tranzakcióba zárni, lockolással. Nos, tudtommal, pont ez az, ami nem megy, mert a belső tranzakció nyitásakor a külső implicit módon COMMIT-olódik.

Van erre valamilyen normális megoldás?

Köszi! Üdv:

Dávid
3

innodb alatt vannak

Tyrael · 2012. Jan. 5. (Cs), 10.18
innodb alatt vannak savepoint-ok, nem pont ugyanaz, mintha lenne tenyleges egymasba agyazott tranzakcio tamogatas, de tudsz 1-1 ilyen savepointra rollbackelni:
http://dev.mysql.com/doc/refman/5.1/en/savepoint.html

Tyrael
5

Köszi! Erről nem tudtam.

tisch.david · 2012. Jan. 5. (Cs), 13.06
Köszi! Erről nem tudtam. Átgondolom, hogy érdemes-e használnom őket.
6

ha nem lenne egyertelmu mire

Tyrael · 2012. Jan. 5. (Cs), 13.47
ha nem lenne egyertelmu mire gondoltam, minden ilyen osszetartozo muveletsor elott leraksz egy ilyen savepoint-ot, es ha nem sikerul, akkor vissza tudsz rollbackelni a muveletsor elotti allapotra a tranzakciodban, hogy ujraprobalhasd a muveletsort.

Tyrael
7

Köszi, én is erre gondoltam!

tisch.david · 2012. Jan. 5. (Cs), 16.06
Köszi, én is erre gondoltam! A belső COMMIT hiánya miatt gondolkodom csak a dolgon. Jelenleg az az áthidaló megoldás, hogy a külső tranzakciót elhagytam, mert a belső fontosabb volt, a session tranzakciózása meg a gyakorlatban elhagyhatónak tűnt (jobb hijján). Tűnődöm még, hogy visszategyem-e és belül meg átálljak-e ilyen SAVEPOINT használatra.

Dávid
8

Kettőn áll a vásár

napalm · 2012. Jan. 9. (H), 11.43
Szerintem mint a legtöbb szerver-kliens kapcsolatra épülő technikánál, itt sem lehet egyértelműen kijelenteni, hogy X a ludas vagy Y.

Hiába kezeli a szerver oldali alkalmazás helyesen, RFC szerint a sessionoket és sütiket, ha egyszer a browser fejlesztőket nem lehet rávenni arra, hogy ők IS egységesen, RFC szerint kezeljék őket. Pillanatnyilag a szerver és kliens oldali alkalmazások mutogatnak egymásra, "igen te kezeled rosszul" vs "nem én jól, te vagy szabványtalan". IMO Legalább akkora szerepe van a böngészőknek is ebben a körben, mint a szerver alkalmazásnak, csak a böngészők megtehetik azt, hogy bizonyos dolgokban kompatibilisek legyenek csak saját magukkal (M$ IE esetén sokszor még ez sem sikerül :), szerver oldalon és a programozókól meg elvárás, hogy minden klienssel tökéletesen működjön minden. Hálátlan dolog azért ilyen helyzetben kijelenteni, hogy X nyelv munkamenet kezelése kuka, mert.. alapvető dolog lenne, hogy 100%-ban be legyen tartva mindenki részéről az RFC ahogy azt kell, és utána kellene goldolkodni hogyan tovább. Amikor egy chrome nem mindíg (! ez a legroszabb) hajlandó a session_destroy()-t végrehajtani olyan sütiknél ahol nincs domain megadva, mert neki ahhoz nincs kedve, ugyanakkor olvasás megy belőle.. stb. Hasonló vicces dolog előfordul minden browsernél.

Ps: egyébként ez ugyanaz az eset, mikor 20 centis betűkkel ki van vésve X browser marketinganyagába, hogy CSS 18-at támogat! Remek! Már csak az a baj hogy CSS 2-t sem tudja az adott browser 100%-ban :( Szóval részemről /slap böngészőverseny, ha erre nem lesz szükség majd utána /slap PHP
9

nem vagyok benne, hogy jo

Tyrael · 2012. Jan. 9. (H), 13.20
nem vagyok benne, hogy jo helyre ment a valaszod, de azert megprobalok valaszolni ra:

"Szerintem mint a legtöbb szerver-kliens kapcsolatra épülő technikánál, itt sem lehet egyértelműen kijelenteni, hogy X a ludas vagy Y."

http://tools.ietf.org/html/rfc6265
"The portions of the set-cookie-string produced by the cookie-av term
are known as attributes. To maximize compatibility with user agents,
servers SHOULD NOT produce two attributes with the same name in the
same set-cookie-string. (See Section 5.3 for how user agents handle
this case.)"

"Servers SHOULD NOT include more than one Set-Cookie header field in
the same response with the same cookie-name. (See Section 5.2 for
how user agents handle this case.)"

Persze ez az RFC elegge friss (2011 aprilis), viszont a mostani browser megegyezesek figyelembevetelevel keszult, ezert szerintem batran kijelentheto, hogy nem lenne szabad a php-nek 1 valaszban tobb azonos nevu sutit visszaadnia (kiveve persze ha a fejleszto explicit keri).

"Hiába kezeli a szerver oldali alkalmazás helyesen, RFC szerint a sessionoket és sütiket, ha egyszer a browser fejlesztőket nem lehet rávenni arra, hogy ők IS egységesen, RFC szerint kezeljék őket."

ahogy emlitettem a tgr-nek irt valaszomban, a problema az, hogy anno elobb lett es terjedt el a cookie, mint hogy kialakult volna a standard, majd a netscape elkeszitette ugyan az elso RFC-t, de se ok, se az IE nem kovette ezt szigoruan soha.
lasd: http://en.wikipedia.org/wiki/HTTP_cookie#History

ezek utan az alternativ browser fejlesztok (ff, opera, chrome) nem sokat tehettek, vagy kovetik az RFC-t (ami egyebkent egy csomo mindent nem specifikal megfeleloen) vagy nehol elternek tole, de igy kompatibilisek maradnek a mainstream browserek mukodesevel.

"Hálátlan dolog azért ilyen helyzetben kijelenteni, hogy X nyelv munkamenet kezelése kuka, mert.. "
nem irtam ilyet, ami problemakat felhoztam, azok (bar nyilvan valamennyire szubjektiv) valos, letezo gondok, es egy reszukre mar keszul a javitas.

"alapvető dolog lenne, hogy 100%-ban be legyen tartva mindenki részéről az RFC ahogy azt kell, és utána kellene goldolkodni hogyan tovább."
ha igy mukodne a vilag, akkor sokkal egyszerubb lenne, de nem igy mukodik. :(

"Amikor egy chrome nem mindíg (! ez a legroszabb) hajlandó a session_destroy()-t végrehajtani"
? a session_destroy()-t a szerver oldalon a php hajtja vegre:

"session_destroy() destroys all of the data associated with the current session. It does not unset any of the global variables associated with the session, or unset the session cookie. To use the session variables again, session_start() has to be called.

In order to kill the session altogether, like to log the user out, the session id must also be unset. If a cookie is used to propagate the session id (default behavior), then the session cookie must be deleted. setcookie() may be used for that."

szoval nem latom, hogy hogy mulhatna a kliensen, hogy sikeresen torlodik-e a sessionhoz tartozo adat a szerver oldalon, vagy nem.

"Hasonló vicces dolog előfordul minden browsernél. "
sajnos igen.

"Ps: egyébként ez ugyanaz az eset, mikor 20 centis betűkkel ki van vésve X browser marketinganyagába, hogy CSS 18-at támogat! Remek! Már csak az a baj hogy CSS 2-t sem tudja az adott browser 100%-ban :( Szóval részemről /slap böngészőverseny, ha erre nem lesz szükség majd utána /slap PHP"
a w3c sajnos csak ajanlasokat keszit, viszont a CSS kapcsan en pont azt erzem, hogy sikerult nagyon elszakadniuk a valos igenyektol az ajanlasokkal, de latok nemi javulast a html5 kapcsan.

a tanulsag szerintem az, hogy a szabvany nem er semmit, ha nem tartjak be, ahogy a betarthatatlan szabvany sem.

Tyrael