2009. március 6., péntek

Webalkalmazások biztonsági hibái - 2. Hitelesítési bakik

Betörési tesztjeink során gyakran találkozunk azzal a jelenséggel, hogy a weboldalak fejlesztői ugyanazokat a hibákat követik el újra és újra. Ezen klasszikus hibákat egy több részből álló sorozatban szeretnénk bemutatni.

Hitelesítés során a felhasználót azonosítjuk: klasszikus definícióban a hitelesítés olyan információ felmutatását jelenti a hitelesíteni kívánt felhasználótól, amit a felhasználó ismer (jelszó), birtokol (token, smart card), vagy a felhasználóhoz egyértelműen köthető (pl. ujjlenyomat). Első közelítésben tehát pofonegyszerűnek tűnhet a dolog: a felhasználó regisztrál, megadja a jelszavát, aztán belépés előtt ellenőrizzük, hogy a belépési felületen megadott jelszó megfelelő-e.

A gyakorlatban számos buktatója lehet ennek az "egyszerű" sémának, lássunk néhány példát.

Jelszavak bedrótozása a kódba. Az egyik leggyakoribb hiba az, amikor az alkalmazás fejlesztője beleírja explicit módon a jelszót (kevésbé durva esetben annak lenyomatát) az alkalmazás kódjába. A probléma nem csak a felhasználók jelszavaira vonatkozhat (mondjuk ott többnyire az adminisztrátori account van kőbe vésve), hanem az adatbázishoz történő kapcsolódás connect stringjeivel, az esetleges backendekhez történő authentikáció során használt hitelesítési információkkal mind-mind találkoztunk már. A legdurvább hiba, amibe belebotlottunk, az volt, amikor egy on-line payment(!) rendszerhez történő kapcsolódás éles felhasználóneve(!!) és jelszava(!!!) volt beleírva a PHP kódba.

Miért jelent ez biztonsági kockázatot? Egyrészt azért, mert nem lehet menedzselni központilag (vagy a kézzel történő átírástól különböző módon) a jelszavakat, másrészt ha a támadó valamilyen módon hozzáfér az alkalmazás forráskódjához (mondjuk egy file inclusion sebezhetőséggel, vagy ugyanazon szerver egy másik alkalmazásának kompromittálásával), automatikusan hozzáférést szerez az alkalmazás által megvalósított logikához is.

Nem átgondoltan megvalósított "emlékeztető kérdés"-funkció. Emlékeznek Sarah Palinre? Tavaly év végén volt óriási botrány abból, hogy "feltörték" a yahoo-s postafiókját, és a benne található információkkal járatták le a hölgyet. A probléma az volt, hogy a Yahoo a regisztrációs eljárás során csak néhány, tipikus kérdést tesz lehetővé arra az esetre, amikor a felhasználó elfelejti a jelszavát: anyja neve, hol lakott két éve, mi volt a kutyája neve stb. Ezen információk könnyen kideríthetőek a különféle social networking alkalmazások segítségével, vagy akár egyszerű social engineeringgel is.

Másrészről gyakran előkerülő hiba, hogy a különféle brute-force támadások elleni intézkedések (már ha vannak egyáltalán) csak a "főbejáraton" vannak érvényben: találkoztunk olyan esettel, amikor az "elfelejtettem a jelszavam" funkció a felhasználó e-mailcímének bevitele után megkérdezte, hogy mi a kedvenc színünk. Színből márpedig nincs túl sok, a keresési tér így triviálisan leszűkíthető volt néhány (tíz) elemre, ezek végigpróbálgatása pedig rövidebb ideig tartott, mint ezt a bekezdést végigolvasni (bordó volt a kedvenc színe az illetőnek egyébként). Az ilyen jellegű támadásokat megkönnyíti az is, hogy számtalan tematizált szólistát lehet letölteni a netről (filmszínészek, cégnevek, kutyanevek, városok stb.)

A regisztrációs felületen enumerálható felhasználók. Nem szorosan hitelesítéshez kapcsolódik, de gyakori hiányosság, hogy a felhasználók szabadon megválaszthatják a felhasználónevüket, a felületen pedig nincs védelem viharszerű próbálkozások ellen. A támadó a regisztrációs felületen végigpróbálgathat egy szólistát az érvényes felhasználónevek után kutatva.

