PHP kézikönyv
Előző | 27. Fejezet. Adatbázis biztonság | Következő

# SQL "beoltás"

Sok web fejlesztő nincs tudatában annak, hogy hogyan lehet megbabrálni az SQL utasításokat, ezért az SQL utasításokat megbízható parancsoknak feltételezik. Ez azt jelenti, hogy az SQL lekérdezésekkel ki lehet játszani a hozzáférés szabályozásokat, meg lehet kerülni a szabályos engedélyezési folyamatokat , és néha az SQL lekérdezésekkel a gazdagépen operációs rendszer szintű hozzáférést is lehet létrehozni.

A "közvetlen SQL utasítás befecskendezés" olyan módszer, amellyel a támadó a régi SQL utasításokat módosítja vagy újakat ad hozzájuk annak érdekében, hogy titkos információkhoz jusson hozzá, vagy felülírja azokat, vagy veszélyes rendszer szintű parancsokat futtasson az adatbázis gazdagépén. Ez olyan alkalmazások esetén tehető meg, amelyek a felhasználótól származó adatokból és statikus paraméterekből állítanak össze SQL lekérdezéseket. Sajnos, a következő példák mind megtörtént eseteken alapulnak.

Az, hogy az adatbázishoz superuserként (olyan személyként, aki superusert képes létrehozni) csatlakozott az alkalmazás, és a bevitt adatok ellenőrzésének hiánya odavezethet, hogy a támadó superuser hozzáférést hozhat létre az adatbázishoz.

Példa 27-2. A keresési eredmények lapokra tördelése ... és superuserek létrehozása (PostgreSQL)
`<?php $offset = $argv[0]; // Vigyázz, nincs beviteli ellenőrzés! $query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;"; $result = pg_query($conn, $query); ?>`

A szokványos felhasználó az 'előző', 'következő' linkekre kattint, ahol az $offset az URL-be van kódolva. A szkript azt várja, hogy $offset decimális szám. Mégis, valaki megpróbálhatja a következő utasítás urlencode() alakját hozzáfűzni az URL-hez:

    0;
    insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
        select 'crack', usesysid, 't','t','crack'
        from pg_shadow where usename='postgres';
    --

Ha ez megtörténne, akkor a szkript megajándékozná a támadót egy superuser hozzáféréssel. A 0; arra való, hogy érvényes offset-et biztosítson az eredeti lekérdezésnek.

> Megjegyzés: Általános módszer, hogy a -- jellel kényszerítik ki, hogy az SQL elemző figyelmen kívül hagyja a lekérdezésként átadott string fennmaradó részét, mivel ez a megjegyzés szabványos jelölése SQL-ben.

Egy lehetséges módja a jelszavak megszerzésének, hogy kijátszák a kereső oldalak találati listájának lekérdezéseit. A támadónak mindössze annyit kell tennie, hogy végig próbálja melyik elküldött SQL lekérdezésben használt változó nincs megfelelően lekezelve. Ezeket általában egy megelőző űrlapon lehet beállítani, hogy testre szabjuk a SELECT utasítás WHERE, ORDER BY, LIMIT és OFFSET klauzuláit. Ha a használt adatbáziskezelő támogatja a UNION szerkezetet, akkor a támadó esetleg hozzáfűzhet egy teljesen új lekérdezést a már meglevőhöz, hogy kilistázza valamelyik táblában tárolt jelszavakat. Titkosított tárolás erősen ajánlott!

Példa 27-3. Árucikkek listázása ... és néhány jelszóé (valamilyen adatbázis kezelő)
`<?php $query = "SELECT id, name, inserted, size FROM products WHERE size = '$size' AND inserted BETWEEN '$min_date' AND '$max_date' ORDER BY $order LIMIT $limit, $offset;"; $result = odbc_exec($conn, $query); ?>`

A lekérdezés statikus része egy másik SELECT utasítással kombinálható, ami az összes jelszót kilistázza:

    '
    union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable;
    --

