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*() és render*() 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']);
verzió: 4.0