IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 3 - Jak se bránit proti SQL injection

V předchozí lekci, Technika útoku SQL injection, jsme se seznámili s útokem SQL injection.

V dnešní lekci si ukážeme, jak se před útokem SQL injection chránit.

Jak aplikaci chránit?

Problém je v tom, že vkládáme přímo proměnnou do SQL dotazu. A je jedno, jestli je přímo od uživatele nebo ne, vždy je riziko, že může dotaz nějakým způsobem rozbít.

Zastaralý způsob ochrany je ošetřování proměnných (tzv. escapování), které ovšem v několika případech selhává. Jediná správná ochrana proti tomuto útoku je nevkládat proměnné do dotazů vůbec a používat tzv. Prepared Statements (parametrizované dotazy).

Parametrizované dotazy (Prepared Statements)

SQL dotazy nejprve připravíme tak, že místo hodnot napíšeme zástupné znaky. Hodnoty a dotaz předáme databázi úplně odděleně a ona si je tam sama automaticky vloží tak, aby to bylo bezpečné. Automat na rozdíl od lidí nechybuje a my si můžeme být jistí, že se nevystavujeme žádnému riziku.

PDO pro parametrizované dotazy nabízí dvě metody:

  • PDO::prepare() a
  • PDOStatement::execute():
$name = $_POST["name"];
$password = hash("SHA512", $_POST["password"] . 'sůůůl');

$prepared = $pdo->prepare("
    SELECT `id`
    FROM `user`
    WHERE `name` = ? AND `password` = ?
    LIMIT 1
");

// Metoda prepare() vrací instanci třídy PDOStatement
$prepared->execute(array($name, $password));

Místo proměnných použijeme v dotazu otazníky. Proměnné předáme později najednou v poli. Zástupné znaky můžeme i pojmenovat:

$name = $_POST["name"];
$password = hash("SHA512", $_POST["password"]);

$prepared = $pdo->prepare("
    SELECT `id`
    FROM `user`
    WHERE `name` = :name AND `password` = :password
    LIMIT 1
");

$prepared->execute(array(
    ":name" => $name,
    ":password" => $password
));

Pojmenované zástupné znaky se mohou hodit jak pro přehlednost dotazu, tak i v případě, že jednu hodnotu chceme použít vícekrát (nemusíme psát tolik otazníků, kolikrát chceme hodnotu použít).

Některé databázové nadstavby (wrappery) umožňují zápis prepare() a execute() zkrátit. Naše tři metody pro získání ID uživatele bychom mohli vyměnit za jednu:

$id = $db->fetchColumn("
    SELECT `id`
    FROM `user`
    WHERE `name` = ? AND `password` = ?
    LIMIT 1
", $name, $password);

První parametr je SQL dotaz, ostatní jsou parametry, které se předají metodě PDOStatement::execute().

Manuální escapování

Druhým, zastaralým a nebezpečným způsobem obrany proti SQL injekci je proměnné ručně ošetřovat (tzv. escapovat). Tento způsob nepoužívejte, ukážeme si ho jen pro úplnost.

Každá databáze má trochu jinou strukturu SQL dotazu, proto má také jiný algoritmus pro ošetření řetězce. Databáze MySQL pro to má funkci s názvem mysqli_real_escape_string(). Funkce předsadí nebezpečné znaky jako uvozovky zpětným lomítkem, databáze je potom bere jako text a ne jako část dotazu:

$conn = mysqli_connect("localhost", "root", "password", "db");

$name = mysqli_real_escape_string($conn, $_POST["name"]);
$password = mysqli_real_escape_string($conn, hash("SHA512", $_POST["password"] . 'sůůůl'));

$idQuery = mysqli_query($conn, "
    SELECT `id`
    FROM `user`
    WHERE `name` = '{$name}' AND `password` = '{$password}'
    LIMIT 1
");

PDO ovladač má pro tyto účely metodu quote():

$name = $pdo->quote($_POST["name"]);
$password = $pdo->quote(hash("SHA512", $_POST["password"] . 'sůůůl'));

$idQuery = $pdo->query("
    SELECT `id`
    FROM `user`
    WHERE `name` = '{$name}' AND `password` = '{$password}'
    LIMIT 1
");

Jsme tedy zabezpečení? Ne, je to jen iluze. Představte si tento dotaz:

'DELETE * FROM user WHERE id=' . mysqli_real_escape_string($conn, $_GET['id'])

Útočník může do parametru GET zadat řetězec:

1 OR 1=1

A hle, nejsou v něm žádné uvozovky ani jiné škodlivé znaky. Escapovací funkce tedy neprovede nic a útočník stejně vymaže všechny uživatele místo jednoho. Jak se tomu bránit? Zkusme dát hodnotu do uvozovek, i když je to číslo:

'DELETE * FROM user WHERE id="' . mysqli_real_escape_string($conn, $_GET['id']) . '"'

Dotaz funguje, databáze se s číslem zadaným jako text v tomto případě popere. Když bychom takto však zadali číslo v klauzuli LIMIT, máme problém. Jediné řešení je přetypovat parametry na datový typ int:

'SELECT * FROM user LIMIT ' . (int)($_GET['id'])

Musíme přemýšlet nad typem dat a podle toho ručně ošetřovat. Máme spoustu prostoru k tomu, abychom vytvořili nějakou bezpečnostní chybu. Proto vždy používáme parametrizované dotazy, nikdy proměnné raději neošetřujme sami.

Ochrana na straně databáze

Ochranu bychom neměli zanedbat ani na straně databáze, kde by mělo být správně nastaveno oprávnění uživatele. Kdyby se tedy podařilo útočníkovi odeslat jeho vlastní SQL dotaz, databáze by mu zabránila v přístupu k tabulkám. Mezi nejdůležitější oprávnění patří příkazy pro úpravu tabulek například DROP a ALTER, ale taky práva pro zobrazení systémových tabulek uchovávající strukturu databáze. Vhodné je také použít databázové pohledy, které uchovávají zúžený pohled tabulky.

Databázové knihovny

Pro PHP existují knihovny, které dotazy automaticky generují pomocí volání metod. Takové knihovny v nitru obvykle používají parametrizované dotazy. Rozdíl je v tom, že sestaví SQL dotaz přesně podle druhu databáze. Pokud bychom dotaz psali ručně, museli bychom ho po změně databáze upravit (= práce navíc). Například ve frameworku Nette by šlo dotaz sestavit pomocí zřetězení metod:

$id = $db->table("user")->
   where(
      array(
      "name" => $name,
      "password" => $password
   ))->fetch()->id;

Závěr

Každý vstup od uživatele znamená pro naši aplikaci potenciální nebezpečí. Nikdy nevkládáme proměnné do SQL dotazu, jinak se vystavujeme bezpečnostnímu riziku. V SQL dotazech se smí vyskytovat pouze zástupné znaky. Proměnné předáme až ve druhém kroku a odděleně

V další lekci, Obrana proti útoku XSS v PHP, si ukážeme, jak zabezpečit web v PHP proti útoku XSS.


 

Předchozí článek
Technika útoku SQL injection
Všechny články v sekci
Bezpečnost webových aplikací v PHP
Přeskočit článek
(nedoporučujeme)
Obrana proti útoku XSS v PHP
Článek pro vás napsal Jan Hranický
Avatar
Uživatelské hodnocení:
18 hlasů
Aktivity