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.
Ajánlott validálási módszerek
- 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 arender*()
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']);