Bezpečnostní rizika
Databáze často obsahuje citlivá data a umožňuje provádět nebezpečné operace. Pro bezpečnou práci s Nette Database je klíčové:
- Porozumět rozdílu mezi bezpečným a nebezpečným API
- Používat parametrizované dotazy
- Správně validovat vstupní data
Co je 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 neoprávněný přístup k datům
- Modifikovat nebo smazat data v databázi
- Obejít autentizaci
// ❌ 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 - zranitelný vůči SQL injection
$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í.
Nejjednodušší způsob je použití zástupných otazníků:
// ✅ Bezpečný parametrizovaný dotaz
$database->query('SELECT * FROM users WHERE name = ?', $name);
// ✅ Bezpečná podmínka v Exploreru
$table->where('name = ?', $name);
Tohle platí pro všechny další metody v Database Explorer, které umožňují vkládat výrazy se zástupnými otazníky a parametry.
Pro příkazy INSERT, UPDATE nebo klauzuli WHERE můžeme bezpečně předat hodnoty v poli:
// ✅ Bezpečný INSERT
$database->query('INSERT INTO users', [
'name' => $name,
'email' => $email,
]);
// ✅ Bezpečný INSERT v Exploreru
$table->insert([
'name' => $name,
'email' => $email,
]);
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 - nejsou ošetřené klíče v poli
$database->query('INSERT INTO users', $_POST);
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ů
(tzv Mass Assignment Vulnerability).
Ve WHERE podmínkách je to ještě nebezpečnější, protože mohou obsahovat oprátory:
// ❌ NEBEZPEČNÝ KÓD - nejsou ošetřené klíče v poli
$_POST['salary >'] = 100000;
$database->query('SELECT * FROM users WHERE', $_POST);
// vykoná dotaz WHERE (`salary` > 100000)
Útočník může tento přístup využít k systematickému zjišťování platů zaměstnanců. Začne například dotazem na platy nad 100.000, pak pod 50.000 a postupným zužováním rozsahu může odhalit přibližné platy všech zaměstnanců. Tento typ útoku se nazývá SQL enumeration.
Metoda where()
podporuje v klíčích SQL výrazy včetně operátorů a funkcí. To dává útočníkovi
možnost provést komplexní SQL injection:
// ❌ NEBEZPEČNÝ KÓD - útočník může vložit vlastní SQL
$_POST['0) UNION SELECT name, salary FROM users WHERE (?'] = 1;
$table->where($_POST);
// vykoná dotaz WHERE (0) UNION SELECT name, salary FROM users WHERE (1)
Tento útok ukončí původní podmínku pomocí 0)
, připojí vlastní SELECT
pomocí
UNION
aby získal citlivá data z tabulky users
a uzavře syntakticky správný dotaz pomocí
WHERE (1)
.
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($_POST, array_flip($allowedColumns));
$database->query('INSERT INTO users', $values);
Validace vstupních dat
Nejdůležitější je zajistit správný datový typ parametrů – to je nutná podmínka pro bezpečné použití Nette Database. Databáze předpokládá, že všechna vstupní data mají správný datový typ odpovídající danému sloupci.
Například pokud by $name
v předchozích příkladech bylo neočekávaně pole místo řetězce, Nette Database
by se pokusilo vložit všechny jeho prvky do SQL dotazu, což by vedlo k chybě. Proto nikdy nepoužívejte nevalidovaná
data z $_GET
, $_POST
nebo $_COOKIE
přímo v databázových dotazech.
Na druhé úrovni kontrolujeme technickou validitu dat – například zda jsou řetězce v UTF-8 kódování a jejich délka odpovídá definici sloupce, nebo zda jsou číselné hodnoty v povoleném rozsahu pro daný datový typ sloupce. U této úrovně validace se můžeme částečně spolehnout i na databázi samotnou – mnoho databází odmítne nevalidní data. Nicméně chování se může lišit, některé mohou dlouhé řetězce tiše zkrátit nebo čísla mimo rozsah oříznout.
Třetí úroveň představují logické kontroly specifické pro vaši aplikaci. Například ověření, že hodnoty ze select boxů odpovídají nabízeným možnostem, že čísla jsou v očekávaném rozsahu (např. věk 0–150 let) nebo že vzájemné závislosti mezi hodnotami dávají smysl.
Doporučené způsoby implementace validace:
- Používejte Nette Formuláře, které automaticky zajistí správnou validaci všech vstupů
- Používejte Presentery a uvádějte u parametrů v
action*()
arender*()
metodách datové typy - Nebo implementujte vlastní validační vrstvu pomocí standardních PHP nástrojů jako
filter_var()
Dynamické identifikátory
Pro dynamické názvy tabulek a sloupců použijte zástupný symbol ?name
. Ten zajistí správné escapování
identifikátorů podle syntaxe dané databáze (např. pomocí zpětných uvozovek v MySQL):
// ✅ Bezpečné použití důvěryhodných identifikátorů
$table = 'users';
$column = 'name';
$database->query('SELECT ?name FROM ?name', $column, $table);
// Výsledek v MySQL: SELECT `name` FROM `users`
// ❌ NEBEZPEČNÉ - nikdy nepoužívejte vstup od uživatele
$database->query('SELECT ?name FROM users', $_GET['column']);
Důležité: 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. Jinak se vystavujete bezpečnostním rizikům, jako například dříve uvedený SQL
enumeration nebo Mass Assignment Vulnerability.