2009. április 6., hétfő

Webalkalmazások biztonsági hibái - 5. SQL injection

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.

Az SQL injection sebezhetőség a szerveroldali inputvalidációs kontrollok igen népes családjának prominens tagja, viszont önmagában is kiemelt jelentőséggel bír, ezért külön foglalkozunk vele.

Majdnem minden webalkalmazás használ valamilyen formában adatbáziskezelőt háttérrendszerként: kényelmes, gyorsan fejleszthető és megfelelő termékkel kimondottan hatékonyan működtethető infrastruktúrát alkot a webszerver(ek) mögött dolgozó adatbázisszerver. A legtöbb webalkalmazás széleskörű adattárolási feladatokra használja: itt tárolják többek között az üzleti logika adatait, a felhasználóneveket-jelszavakat és az éppen aktuális sessionök azonsítóit is akár. A webportálok felépítéséből és használatából következően viszont előbb-utóbb eljön az a pillanat, amikor felhasználói inputot illeszt SQL query-be - és itt kezdődnek a problémák.



Mi okozza a gondot? Az SQL injection sebezhetőségek abból fakadnak, hogy a webalkalmazást az adatbázis-kezelő rendszer queryértelmezője biztonságos forrásnak hiszi, és készséggel megtesz bármit, amire utasítást ad. Első példaként lássuk az alábbi PHP kódot:

keres.php:
<?php
$query="select * from tabla where id=$_GET['azon'];";
run($query);
?>

Mi itt a probléma? Ha a rosszindulatú felhasználó az alábbi HTTP-kérést adja ki: keres.php?azon=2; drop database; , akkor mi történik? Két SQL queryt kap meg az értelmező, az egyik a select * from tabla where id=2, a másik pedig a drop database lesz. Ha az adatbázis-kezelő lehetővé teszi a kötegelt queryk feldolgozását (például az MS-SQL), készséggel eldobja a második query miatt a teljes adatbázist.

A fenti példa természetesen nagyon naivnak tűnik, de gyakori eset, hogy a webfejlesztők a nem közvetlenül a felhasználótól érkező inputok tekintetében egész egyszerűen megfeledkeznek a validálásról (például hidden html mezők gyakran támadhatók SQL injectionnel).

Milyen támadásokat lehet véghezvinni SQL injectionnel? Néhány lehetőséget sorolunk csak fel, a lista célja annak érzékeltetése, hogy jóval többről van szó, mint néhány adat kompromittálása.

Login felületek megkerülése. Login felületek naiv implementációjában a felhasználó azonosítása ilyesmi SQL query kiadásával történik:

select * from users where uname="$_POST['user']" and passwd="$_POST['pass']";

A query kiadását követően feltételvizsgálat történik, miszerint ha a visszaadott táblának nullánál több sora van (azaz a felhasználónév és a jelszó is megtalálható), a felhasználói authentikációt érvényesnek fogadjuk el. Hogy lehet ezt megkerülni? Ha például az alábbi paraméterezéssel hívjuk meg:

user=admin";--
pass=pass

Mi jön ebből?
select * from users where uname="admin";-- and passwd="pass";

Azaz a query az admin" után terminálódik, az utána lévő részt egész egyszerűen kommentnek tekinti a queryértelmező: nullától különböző sorral tér vissza a query, a felhasználót beengedjük, bár nem írt be megfelelő jelszót. Természetesen nem ez az egyetlen mód, jó tipp például a klasszikus ' or true or ' string beírása mind a felhasználónév, mind a jelszó helyére (ennek végiggondolását az Olvasóra bízzuk).

Adatbázis-kezelő rendszer azonosítása. Az adatbázis-kezelők nagyjából-egészéből ugyanazt az SQL dialektust beszélik, azonban van néhány eset, amelyben ismert, kihasználható módon különböznek egymástól. Ilyen például az, ahogy a szöveges mezőket kezelik. Például elég jól azonosíthatóak az adatbázis-kezelők a sztringek konkatenációjának kezelésével: bármelyik termék hibát dob, ha a másik kettő dialektusával írt queryt kap.
Oracle: 'al'||'ma'
MSSQL: 'al'+'ma'
MySQL: 'al' 'ma' (szóköz a kettő között)