Nem megfelelő belépési logika. A belépési felület mögött található logika több szempontból is érzékeny pont. Néhány példa a hibás implementációkra.

Fail-open hitelesítési logika. Tipikusan hibás implementáció az alábbi kód:
public Response checkLogin(Session session) {
try {
String uname = session.getParameter("username");
String passwd = session.getParameter("password");
User user = db.getUser(unamepublic Response checkLogin(Session session) {
try {
String uname = session.getParameter("username");
String passwd = session.getParameter("password");
User user = db.getUser(uname, passwd);
if (user == null) {
// invalid credentials
session.setMessage("Login failed.");
return doLogin(session);
}
}
catch (Exception e) {}
// valid user
session.setMessage("Login successful.");
return doMainMenu(session);
}, passwd);
if (user == null) {
// invalid credentials
session.setMessage("Login failed.");
return doLogin(session);
}
}

catch (Exception e) {}
// valid user
session.setMessage("Login successful.");
return doMainMenu(session);
}

Mi itt a gond? Ha a felhasználó pl. manuálisan kitörli az elküldött HTTP-kérésből az "username" mezőt, exceptiont dob a kód, viszont az exception kezelése után belépteti a felhasználót. Igaz, hogy az esetek nagy részében nem lesz valid felhasználóhoz köthető a session, de valószínűleg elérhetőek lesznek a támadó számára tiltott funkciók is.

Többlépcsős beléptetési folyamat során közvetlenül elérhető az elsőtől különböző lépést implementáló felületek. Ez a hiba abból adódik, hogy a login logika implementálásakor a fejlesztő abból a feltételezésből indul ki, hogy a felhasználó, amikor a harmadik lépéshez ér a folyamatban, sikeresen túllépett az első kettőn. Főleg akkor jelent súlyos biztonsági kockázatot a hiba, ha az utolsó lépésben pl. egy rövid, számokból álló kód beütése szükséges, a többi adatot (pl. a felhasználónevet) hidden mezőkből, vagy cookie-ból veszi a logika: meghamisítva a szükséges adatokat, könnyen végig lehet próbálgatni a kód lehetséges értékeit.

A hitelesítés nem terjed ki minden funkcióra. Sok esetben felmerül az a probléma, hogy bár van hitelesítési eljárás érvényben, a logika nem minden elemére terjed ki. Gyakori példa erre az, amikor webalkalmazásból megvásárolható tartalmak (pl. videók, pdf-ek) közvetlenül, hitelesítés nélkül elérhetőek, ha ismerjük az URL-t. Majdnem minden tesztelési munka során használjuk azt a módszert, hogy a kapott HTML-kódból kigyűjtjük a hivatkozásokat css fájlokra, javascriptes forrásokra, php, jsp lapokra stb., és mindenféle paraméterezés nélkül meghívjuk őket. Sok esetben (bár be kellene jelentkezni a funkciók használatához) elérhető funkciókat biztosítanak, másrészről pedig hasznos és beszédes hibaüzeneteket lehet így kisajtolni az alkalmazásból.

Gyakori baki az is, ha a például a felhasználókat regisztráló form nem saját magának postolja el a bevitt adatokat, hanem másik php (jsp, asp, cgi,...) végzi a feldolgozást. Ha ilyen megvalósítással találkozunk, azonnal felmerül a kérdés, hogy vajon a feldolgozószkript végez-e ellenőrzést a forrás hitelessége felől, illetve a front-end felületen működő logikai kontrollok érvényben vannak-e a feldolgozásnál is: találtunk már olyan sebezhetőséget, ahol egy on-line foglalási rendszerben a felületen csak az elkövetkezendő két hétre lehetett foglalni, viszont a feldolgozószkript nem végzett ellenőrzést. Ezzel a módszerrel tetszőleges időpontra tudtunk foglalást feladni.

