Adatbázis biztonság

Mostanában, a dinamikus tartalmat szolgáltató web alkalmazások sarokkövének számítanak az adatbázisok. Mivel nagyon kényes, titkos adatok tárolására szolgálhatnak ezek az adatbázisok, erősen megfontolandó, miképp védjük meg ezeket.

Információk tárolásához vagy visszakereséséhez csatlakozni kell az adatbázishoz, egy érvényes lekérdezést kell küldeni, az eredményt ki kell olvasni, és le kell zárni a kapcsolatot. Manapság ebben a párbeszédben a Structured Query Language (SQL) a leggyakrabban használt lekérdezőnyelv. Figyeld meg, miként lehet SQL lekérdezéseket megbabrálni!

Mint látható, a PHP egymagában, magától nem képes megvédeni az adatbázist. A következő bekezdések célja, hogy betekintést adjanak az alapokba, hogyan kell adatbázisokat elérni és módosítani egy PHP programon belül.

Tartsd észben a következő egyszerű szabályt: tagoltan védekezni. Minél több helyen minél többet teszel a biztonság növeléséért, annál kisebb a valószínűsége, hogy a támadók sikerrel járjanak, és kiteregessék titkos adataidat, vagy visszaéljenek velük. A jó adatbázis- és alkalmazástervezés mindig a legnagyobb félelmek figyelembevételéről ismerszik meg.

Adatbázis-tervezés

Az első lépés mindig az adatbázis létrehozása, hacsak nem egy kívülállóét kell használni. Az adatbázis létrehozásakor az a tulajdonosáé lesz, azé, aki lefuttatta az utasításokat. Általában csak a tulajdonos - esetleg az ún. superuser - jogosult bármiféle az adatbázis elemeit érintő műveletre. Annak érdekében, hogy más felhasználók is hozzáférjenek, jogokat kell nekik biztosítani.

Az alkalmazásoknak soha nem szabad a tulajdonosaként vagy superuserként csatlakozni az adatbázishoz, mert ezek bármilyen utasítást és lekérdezést tetszés szerint futtathatnak, pl. a szerkezeti módosítást (táblák megszüntetése) vagy táblák komplett törlése.

Létre lehet hozni különböző, szigorúan korlátozott jogosultásgú adatbázis- felhasználókat, melyek mindegyike az adatbázis manipulációnak egy-egy különböző nézőpontjáért felelősek. Mindig csak a legszükségesebb jogokat szabad engedélyezni, és el kell kerülni, hogy ugyanazt a felhasználót használjuk szerepeiben egymástól különböző esetekben. Ez azt jelenti, hogy ha a behatoló meg is szerzi valamelyik ilyen minősítést (hitelesítési információt = felhasználói név + jelszó), akkor is csak akkora változást tud okozni, mint az alkalmazás maga.

Nem kell minden feladatfüggő szabályozást a webalkalmazásban (PHP szkriptben) kódolni, ehelyett inkább használd az adatbázis lehetőségeit: view-k (nézetek), trigger-ek, rule-ok (szabályok). Ha a rendszer fejlődik, és más alkalmazásokat is csatlakoztatni kell az adatbázishoz, akkor mindegyiknél újra kellene programozni ezeket a szabályokat. Mindezen felül a triggerek arra is jók, hogy átlátszó módon és automatikusan kezeljenek egyes mezőket az adatbázisban, amelyek gyakran bepillantást adnak abba, hogy mi is történik/történt egy tranzakció közben, vagy nagyon hasznosnak bizonyulhatnak hibakeresés során.

Kapcsolódás az adatbázishoz

Elképzelhető, hogy SSL-n keresztül szeretnél kapcsolódni az adatbázishoz, hogy a kiszolgáló és ügyfél közti teljes kommunikáció titkosításával növeld a védelmet. Használhatsz ssh-t is erre a célra. Akármelyik is áll, nagyon nehéz lesz a forgalom lehallgatásából információkat kinyerni ezek után.

Titkosított tárolás

SSL/SSH az ügyfél és kiszolgáló közt mozgó adatokat védi, és nem védi az adatbázisban tárolt megmaradó adatokat. Az SSL - kapcsolati protokoll.

Mihelyst a támadó közvetlen hozzáférést szerzett az adatbázishoz - megkerülve a webszervert -, a tárolt adatok védtelenné váltak, és visszaélhet velük, ha csak maga az adatbázis nem védi valahogy azokat. Az adatok titkosítása kellőképp enyhíti ezt a veszélyt, de jelenleg nagyon kevés adatbázis kezelő támogatja a titkosítást.

Ez a legkönnyebben saját titkosító csomag írásával oldható meg, amelyet utána a PHP szkriptből el lehet érni. Ebben az esetben a PHP segítséget nyújthat néhány kiterjesztésével, mint például az Mcrypt vagy az Mhash, amelyek nagyon sokféle titkosító algoritmust fednek le. A szkript először titkosítja a tárolni kívánt adatot, majd visszakereséskor visszafejti azokat. Nézd meg a hivatkozott fejezeteket további példákért, hogyan kell a titkosítást végrehajtani.

Olyan teljesen rejtett adatok esetén, amelyeknek nyílt ábrázolásukra nincs szükség, mert nem lesznek kiíratva, a hashelés alkalmazása is meggondolandó. A hashelés jól ismert példája az, hogy a jelszavak helyett, azoknak csak MD5 hash értékét tárolják az adatbzisban. Lásd még: crypt() és md5()!

Példa 5-5. Hashelt jelszó mező használata

// jelszó hash értékének tárolása
$query  = sprintf("INSERT INTO users(name,pwd) VALUES('%s','%s');",
            addslashes($username), md5($password));
$result = pg_exec($connection, $query);

// lekérdezés, vajon a felhasználó a helyes jelszót adta-e meg
$query = sprintf("SELECT 1 FROM users WHERE name='%s' AND pwd='%s';",
            addslashes($username), md5($password));
$result = pg_exec($connection, $query);

if (pg_numrows($result) > 0) {
    echo "Üdvözöllek, $username!";
}
else {
    echo "$username hitelesítése nem sikerült.";
}

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 5-6. A keresési eredmények lapokra tördelése ... és superuserek létrehozása (PostgreSQL és MySQL)

$offset = argv[0]; // Vigyázz, nincs beviteli ellenőrzés!
$query  = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
// PostgreSQL
$result = pg_exec($conn, $query);
// MySQL
$result = mysql_query($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:

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

// vagy MySQL esetén
0;
UPDATE user SET Password=PASSWORD('crack') WHERE user='root';
FLUSH PRIVILEGES;

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 5-7. Árucikkek listázása ... és néhány jelszóé (valamilyen adatbázis kezelő)

$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 5-8. Jelszó átírásától ... új jogok megszerzéséig (valamilyen adatbázis kezelő)

$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 5-9. Az adatbázis-gazdagép operációs rendszere elleni támadás (MSSQL Server)

$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ékkek 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 5-10. A lapozáshoz használt lekérdezés összeállításának biztonságosabb módja

    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 addslashes() vagy az addcslashes(). Lásd még az első példát! Ahogy a példák is mutatják, a statikus részbe égetett idézőjelek nem elegendőek, és könnyen kijátszhatók.

  • 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.