Tetszőleges adat kinyerése az adatbázisból. Ha már sikerült kitörni a queryből, akkor szabad az út, tetszőleges adat kinyeréséhez. Ha például az id paraméterbe aposztrófot írva adatbázishibát kapunk, jó indikátor a hibára: a kitöréshez elég egyetlen input, ami nem megfelelő ellenőrzés után a queryben köt ki - ha ez megvan, a következő lépésként fel kell derítenünk, hogy mennyi és milyen típusú oszlopot ad vissza. Ezt nagyon egyszerűen megtehetjük például az union operátor használatával: a módszer azon alapszik, hogy két SQL query egyesítése csak akkor lehetséges, ha pontosan ugyanannyi oszlopból állnak, amik egymással kompatibilis típusúak (például int oszlop nem illeszthető varchar típusúval a legtöbb esetben). Ha sikerült megtudni a query által visszaadott oszlopok nevét és típusát, az union újbóli használatával tetszőleges adatot (táblaneveket, az adatbázis-kezelőnek és magának a webalkalmazásnak a felhasználóit is akár) kinyerhetünk. A téma meglehetősen szerteágazó, de ezen a linken kimerítő összefoglalót talál az érdeklődő a null értékek és az union összekapcsolásáról.

A teljes operációs rendszer kompromittálása. Az eddig elmondott lehetőségek szorosan véve az adatbázishoz és a benne tárolt adatokhoz kapcsolódnak. Az MS-SQL-ben azonban lehetőség van arra is, hogy parancsokat adjunk ki az SQL szervert futtató account nevében: az xp_cmdshell() tárolt függvény meghívásával nem megfelelően alacsony jogosultságú service account esetén például felhasználót adhatunk hozzá az adatbázis-kezelő rendszerhez, vagy szélsőséges esetben akár a lokális rendszerhez is, illetve megemelhetjük a jogosultságait.

Az SQL injection támadások sikeres kivitelezéséhez először is találunk kell egy olyan paramétert, amellyel valamilyen módon ki tudunk törni az SQL query keretei közül. Numerikus paraméterek esetében a legegyszerűbb ezt megtenni, hiszen nem kell küzdeni az adatbázis-kezelő dialektusától függő "aposztróf vagy idézőjel?!" - témakörrel. A gyakorlat azt mutatja, hogy szöveges paraméterek esetében körülményesebb érvényes query-t összeállítani, ugyanis megfelelő módon terminálni kell a query "beépített" idézőjeleit: az adatbázis-kezelő hibaüzenetei (ha eljutnak a felhasználó böngészőjéig) alapján némi gyakorlattal egyszerűen összeállítható a megfelelő, értelmes query.

Az SQL injection támadásnak van egy speciális fajtája, amely nem igényli, hogy közvetlenül eljussanak a kinyerni kívánt adatok a felhasználó böngészőjéig: blind injection során azt használjuk ki, hogy ha értelmes a query (azaz eredménnyel tér vissza), akkor más lesz az oldal megjelenése, mint amikor hibára fut, illetve nulla találatot ad vissza. Ennek kihasználásával minden lekérdezésnél egy bitnyi információt nyerhetünk ki - például azt, hogy "igaz-e, hogy az adatbázis nevének első betűje A és M között van?". Lassú, alattomos és piszok zajos támadás, de működik.

Hogyan szoktak védekezni SQL injection ellen?

A leggyakoribb ellenlépés az, hogy egész egyszerűen escape-elik a fejlesztők a "speciális", SQL query-k metakaraktereiként ismert karaktereket (idézőjel, aposztróf, pontosvessző, mínuszjel stb.) Ez meglehetősen jó védelmet biztosít a legegyszerűbb SQL injection támadásokkal szemben, viszont nem véd az "SQL smuggling" néven ismeretes módszer ellen. SQL smuggling során kihasználjuk azt, hogy az adatbázis-kezelő rendszerek sok esetben transzparens karakterkonverziót végeznek az "egzotikus" kódolású karakterek esetében. Ha például UTF-8-as kódokkal adjuk meg a karaktereket a beviteli mezőben, az egyszerű "escape-eljük az aposztrófot"-hozzáállás sajnos nem nyújt védelmet.

A legjobb megoldás SQL injection ellen az, ha paraméterezhető tárolt eljárások segítségével érjük el az adatbázist: sajnos az a nagyon egyszerű mód, amikor szövegekből rakjuk össze az SQL query-ket, a legtöbb esetben támadható.

1 megjegyzés:

  1. Köszi,

    nagyon hasznos cikk, pont most kezdtem el foglalkozni a PHP-MySQL-Javascript-el. Egy többfelhasználós játék írását tervezem, és így most számos buktatót elkerülhetek.

    (eddig nem foglalkoztam biztonsági kérdésekkel + több felhasználóval)

    VálaszTörlés

Kommentek