Biztonsági kockázatok

Az adatbázisok gyakran tartalmaznak érzékeny adatokat, és lehetővé teszik veszélyes műveletek végrehajtását. A Nette adatbázissal való biztonságos munkavégzéshez a legfontosabb szempontok a következők:

  • A biztonságos és a nem biztonságos API közötti különbség megértése.
  • Paraméteres lekérdezések használata
  • A bemeneti adatok megfelelő validálása

Mi az SQL Injection?

Az SQL injektálás a legsúlyosabb biztonsági kockázat az adatbázisokkal való munka során. Akkor fordul elő, amikor a szűretlen felhasználói bemenet egy SQL-lekérdezés részévé válik. A támadó beillesztheti saját SQL-parancsait, és ezáltal:

  • illetéktelen adatok kinyerése
  • módosíthatja vagy törölheti az adatbázisban lévő adatokat
  • a hitelesítés megkerülése
// ❌ VESZÉLYES KÓD - SQL injekcióval sebezhető
$database->query("SELECT * FROM users WHERE name = '$_GET[name]'");

// Egy támadó beírhat egy olyan értéket, mint pl: ' OR '1'='1
// Az eredményül kapott lekérdezés a következő lenne: Vagy '1'='1'.
// Ami az összes felhasználót visszaadja

Ugyanez vonatkozik az Adatbázis-kutatóra is:

// ❌ VESZÉLYES KÓD - SQL injekcióval sebezhető
$table->where('name = ' . $_GET['name']);
$table->where("name = '$_GET[name]'");

Biztonságos paraméterezett lekérdezések

Az SQL-injekció elleni alapvető védelem a paraméterezett lekérdezések. A Nette Database többféle lehetőséget biztosít ezek használatára.

A legegyszerűbb mód a kérdőjeles helyőrzőket használni:

// ✅ Biztonságos paraméteres lekérdezés
$database->query('SELECT * FROM users WHERE name = ?', $name);

// ✅ Biztonságos feltétel az Explorerben
$table->where('name = ?', $name);

Ez vonatkozik az Adatbázis-kutató minden más módszerére, amely lehetővé teszi kérdőjeles helyőrzővel és paraméterekkel ellátott kifejezések beillesztését.

A INSERT, UPDATE vagy WHERE záradékok esetében az értékeket tömbben is átadhatja:

// ✅ Biztonságos BESZERZÉS
$database->query('INSERT INTO users', [
	'name' => $name,
	'email' => $email,
]);

// ✅ Biztonságos INSERT az Explorerben
$table->insert([
	'name' => $name,
	'email' => $email,
]);

Paraméterérték érvényesítés

A paraméterezett lekérdezések a biztonságos adatbázis-munka sarokkövei. A beléjük átadott értékeknek azonban több szintű érvényesítésen kell átesniük:

Típusellenőrzés

A paraméterek helyes adattípusának biztosítása kritikus fontosságú – ez a Nette adatbázis biztonságos használatának szükséges feltétele. Az adatbázis feltételezi, hogy minden bemeneti adat az oszlopnak megfelelő helyes adattípussal rendelkezik.

Ha például az előző példákban a $name váratlanul tömb lett volna a string helyett, a Nette Database megpróbálná az összes elemét beilleszteni az SQL-lekérdezésbe, ami hibát eredményezne. Ezért soha ne használjon érvénytelen adatokat a $_GET, $_POST vagy $_COOKIE oldalról közvetlenül az adatbázis-lekérdezésekben.

Formátum érvényesítés

A második szint az adatok formátumát ellenőrzi – például biztosítja, hogy a karakterláncok UTF-8 kódolásúak és hosszuk megfelel az oszlopdefiníciónak, vagy ellenőrzi, hogy a numerikus értékek az oszlop adattípusának megengedett tartományába esnek.

Ezen a szinten részben magára az adatbázisra támaszkodhat – sok adatbázis elutasítja az érvénytelen adatokat. A viselkedés azonban eltérő lehet: egyesek a hosszú karakterláncokat csonkítják, vagy a tartományon kívül eső számokat vágják le.

Tartomány-specifikus érvényesítés

A harmadik szint az alkalmazásodra jellemző logikai ellenőrzéseket foglalja magában. Például annak ellenőrzése, hogy a kiválasztó dobozok értékei megfelelnek-e a rendelkezésre álló lehetőségeknek, hogy a számok egy elvárt tartományba esnek-e (pl. életkor 0–150 év), vagy hogy az értékek közötti kapcsolatoknak van-e értelme.

  • Használja a Nette Forms-t, amely automatikusan kezeli az összes bemenet megfelelő érvényesítését.
  • Használjon bemutatókat, és deklarálja a paraméterek adattípusait a action*() és a render*() metódusokban.
  • Vagy valósítson meg egy egyéni érvényesítési réteget szabványos PHP eszközökkel, mint például a filter_var().

