Biztonsági kockázatok
Az adatbázis gyakran tartalmaz érzékeny adatokat és lehetővé teszi veszélyes műveletek végrehajtását. A Nette Database biztonságos használatához kulcsfontosságú:
- Megérteni a különbséget a biztonságos és a nem biztonságos API között
- Paraméterezett lekérdezéseket használni
- Helyesen validálni a bemeneti adatokat
Mi az SQL Injection?
Az SQL injection a legkomolyabb biztonsági kockázat az adatbázisokkal való munka során. Akkor keletkezik, ha a felhasználótól származó, nem kezelt bemenet az SQL lekérdezés részévé válik. A támadó saját SQL parancsokat illeszthet be, és ezzel:
- Jogosulatlan hozzáférést szerezhet az adatokhoz
- Módosíthatja vagy törölheti az adatokat az adatbázisban
- Megkerülheti az authentikációt
// ❌ VESZÉLYES KÓD - sebezhető az SQL injection-nel szemben
$database->query("SELECT * FROM users WHERE name = '$_GET[name]'");
// A támadó például megadhatja a következő értéket: ' OR '1'='1
// Az eredményül kapott lekérdezés ez lesz: SELECT * FROM users WHERE name = '' OR '1'='1'
// Ami visszaadja az összes felhasználót
Ugyanez vonatkozik a Database Explorer-re is:
// ❌ VESZÉLYES KÓD - sebezhető az SQL injection-nel szemben
$table->where('name = ' . $_GET['name']);
$table->where("name = '$_GET[name]'");
Paraméterezett lekérdezések
Az SQL injection elleni alapvető védekezés a paraméterezett lekérdezések használata. A Nette Database több módszert is kínál ezek használatára.
A legegyszerűbb módszer a kérdőjeles helyettesítők használata:
// ✅ Biztonságos paraméterezett lekérdezés
$database->query('SELECT * FROM users WHERE name = ?', $name);
// ✅ Biztonságos feltétel az Explorerben
$table->where('name = ?', $name);
Ez érvényes minden további metódusra a Database Explorerben, amelyek lehetővé teszik kifejezések beillesztését kérdőjeles helyettesítőkkel és paraméterekkel.
Az INSERT, UPDATE parancsokhoz vagy a WHERE záradékhoz az értékeket tömbben adhatjuk át:
// ✅ Biztonságos INSERT
$database->query('INSERT INTO users', [
'name' => $name,
'email' => $email,
]);
// ✅ Biztonságos INSERT az Explorerben
$table->insert([
'name' => $name,
'email' => $email,
]);
Paraméterértékek validálása
A paraméterezett lekérdezések a biztonságos adatbázis-kezelés alapkövei. Azonban az értékeknek, amelyeket beléjük illesztünk, több ellenőrzési szinten kell átesniük:
Típusellenőrzés
A legfontosabb a paraméterek helyes adattípusának biztosítása – ez szükséges feltétele a Nette Database biztonságos használatának. Az adatbázis feltételezi, hogy minden bemeneti adat helyes adattípussal rendelkezik, amely megfelel az adott oszlopnak.
Például, ha az előző példákban a $name
váratlanul egy tömb lenne egy string helyett, a Nette Database
megpróbálná az összes elemét beilleszteni az SQL lekérdezésbe, ami hibához vezetne. Ezért soha ne használjon
validálatlan adatokat a $_GET
, $_POST
vagy $_COOKIE
tömbökből közvetlenül az
adatbázis lekérdezésekben.
Formátumellenőrzés
A második ellenőrzési szinten az adatok formátumát ellenőrizzük – például, hogy a stringek UTF-8 kódolásúak-e, és hosszuk megfelel-e az oszlop definíciójának, vagy hogy a numerikus értékek az adott oszlop adattípusához megengedett tartományban vannak-e.
Ezen a validálási szinten részben magára az adatbázisra is támaszkodhatunk – sok adatbázis elutasítja az érvénytelen adatokat. Azonban a viselkedés eltérő lehet, némelyik csendben levághatja a hosszú stringeket, vagy a tartományon kívüli számokat.
Domain ellenőrzés
A harmadik szint az alkalmazásspecifikus logikai ellenőrzéseket jelenti. Például annak ellenőrzése, hogy a select boxokból származó értékek megfelelnek-e a kínált lehetőségeknek, hogy a számok a várt tartományban vannak-e (pl. életkor 0–150 év), vagy hogy az értékek közötti kölcsönös függőségek értelmesek-e.
Ajánlott validálási módszerek
- Használjon Nette Űrlapokat, amelyek automatikusan biztosítják az összes bemenet helyes validálását
- Használjon Presentereket és adja meg az adattípusokat a paramétereknél
az
action*()
ésrender*()
metódusokban - Vagy implementáljon saját validálási réteget standard PHP eszközökkel, mint például a
filter_var()
Biztonságos munka az oszlopokkal
Az előző szakaszban megmutattuk, hogyan kell helyesen validálni a paraméterértékeket. Azonban az SQL lekérdezésekben tömbök használatakor ugyanolyan figyelmet kell fordítanunk a kulcsaikra is.
// ❌ VESZÉLYES KÓD - a tömb kulcsai nincsenek kezelve
$database->query('INSERT INTO users', $_POST);
Az INSERT és UPDATE parancsoknál ez alapvető biztonsági hiba – a támadó bármilyen oszlopot beilleszthet vagy
módosíthat az adatbázisban. Például beállíthatná az is_admin = 1
-et, vagy tetszőleges adatokat illeszthetne
be érzékeny oszlopokba (ún. Mass Assignment Vulnerability).
A WHERE feltételekben ez még veszélyesebb, mivel operátorokat tartalmazhatnak:
// ❌ VESZÉLYES KÓD - a tömb kulcsai nincsenek kezelve
$_POST['salary >'] = 100000;
$database->query('SELECT * FROM users WHERE', $_POST);
// végrehajtja a WHERE (`salary` > 100000) lekérdezést
A támadó ezt a megközelítést használhatja a munkavállalók fizetésének szisztematikus kiderítésére. Például elkezdheti a 100 000 feletti fizetések lekérdezésével, majd az 50 000 alattiakkal, és a tartomány fokozatos szűkítésével felfedheti az összes munkavállaló hozzávetőleges fizetését. Ezt a támadástípust SQL enumeration-nek nevezik.
A where()
és whereOr()
metódusok még sokkal rugalmasabbak, és támogatják az SQL kifejezéseket,
beleértve az operátorokat és függvényeket a kulcsokban és értékekben. Ez lehetőséget ad a támadónak SQL injection
végrehajtására:
// ❌ VESZÉLYES KÓD - a támadó saját SQL-t illeszthet be
$_POST = ['0) UNION SELECT name, salary FROM users WHERE (1'];
$table->where($_POST);
// végrehajtja a WHERE (0) UNION SELECT name, salary FROM users WHERE (1) lekérdezést
Ez a támadás lezárja az eredeti feltételt a 0)
segítségével, saját SELECT
-et csatol a
UNION
segítségével, hogy érzékeny adatokat szerezzen a users
táblából, és szintaktikailag
helyes lekérdezést zár le a WHERE (1)
segítségével.
Oszlopok Whitelistje
Az oszlopnevekkel való biztonságos munkához szükségünk van egy mechanizmusra, amely biztosítja, hogy a felhasználó csak az engedélyezett oszlopokkal dolgozhasson, és ne tudjon sajátokat hozzáadni. Megpróbálhatnánk észlelni és blokkolni a veszélyes oszlopneveket (blacklist), de ez a megközelítés megbízhatatlan – a támadó mindig kitalálhat egy új módszert a veszélyes oszlopnév beírására, amit nem láttunk előre.
Ezért sokkal biztonságosabb megfordítani a logikát, és explicit módon definiálni az engedélyezett oszlopok listáját (whitelist):
// Oszlopok, amelyeket a felhasználó módosíthat
$allowedColumns = ['name', 'email', 'active'];
// Eltávolítjuk az összes nem engedélyezett oszlopot a bemenetből
$filteredData = array_intersect_key($userData, array_flip($allowedColumns));
// ✅ Most már biztonságosan használhatjuk a lekérdezésekben, például:
$database->query('INSERT INTO users', $filteredData);
$table->update($filteredData);
$table->where($filteredData);
Dinamikus azonosítók
Dinamikus tábla- és oszlopnevekhez használja a ?name
helyettesítő szimbólumot. Ez biztosítja az
azonosítók helyes escapelését az adott adatbázis szintaxisa szerint (pl. backtickek használatával MySQL-ben):
// ✅ Megbízható azonosítók biztonságos használata
$table = 'users';
$column = 'name';
$database->query('SELECT ?name FROM ?name', $column, $table);
// Eredmény MySQL-ben: SELECT `name` FROM `users`
Fontos: a ?name
szimbólumot csak az alkalmazás kódjában definiált, megbízható értékekhez használja.
Felhasználótól származó értékekhez használja újra a whitelistet. Ellenkező esetben
biztonsági kockázatoknak teszi ki magát:
// ❌ VESZÉLYES - soha ne használjon felhasználói bemenetet
$database->query('SELECT ?name FROM users', $_GET['column']);