Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed Dec 18, 2024
1 parent 1fb1a16 commit 9ac3313
Showing 1 changed file with 71 additions and 56 deletions.
127 changes: 71 additions & 56 deletions database/cs/security.texy
Original file line number Diff line number Diff line change
@@ -1,29 +1,38 @@
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.
<div class=perex>

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é:

SQL Injection
=============
- Porozumět rozdílu mezi bezpečným a nebezpečným API
- Používat parametrizované dotazy
- Správně validovat vstupní data

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.
</div>


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

```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!
// 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
// ❌ NEBEZPEČNÝ KÓD - zranitelný vůči SQL injection
$table->where('name = ' . $_GET['name']);
$table->where("name = '$_GET[name]'");
```
Expand All @@ -34,83 +43,87 @@ 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ů:
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ý parametrizovaný dotaz
$database->query('SELECT * FROM users WHERE name = ?', $name);

// ✅ Bezpečná podmínka v Exploreru
$table->where('name = ?', $_GET['name']);
$table->where('name = ?', $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.

.[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í.


Pole hodnot
-----------
Tohle platí pro všechny další metody v [Database Explorer|explorer], které umožňují vkládat výrazy se zástupnými otazníky a parametry.

Pro příkazy INSERT, UPDATE nebo klauzule WHERE můžeme použít pole hodnot:
Pro příkazy INSERT, UPDATE nebo klauzuli WHERE můžeme bezpečně předat hodnoty v poli:

```php
// ✅ Bezpečný INSERT
$database->query('INSERT INTO users', [
'name' => $_GET['name'],
'email' => $_GET['email'],
'name' => $name,
'email' => $email,
]);

// ✅ Bezpečný UPDATE
$database->query('UPDATE users SET', [
'name' => $_GET['name'],
'email' => $_GET['email'],
], 'WHERE id = ?', $_GET['id']);
// ✅ Bezpečný INSERT v Exploreru
$table->insert([
'name' => $name,
'email' => $email,
]);
```

Nette Database automaticky escapuje všechny hodnoty předané přes parametrizované dotazy. Musíme však zajistit správný datový typ parametrů.
.[warning]
Musíme však zajistit správný datový typ parametrů.


Validace vstupních dat
======================

**Databáze předpokládá**, že všechna vstupní data už prošla validací. Nikdy do ní nesmí vstoupit nevalidovaná data - validační mezivrstva je povinná součást bezpečné práce s databází.

Validace musí zajistit:

1. Správný datový typ - parametry musí mít přesně ten typ, který se očekává. Například pokud by `$name` v příkladu výše bylo pole místo řetězce, Nette Database by se pokusilo vložit všechny jeho prvky do SQL dotazu, což by mohlo být nežádoucí.
2. Validní obsah - například u řetězců je třeba oveřit validní UTF-8 kódování nebo že maximální délka odpovídá definici sloupce v databázi, i čísel, že jejich hodnota je v povoleném rozsahu pro daný sloupec atd.

Proto nikdy nepoužívejte nevalidovaná data z `$_GET`, `$_POST` nebo `$_COOKIE` přímo v databázových dotazech. Místo toho:

- Používejte [Nette Formuláře|forms:], které automaticky zajistí správnou validaci všech vstupů
- Nebo implementujte vlastní validační vrstvu pomocí standardních PHP nástrojů jako `filter_var()`


Klíče polí nejsou bezpečné API
==============================

Zatímco hodnoty v polích jsou bezpečné, o klíčích to neplatí:
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);
// ❌ 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ů.
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 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čí:
Ve WHERE podmínkách je to ještě nebezpečnější, protože mohou obsahovat oprátory:

```php
$_GET = ['salary >', 100000]; // začne zjišťovat platové rozsahy
// ❌ 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)
```

Ale hlavní problém je, že WHERE podmínky podporují v klíčích SQL výrazy:
Ú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.

```php
// Legitimní použití operátorů v klíčích
$table->where([
'age > ?' => 18,
'ROUND(score, ?) > ?' => [2, 75.5],
]);
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É: ú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ů
```php
// ❌ 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)
```

Toto je opět **SQL injection**.
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ů
Expand All @@ -121,7 +134,7 @@ Pokud chcete uživateli umožnit volbu sloupců, vždy použijte whitelist:
```php
// ✅ Bezpečné zpracování - pouze povolené sloupce
$allowedColumns = ['name', 'email', 'active'];
$values = array_intersect_key($_GET, array_flip($allowedColumns));
$values = array_intersect_key($_POST, array_flip($allowedColumns));

$database->query('INSERT INTO users', $values);
```
Expand All @@ -130,16 +143,18 @@ $database->query('INSERT INTO users', $values);
Dynamické identifikátory
========================

Pro dynamické názvy tabulek a sloupců použijte zástupný symbol `?name`:
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):

```php
// ✅ 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']);
```

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.
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.

0 comments on commit 9ac3313

Please sign in to comment.