Biztonságos munka oszlopokkal

Az előző részben azt tárgyaltuk, hogyan kell megfelelően érvényesíteni a paraméterértékeket. Amikor azonban tömböket használunk SQL-lekérdezésekben, ugyanilyen figyelmet kell fordítanunk a kulcsokra is.

// ❌ VESZÉLYES KÓD - a tömbkulcsok nincsenek szanitizálva
$database->query('INSERT INTO users', $_POST);

Az INSERT és UPDATE parancsok esetében ez komoly biztonsági hiba – egy támadó az adatbázis bármelyik oszlopát beszúrhatja vagy módosíthatja. Például beállíthatja a is_admin = 1 címet, vagy tetszőleges adatokat illeszthet be érzékeny oszlopokba (ez az úgynevezett tömeges hozzárendelési sebezhetőség).

A WHERE feltételeknél ez még veszélyesebb, mert ezek tartalmazhatnak operátorokat:

// ❌ VESZÉLYES KÓD - a tömbkulcsok nincsenek szanitizálva
$_POST['salary >'] = 100000;
$database->query('SELECT * FROM users WHERE', $_POST);
// a lekérdezés végrehajtása WHERE (`bér` > 100000)

Egy támadó ezt a megközelítést arra használhatja, hogy módszeresen feltárja az alkalmazottak fizetését. Kezdhetik a 100 000 feletti, majd az 50 000 alatti fizetésekre vonatkozó lekérdezéssel, és a tartomány fokozatos szűkítésével feltárhatják az összes alkalmazott hozzávetőleges fizetését. Az ilyen típusú támadást SQL enumerációnak nevezik.

A where() és a whereOr() módszerek még rugalmasabbak, és támogatják az SQL-kifejezéseket, beleértve az operátorokat és függvényeket is, mind a kulcsokban, mind az értékekben. Ez lehetővé teszi a támadó számára, hogy összetett SQL injekciót hajtson végre:

// ❌ VESZÉLYES KÓD - a támadó beillesztheti a saját SQL kódját
$_POST = ['0) UNION SELECT name, salary FROM users WHERE (1'];
$table->where($_POST);
// végrehajtja a lekérdezést WHERE (0) UNION SELECT name, salary FROM users WHERE (1)

Ez a támadás az eredeti feltételt a 0) segítségével fejezi be, a UNION segítségével saját SELECT -t csatol a users táblából származó érzékeny adatok megszerzéséhez, és a WHERE (1) segítségével egy szintaktikailag helyes lekérdezéssel zárja le.

Oszlop fehér lista

Az oszlopnevekkel való biztonságos munkavégzéshez olyan mechanizmusra van szükség, amely biztosítja, hogy a felhasználók csak az engedélyezett oszlopokkal léphetnek kapcsolatba, és nem adhatnak hozzá sajátokat. A veszélyes oszlopnevek felismerésére és blokkolására tett kísérlet (feketelista) nem megbízható – egy támadó mindig kitalálhat egy új módszert egy veszélyes oszlopnév megírására, amire nem számított.

Ezért sokkal biztonságosabb megfordítani a logikát, és meghatározni a megengedett oszlopok explicit listáját (fehérlista):

// A felhasználó által módosítható oszlopok
$allowedColumns = ['name', 'email', 'active'];

// Az összes nem engedélyezett oszlop eltávolítása a bemenetből
$filteredData = array_intersect_key($userData, array_flip($allowedColumns));

// ✅ Most már biztonságosan használható lekérdezésekben, például:
$database->query('INSERT INTO users', $filteredData);
$table->update($filteredData);
$table->where($filteredData);

Dinamikus azonosítók

A dinamikus tábla- és oszlopnevekhez használja a ?name helyőrzőt. Ez biztosítja, hogy az azonosítók az adott adatbázis szintaxisának megfelelően (pl. a MySQL-ben backtickek használata) megfelelően legyenek szkriptelve:

// ✅ Megbízható azonosítók biztonságos használata
$table = 'users';
$column = 'name';
$database->query('SELECT ?name FROM ?name', $column, $table);
// Eredmény a MySQL-ben: SELECT `név` FROM `felhasználók`

Fontos: A ?name szimbólumot csak az alkalmazáskódban meghatározott megbízható értékekhez használja. A felhasználó által megadott értékek esetében ismét használjon fehérlistát. Ellenkező esetben biztonsági réseket kockáztat:

// ❌ VESZÉLYES - soha ne használjon felhasználói bevitelt
$database->query('SELECT ?name FROM users', $_GET['column']);
verzió: 4.0