Bezpečnostní rizika
Databáze často obsahuje citlivá data a umožňuje provádět nebezpečné operace. Nette Database nabízí řadu bezpečnostních prvků. Klíčové je ale pochopení rozdílu mezi bezpečným a nebezpečným API.
SQL Injection
SQL injection je nejzávažnější bezpečnostní riziko při práci s databází. Vzniká, když se neošetřený vstup od uživatele stane součástí SQL dotazu. Útočník může vložit vlastní SQL příkazy a tím získat nebo modifikovat data v databázi.
// ❌ NEBEZPEČNÝ KÓD - zranitelný vůči SQL injection
$database->query("SELECT * FROM users WHERE name = '$_GET[name]'");
// Útočník může zadat například hodnotu: ' OR '1'='1
// Výsledný dotaz pak bude:
// SELECT * FROM users WHERE name = '' OR '1'='1'
// Což vrátí všechny uživatele!
Totéž se týká i Database Explorer:
// ❌ NEBEZPEČNÝ KÓD
$table->where('name = ' . $_GET['name']);
$table->where("name = '$_GET[name]'");
Bezpečné parametrizované dotazy
Bezpečným způsobem vkládání hodnot do SQL dotazů jsou parametrizované dotazy. Nette Database nabízí několik způsobů jejich použití.
Zástupné otazníky
Nejjednodušší způsob je použití zástupných otazníků:
// ✅ Bezpečné parametrizované dotazy
$database->query('SELECT * FROM users WHERE name = ?', $_GET['name']);
// ✅ Bezpečná podmínka v Exploreru
$table->where('name = ?', $_GET['name']);
Totéž platí pro všechny další metody v Database Explorer, které umožňují vkládat výrazy se zástupnými otazníky a parametry.
Hodnoty musí být skalárního typu (string, int, float, bool) nebo null. Pokud by například
$_GET['name']
bylo pole, Nette Database by vložil do SQL všechny jeho prvky, což může být nežádoucí.
Pole hodnot
Pro příkazy INSERT, UPDATE nebo klauzule WHERE můžeme použít pole hodnot:
// ✅ Bezpečný INSERT
$database->query('INSERT INTO users', [
'name' => $_GET['name'],
'email' => $_GET['email'],
]);
// ✅ Bezpečný UPDATE
$database->query('UPDATE users SET', [
'name' => $_GET['name'],
'email' => $_GET['email'],
], 'WHERE id = ?', $_GET['id']);
Nette Database automaticky escapuje všechny hodnoty předané přes parametrizované dotazy. Musíme však zajistit správný datový typ parametrů.
Klíče polí nejsou bezpečné API
Zatímco hodnoty v polích jsou bezpečné, o klíčích to neplatí:
// ❌ NEBEZPEČNÝ KÓD - klíče mohou obsahovat SQL injection
$database->query('INSERT INTO users', $_GET);
$database->query('SELECT * FROM users WHERE', $_GET);
$table->where($_GET);
U příkazů INSERT a UPDATE je to zásadní bezpečnostní chyba – útočník může do databáze vložit nebo změnit
jakýkoliv sloupec. Mohl by si například nastavit is_admin = 1
nebo vložit libovolná data do citlivých
sloupců.
Ve WHERE podmínkách je to ještě nebezpečnější, protože umožňuje SQL enumeration – techniku postupného
zjišťování informací o databázi. Útočník může třeba zkoumat plat zaměstnanců tím, že do $_GET
podstrčí:
$_GET = ['salary >', 100000]; // začne zjišťovat platové rozsahy
Ale hlavní problém je, že WHERE podmínky podporují v klíčích SQL výrazy:
// Legitimní použití operátorů v klíčích
$table->where([
'age > ?' => 18,
'ROUND(score, ?) > ?' => [2, 75.5],
]);
// ❌ NEBEZPEČNÉ: útočník může vložit vlastní SQL
$_GET = ['1) UNION SELECT name, salary FROM users WHERE (is_admin = ?' => 1];
$table->where($_GET); // umožní útočníkovi získat platy adminů
Toto je opět SQL injection.
Whitelist sloupců
Pokud chcete uživateli umožnit volbu sloupců, vždy použijte whitelist:
// ✅ Bezpečné zpracování - pouze povolené sloupce
$allowedColumns = ['name', 'email', 'active'];
$values = array_intersect_key($_GET, array_flip($allowedColumns));
$database->query('INSERT INTO users', $values);
Dynamické identifikátory
Pro dynamické názvy tabulek a sloupců použijte zástupný symbol ?name
:
// ✅ Bezpečné použití důvěryhodných identifikátorů
$table = 'users';
$column = 'name';
$database->query('SELECT ?name FROM ?name', $column, $table);
// ❌ NEBEZPEČNÉ - nikdy nepoužívejte vstup od uživatele
$database->query('SELECT ?name FROM users', $_GET['column']);
Symbol ?name
používejte pouze pro důvěryhodné hodnoty definované v kódu aplikace. Pro hodnoty od
uživatele použijte opět whitelist.