Egyszerű jelszavak használatának engedélyezése. Rengeteg esetben egyszerűen ki lehet találni(!) az adminisztrátor vagy egyéb felhasználó jelszavát. Toplista néhány tipikusról, ezeket használva meglepően sokat ki lehet találni:
  • Születési idő
  • Cégnév
  • Loginnévvel megegyező
  • "Jelszo123", "admin",...
  • a webalkalmazás neve
  • egybetűs jelszó
Plain-text jelszótárolás. Triviálisnak tűnhet, de ennek ellenére újra és újra előjön az egyik legsúlyosabb "időzített bomba", amit webalkalmazásba a fejlesztés során tenni lehet. Egy esetben meg is indokolták a fejlesztők, hogy miért volt tudatos döntés ilyen módon tárolni a jelszavakat: a felhasználók időről időre elfelejtik a jelszót, ezért ekkor e-mailben ki kell küldeni nekik...

A felhasználói hitelesítés hibái igen súlyos következményekhez vezethetnek, ugyanis a támadó ekkor illetéktelenül hozzáférhet az alkalmazás funkcióihoz, és sok esetben további sebezhetőségek kihasználásával akár a teljes szervert is kompromittálhatja.

Sorozatunk következő részében a webalkalmazások hanyag inputvalidációs hibáit vesszük sorra.

3 megjegyzés:

  1. Szia!

    Lenne egy kérdésem a jelszavak bedrótozásával kapcsolatosan. Sajnos PHP-ben példálul a mysql elérését ténylegesen bele kell drótozni a kódba. Én a kapcsolati információkat egy objektumba emelem ki. Elegendő-e a védelem, ha ezt az objektumot tartalmazó file-t a webroot-on kívül helyezem el?

    VálaszTörlés
  2. Szervusz!

    A webalkalmazások és a háttérben futó adatbázis-kezelő kapcsolatát nem egyedül a connect string titkossága védi. Tesztelésnél nyilván hiányosság, ha a támadó megszerzi, de ez ellen lehet védekezni (bizonyos szintig) az általad is javasolt megoldással.

    További ötletek az adatbázis-kezelő megvédésére:
    - Úgy kell konfigurálni a DBMS-t és a tűzfalakat, hogy csak onnét lehessen TCP szinten elérni a szolgáltatást (TCP szinten portot és feljebbi szinten magát a szolgáltatást), ahonnét legitim működés közben használják, tipikusan a localhostról.
    - A felhasználónév/jelszó, amit a connect stringben használsz, nem szabad, hogy mást is nyisson.
    - Ha az adatbázis-kezelőd támogatja, bejelentkezési helyes + tanúsítványos bejelentkezést is alkalmazhatsz pusztán jelszavas helyett.
    - Az adatbázis-kezelő felhasználóinak úgy kell osztani a jogosultságokat, hogy csak azokat az adatbázisokat és táblákat érjék el, amelyre a működéshez szükségük van. Nyilvánvaló, hogy adminisztrátor jogkörű accountot nem használunk service accountként.

    Ez csak néhány tipp, de ezeket alkalmazva már nem fenyegeti (akkora) biztonsági kockázat a webalkalmazásod és a DBMS kapcsolatát.

    VálaszTörlés
  3. Angolban ezt a témakört szét szokták kapni két részre: authentication és authorization. Az elsőnél az indentification factors (amiket ti is felsoroltatok, pl ujjlenyomat) alapján azonosítja a rendszer a felhasználót, és jogokat oszt a session-nek ez alapján, a másodiknál a session jogosultságát ellenőrzi a rendszer az adott funkció elérésére. A cikkben egy kicsit keverve van ez a kettő. Mondjuk sokszor van az, hogy a session-ben felhasználói azonosítót tárolnak, és az alapján ellenőrzik a jogokat authorization esetében, úgyhogy bizonyos szempontból jogos ez a keveredés.

    A jelszó bedrótozása a kódba szerintem inkább kód karbantarthatósági hiba, az ilyen jellegű konstansokat inkább konfig fájlokból szokás beparsolni, de file inclusion, xee, stb... esetében azok is védtelenek. Tudtok esetleg jobb módszert, amivel az ilyen jelszavakat védeni lehet még kód lopás esetében is?

    VálaszTörlés

Kommentek