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*() a render*() 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.

verze: 4.0 3.x