Letscode.hu

… minden ami fejlesztés

Önvédelmi gyakorlatok – CSRF és Session hijacking

7 min read

Az előző részben végigvettünk pár támadási formát és az ellenük való védekezés lehetőségét. Ebben a részben a CSRF-el, a cross site request forgery-vel és a session hijackingel fogunk foglalkozni.

A CSRF támadások azt használja ki, hogy az adott oldal megbízik a felhasználóban és nem ellenőrzi, hogy adott lekérést valóban a felhasználó vitte-e véghez, mégpedig valóban a saját oldaláról.form_builder

A CSRF támadások általában fórumokról indulnak, ahol az egyes hozzászólások szövegét az XSS-nél említett htmlentities-el már hatástalanították, így javascript kódot nem tudnak posztolni, ellenben képet igen. A képek src attributumában el lehet helyezni ártalmas kódot, a következőképpen:

<img src="http://letscode.hu/wp-admin/post.php?post=243&action=delete" />

Ugye mikor a böngészőnk ezt meglátja, akkor azt feltételezi, hogy az src-ben szereplő lekérés egy képet fog majd visszaadni, így küld egy lekérést oda is.

De hát ezt meg tudná csinálni a saját böngészőjével is, mi ebben a pláne?

A pláne az, hogy a lekérést a felhasználónk böngészője teszi, amiben ott lapulnak az esetleges oldalra tartozó session cookie-k, amik által a szerver őt fogja a lekérés indítójaként azonosítani, ha az oldal megbízik a kérésben és elhiszi, hogy azt valóban a felhasználó végezte. (A böngészőnk pedig rinyál egy sort, hiszen a lekérés által visszaadott tartalom text/html. Viszont a támadónk se fogja látni, hogy az adott lekérésre milyen válasz jött, max ha JS kódot is tud futttatni az adott felületen, de akkor már XSS-ről is beszélünk)

Persze ez az eset közel sem nevezhető súlyosnak, hiszen nem pénzügyi adatokról és hasonlókról van szó, de ugyanezt az elvet követve durva dolgok is történhettek volna.

Tehát a lényeg:

A user meglátogatja B oldalt, ahol egy C oldalra mutató link van img tagben, ami C oldalon A user be van épp jelentkezve.

Mit is tegyünk ez ellen?

Régi megoldások egyike, hogy a session ID-t GET paraméterben is elfogadta az adott oldal. Ezt NE tegyük. Session ID-t mindenesetben cookie-ban tároljunk, viszont még ez se véd meg minket mindentől, ugyanis a PHP alap session ID-je viszonylag limitált értéket vehet fel és azt a timestamp alapján generálja, így egy bizonyos tartományon belüli értéket fog felvenni, amit a támadó kikövetkeztethet.

1. szabály, a GET paraméterek, ahogy nevük is mutatja, csupán adatok lekérdezésére szolgál(ja)nak, bárminemű módosításhoz POST kérés szükséges.

Ha a GET paramétereket a helyén kezeljük (és csak a routerünk használja azt, lehetőleg whitelistingel), akkor maradnak a POST lekérések, valamint ellenőrízzük a HTTP_REFERER-t, hiszen így kiszűrhetjük, ha más oldalról érkezik a lekérés.

Megjegyzés: ahol nincs űrlap és annak adatainak feldolgozása, ott nem lehet CSRF támadást véghezvinni

Ha POST kérést küldtünk a szerverünknek, még mindig lehetünk pácban. Ennek kivédésére jött létre az ún. CSRF token. Ennek az a lényege, hogy egy a támadó által ismeretlen mező/érték párost is beleteszünk az űrlapunkba és amikor az űrlapadatokat kiértékeljük, ez alapján is szűrjük a dolgot.

Ezt a következőképp tehetjük: generálunk egy ún CSRF token-t. Ezt a legegyszerűbben egy md5(uniqid(rand()))-al (de md5-öt ne használjunk, erre majd kitérek miért) tudjuk megtenni, ami generál nekünk egy egyedi azonosítót, amit hidden input field-ként az űrlapunkba teszünk (minden egyes űrlapnál újragenerálva), valamint a $_SESSION-be is. Amikor kiértékeljük a formot, ellenőrízzük, hogy létezik-e egyáltalán az sessionben az adott kulcs, ha nem, akkor a lekérést figyelmen kívül hagyjuk. Ha nem egyezik a formban kapott CSRF token értéke és a sessionben tárolt érték, szintén invalid a lekérés. Ha minden klappol, akkor elkezdhetünk foglalkozni a form tényleges tartalommal bíró elemeivel.

A bejelentkező űrlapoknál lehet látni az ún. bejelentkezve maradok jelölőnégyzetet, ami csupán a session cookie élettartamát hivatott változtatni. Ezzel az a baj, hogy minél tovább bejelentkezve tartjuk a felhasználónkat, annál több idő van kitalálni azt a bizonyos CSRF tokent, netán magát a session ID-t.

És itt el is érkeztünk a cikkünk második feléhez, a session hijackinghez.

Session hijacking

Mi alapján azonosítjuk a felhasználónkat? Honnan tudjuk, hogy ő az? Hát $_SESSION-ben ott van egy hozzá tartozó mező, nem? Na de a szerverünk honnan azonosítja azt a bizonyos session-t, hiszen mint tudjuk a HTTP egy stateless protokoll, vagyis a két lekérés közt nincs kapcsolat, egymástól függetlenek. Sütik, mint mindig. Egy ún. PHPSESSID (legalábbis alapértelmezett esetben) cookie-t küld el a szerverünk minden egyes kéréssel és a szerverünk ezáltal tudja hozzárendelni az adott session-t a lekéréshez. A HTTP 1.1 így tudja elérni, hogy úgy tűnik, mintha mégsem lenne stateless az a protokoll 🙂e2b

