From 76825ee30ce0508f247161e32f73a2c7a044d2f1 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 17 Dec 2024 15:27:52 +0100 Subject: [PATCH] security --- database/cs/security.texy | 149 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 database/cs/security.texy diff --git a/database/cs/security.texy b/database/cs/security.texy new file mode 100644 index 0000000000..f1926390c6 --- /dev/null +++ b/database/cs/security.texy @@ -0,0 +1,149 @@ +Bezpečnostní rizika +******************* + +.[perex] +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. + +Ke zranitelnosti dochází, když se hodnoty z nedůvěryhodného zdroje vkládají přímo do SQL řetězce: + +```php +// ❌ 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: + +```php +// ❌ 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ů: + +```php +// ✅ 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 zástupný otazníky parametry. + + +Pole hodnot +----------- + +Pro příkazy INSERT, UPDATE nebo klauzuje WHERE můžeme použít pole hodnot: + +```php +// ✅ 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ů. + +.[warning] +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í. + + +Klíče polí nejsou bezpečné API +============================== + +Zatímco hodnoty v polích jsou bezpečné, o klíčích to neplatí: + +```php +// ❌ 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 zjistit existenci citlivých sloupců a jejich obsah: + +```php +$_GET = ['is_admin =', 1]; // zjistí, zda existuje sloupec is_admin +$_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: + +```php +// Legitimní použití operátorů v klíčích +$table->where([ + 'age > ?' => 18, + 'ROUND(score, ?) > ?' => [2, 75.5], +]); + +// ❌ Proto toto je NEBEZPEČNÉ: +$_GET = [ + 'id > 0 OR 1=1' => 1, // útočník může vložit vlastní SQL +]; +$table->where($_GET); // vygeneruje podmínku WHERE id > 0 OR 1=1 +// což vrátí všechny řádky +``` + +Toto **umožňuje SQL injection**. Proto vždy explicitně vyjmenujte sloupce, se kterými chcete pracovat: + + +Whitelist sloupců +----------------- + +Pro bezpečné zpracování vstupu od uživatele použijte whitelist povolených sloupců: + +```php +// ✅ Bezpečné zpracování vstupu pomocí whitelistu +$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`: + +```php +// ✅ 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']); +```