Ha ezt a lekérdezést (a ' és -- megfelelő használatával) valamelyik $query-ben használt változóhoz sikerülne hozzárendelni, akkor a szörny felébredne.

SQL UPDATE parancsok ugyancsak ki vannak téve az adatbázisok elleni támadásoknak. Ezeket az utasításokat is fenyegetik az előzőekben megismert megrövidítő és hozzáfűző technikák. Ám emellett a támadó meghamisíthatja a SET klauzulát is. Ebben az esetben némi séma információval rendelkeznie kell a támadónak, hogy sikerrel járjon. Ezeket az információkat az űrlapváltozók neveiből szerezhetik meg, vagy egyszerűen próbálgatással. Az általánosan használt elnevezések a felhasználói névre és jelszóra nem nagyon különböznek egymástól.

Példa 27-4. Jelszó átírásától ... új jogok megszerzéséig (valamilyen adatbázis kezelő)
`<?php $query = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';"; ?>`

A rosszindulatú felhasználó a ' or uid like'%admin%'; -- értéket adja át a $uid változónak, és ezzel megváltoztatja az adminisztrátor jelszavát, vagy egyszerűen a $pwd-nek a "hehehe', admin='yes', trusted=100 " (lezáró szóközzel) értéket adva még több jogot szerez magának. Ezt az SQL parancsot ezek így ferdítik el:

`// $uid == "' or uid like'%admin%'; --" $query = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%'; --';"; // $pwd == "hehehe', admin='yes', trusted=100 " $query = "UPDATE usertable SET pwd='hehehe', admin='yes', trusted=100 WHERE ...";`

Egy ijesztő példa, hogyan lehet az adatbázis gazdagépén operációs rendszerszintű parancsokat futtatni.

Példa 27-5. Az adatbázis-gazdagép operációs rendszere elleni támadás (MSSQL Server)
`<?php $query = "SELECT * FROM products WHERE id LIKE '%$prod%'"; $result = mssql_query($query); ?>`

Ha a támadó az a%' exec master..xp_cmdshell 'net user test testpass /ADD' -- értéket küldi el a $prod változónak, akkor a $query a következőképp alakul:

`$query = "SELECT * FROM products WHERE id LIKE '%a%' exec master..xp_cmdshell 'net user test testpass /ADD'--%'"; $result = mssql_query($query);`

MSSQL Server futtatja a kötegbe fogott SQL utasításokat, köztük azt is, amelyik új felhasználót vesz fel az adatbázis kiszolgálógépen. Ha az alkalmazás sa jogosultsággal fut és az MSSQLSERVER service megfelelő jogokkal fut, akkor a támadónak most már hozzáférése van ehhez a géphez.

> Megjegyzés: A példák némelyike bizonyos adatbáziskezelőhöz kötődik. Ez nem azt jelenti, hogy hasonló támadás elképzelhetetlen más termékek ellen. Az általad használt adatbázis-kezelő ugyanilyen sérülékeny lehet, akár más módon.

## Elhárítási módszerek

Ellenevetésként felmerülhet, hogy a példák többségében a támadónak rendelkeznie kell valamennyi előzetes információval az adatbázis felépítéséről. Ez igaz, de soha nem lehet tudni, hogy mikor, hol, hogyan szerezhetik meg ezeket, és ha ez megtörtént, az adatbázisod védtelenné válik. A behatolók könnyen hozzájuthatnank a program egy darabjához nyílt forráskódú, vagy olyan nyilvánosan elérhető adatbázis-kezelő programcsomag használatakor, amelyik egy fórum vagy tartalomszolgáltató rendszer része. Ez különösen veszélyes lehet, ha ezek kevéssé átgondoltak és gyengén megtervezettek.

Ezek a támadások alapvetően olyan programoknak a kijátszásán alapulnak, amelyek a védelmet/biztonságot figyelmen kívül hagyva születtek. Soha nem lehet megbízni semmilyen bejövő adatban, főleg ha az a kliens oldalról érkezik, még akkor sem, ha az egy általunk megadott süti (cookie), vagy rejtett mező (hidden input) értéke esetleg egy legördülő lista eleme. Még egy olyan ártatlan lekérdezés, mint ami az első példában látható, katasztrófát okozhat.

- Soha ne csatlakozz az adatbázishoz tulajdonosaként vagy superuser-ként. Mindig kevés jogosultsággal rendelkező, testreszabott felhasználókat használj!

- Ellenőrizd a bejövő adat típusát, hogy az a vártnak megfelelő-e! A PHP a bevitelt ellenőrző függvények széles körével rendelkezik kezdve a legegyszerűbbektől - pl.: Változókkal kapcsolatos függvények közül is_numeric() vagy a Character Type Functions közül a ctype_digit() - a Perl kompatibilis reguláris kifejezések támogatásáig.

- Ha az alkalmazás számot vár, akkor megfontolandó az is_numeric() függvénnyel ellenőrizni a típusát, vagy csendben megváltoztatni a típusát a settype() függvénnyel, vagy szám szerinti ábrázolását használni az sprintf() függvénnyel.

  Példa 27-6. A lapozáshoz használt lekérdezés összeállításának biztonságosabb módja
  `<?php settype($offset, 'integer'); $query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;"; // Figyelj a %d -re a formázó sztringben, a %s használat értelmetlen lenne $query = sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %d;", $offset); ?>`

- Idézőjelek közé kell tenni minden nem szám jellegű, felhasználótól származó adatot, erre használható az adatbázis-specifikus escape függvény (pl. mysql_escape_string(), sql_escape_string(), stb.). Ha nincs ilyen adatbázis-specifikus escape mechanizmus, akkor hasznosnak bizonyulhatnak az addslashes() és a str_replace() függvények (az adatbázistól függően). Lásd még az első példát! Ahogy a példa is mutatja, a statikus részbe beírt idézőjelek nem elegendőek, mivel ez a lekérdezést könnyen feltörhetővé teszi.

- Semmilyen adatbázisra jellemző információt - különösen szerkezetit - nem szabad kiírni, ha törik, ha szakad. Lásd még: Hibajelzés és Hibakezelő és naplózó függvények!

- Tárolt eljársokat és előre definiált kurzorokat is használhatsz, hogy az adatbázis elérést absztraháld annak érdekében, hogy a felhasználók ne közvetlenül a táblákhoz vagy nézetekhez férjenek hozzá. Ennek a megoldás azonban egyéb hatásai vannak.

Ezeken kívül, hasznot hajthat a lekérdezések naplózása akár a szkripteken belül, akár ha az adatbázis kezelő maga teszi ezt. Nyilvánvalóan ez nem tud megakadályozni egyetlen ártalmas próbálkozást sem, de segítséget nyújthat annak felderítésében, hogy melyik alkalmazás lett kijátszva. A naplózás önmagában nem, csak a benne megjelenő információkon keresztül válik hasznossá: általában a több részlet, hasznosabb.

Előző | Tartalom | Következő
Titkosított tárolás | Fel | Hibakezelés