Ellenben gondoljunk bele, hogy egy unatkozó kisovis a 1,5 perces matek leckéje után írt egy script-et a gépén, ami a háttérben fut és másodpercenként jópár lekérést küld cURL-el az oldalunk egy olyan részére, ahol ezt a session ID-t használnánk arra, hogy eldöntsük a felhasználónk bejelentkezett-e és akkor látja a tartalmat, vagy sem és egy egyedi 403-as error oldalra dobjuk. A pláne abban lesz, hogy a fejléc mezőben elküldött cookie-k közé a PHPSESSID értékét dinamikusan változtatja, magyarán a kis piszok brute force-al próbál egy olyan session id-t találni, ami valakihez tartozik és a cURL által kapott válaszok hash-ét összehasonlítgatva bizony hamar rá is jön, ha olyat talál ami egy bejelentkezett felhasználónkhoz tartozik, mivel a kimenet ott nem a 403-as hibaoldal (vagy akármi más) lesz.

A probléma abban rejlik, hogy a php által generált és sütik közt elhelyezett session ID alapesetben elég rövidke ahhoz, hogy túléljen egy brute force támadást. A megoldás kulcsa pedig az lesz, ha külön ellenőrzés alá vetjük a dolgot:

Generálunk valami cifra session ID-t, mégpedig úgy, hogy hasheljük a felhasználónk IP-jét, User Agent-jét, a timestampet és esetleg megsózzuk*.

define('SESSION_SALT', 'EzIttenASó12%+!#');
$quiteSaferSessionId = sha2($_SERVER['USER_AGENT'].$_SERVER['REMOTE_ADDR'].time().SESSION_SALT);

Salt (só): A jelszavakban és egyéb egyirányú kriptográfiai eljárásokkal titkosított tartalmakban alkalmazott fix karakterlánc, ami matematikailag megnehezíti annak a hashelt tartalomnak a brute force technikával való feltörését. A dolgot úgy kell elképzelni, hogy egy karakterláncot fixen hozzáfűzünk, mielőtt titkosítanánk azt, majd összehasonlításkor szintén. Ennek a salt-nak titkosnak és lehetőleg bonyolultnak kell lennie.

Na de ha megvan ez a szép hosszú session ID, akkor mégis hogy lehetne ezt validálni? Hát először is a hosszával, mivel ez más algoritmussal lett generálva. Aztán ennek is létrehozunk egy táblát az adatbázisban, a már említett változókkal és a belőlük generált session ID-vel. A timestamp csak a session élettartamára vonatkozik, itt ellenőrízhetjük, hogy mikor generáltuk és ha esetleg lejárt a munkamenet, akkor törölhetjük azt és átirányíthatjuk a felhasználónkat a login oldalra. A többi értéket is összevetjük és ha eltérés van, akkor bizony invalid a session, töröljük azt.

Több session-t is rendelhetünk ilyen módon egy adott felhasználóhoz és később annak profiloldalán lehetőséget biztosíthatunk, hogy kiléptessük azt az egyes eszközökről, anélkül, hogy attól kellene tartanunk, valaki bruteforce-al ellopja a felhasználónk identitását.

A fenti támadási formák némileg összefolynak, ezért nehéz külön beszélni róluk (ahogy pl. a session fixationről), mivel az egyes sebezhetőségek új és új utakat nyitnak meg más támadási formák számára.

6 thoughts on “Önvédelmi gyakorlatok – CSRF és Session hijacking

  1. 1. Igen, ha a gonosz ISP dinamikusan osztogatja az IP-t, akkor sajnos ez a módszer nem fog működni, de ez ilyen valamit valamiért. Esetleg annyit lehet rajta javítani, hogy csak egy részét tárolod, hogy más kontinensről ne betyárkodjanak.
    2. Igen, ezt nem nagyon tudod megkerülni, de mivel az adatbázisban csak az aktív session-ök vannak, amik lejárnak X idő után, akkor nem egy őrült nagy számról beszélünk, a select pedig gyors szokott lenni, de amúgy ja, memcache és helló 🙂
    3. Azt esetleg meg lehet csinálni, hogy a 403 hibaoldalba beintegrálsz egy random html kommentet, amiáltal a kimenet MD5-je és a hossza is egyaránt változhat.

  2. Nem teljesen látom át, hogy a gyakorlatban hogyan kell használni a generált session id-t.
    1. Tegyük fel hogy loginnal indul a weboldalam, beazonosítja magát a felhasználó
    2. Generálok neki egy session id-t
    3. Lementem adatbázisba
    4. Lementem sütibe a session id-t?

    Amikor lekérdez egy oldalt a felhasználó, akkor pedig:
    1. Ellenőrzöm, hogy be van-e lépve és érvényes-e még az ő sütijében levő session?
    2. Ha igen megjelenítem neki a weboldalt
    3. Ha nem akkor loginra küldöm

    Valahogy ilyen lépésekkel kéne megvalósíani? Kicsit homályos nekem a validálás rész, hogyan kapom meg a nála levő sessionjét? Vagy így írás közben az jutott még eszembe, hogy nem sütit kérek le tőle, hanem mindig kigenerálom a session id-t és rákeresek az adatbázisomban, hogy van-e ilyen session. Bár ennél a módszernél meg az időbélyeg miatt nem lesz egyezés és lehet lassú is.

    1. Na jó, 10 perce írom a hozzászólást, inkább csinálok egy új posztot a konkrét implementációról, majd csekkold le 😀

Comments are closed.

Copyright Letscode.hu 2014-2020 © Minden jog fenntartva. | Newsphere by AF themes.