Připravené dotazy
Příklady na této stránce byly spuštěny nad vzorovou databází produktů.
SQL Injection – PDO::query a parametrizované dotazy
- Dotazy často závisí na uživatelském vstupu (formuláře, vyhledávání, …)
- Uživatelskému vstupu nemůžeme důvěřovat
S využitím předchozích znalostí bychom mohli paramtrizovaný dotaz implementovat např. takto:
// tento kód není napsán bezpečně
$name = 'Škoda Octavia'; // načteno od uživatele
$statement = $pdo->query( 'SELECT price FROM product WHERE name="' . $name . '"' );
print_r($statement->fetchColumn()); // vypíše 980000Kód funguje, ale…
…$name může obsahovat cokoli (jde o uživatelský vstup). Co když bude obsahovat SQL kód?[1]
// AND 1=0 převede předchozí podmínky na nesplnitelné
// UNION SELECT připojí libovolný další SQL dotaz (určuje útočník)
$name = '" AND 1=0 UNION SELECT name FROM product WHERE id=3 -- '; // načteno od uživatele
$statement = $pdo->query( 'SELECT price FROM product WHERE name="' . $name . '"' );
print_r($statement->fetchColumn()); // vrátí BMW 3 sedan (i když dotaz byl původně na cenu)
Důsledky úspěšné SQL Injection:
- útočník může zobrazit klást libovolné SELECT SQL dotazy a zobrazit tak libovolná data,
- za určitých okolností[2] může databázi i libovolně modifikovat
Ošetření uživatelského vstupu
PDO::quote() převede text tak, aby byl bezpečně použitelný v dotazu (escapování všeho, co má v SQL speciální význam)
$name = 'Škoda Octavia'; $name = $pdo->quote($name); print_r($name); // vypíše 'Škoda Octavia' (vč. uvozovek)- náchylné na chybu vývojáře, útočníkovi stačí jediný neošetřený vstup
- nevhodné používat, připravené dotazy jsou spolehlivější alternativou
// tento kód je bezpečný
$name = 'Škoda Octavia'; // načteno od uživatele
$name = $pdo->quote($name);
$statement = $pdo->query( 'SELECT price FROM product WHERE name=' . $name );
print_r($statement->fetchColumn()); // vypíše 980000Připravené dotazy
- Umožňují rozdělit šablonu dotazu a parametry
- O ošetření uživatelského vstupu se postará databáze
- nižší šance na lidskou chybu ⇒ připravené dotazy jsou bezpečnější
- Databáze si může vytvořit obecný prováděcí plán dotazu a použít jej pro podobné dotazy
- ⇒ připravené dotazy jsou zpravidla rychlejší
- PDO :: prepare: vytvoření šablony dotazu
- PDOStatement :: execute: spuštění dotazu (možné předat parametry)
- Podrobná dokumentace připravených dotazů
Příklad
$query = 'SELECT * FROM `product` WHERE id = :product_id';
$statement = $pdo->prepare($query);
$statement->execute(['product_id' => 1]);
print_r($statement->fetch());$query = "INSERT INTO `product` (name, price, vat_rate) VALUES (:name, :price, :vat_rate)";
$statement = $pdo->prepare($query);
$statement->execute([
'name' => 'Telefon iPhone 15',
'price' => 17000,
'vat_rate' => 0.21,
]);Propojení PHP proměnných s připraveným dotazem
PDOStatement::bindParam(): zadaný parametr je třeba načíst z určené PHP proměnné (předání referencí)$id = 1; $query = 'SELECT name FROM `product` WHERE id = :id'; $statement = $pdo->prepare($query); $statement->bindParam('id', $id); $statement->execute(); $statement->fetchColumn(); // vrací Mitsubishi Lancer $id = 2; $statement->execute(); // parametr předán referencí => dojde k aktualizaci $statement->fetchColumn(); // vrací Škoda OctaviaPDOStatement::bindValue(): zadaný parametr je přečten z určené PHP proměnné (předání hodnotou)$id = 1; $query = 'SELECT name FROM `product` WHERE id = :id'; $statement = $pdo->prepare($query); $statement->bindValue('id', $id); $statement->execute(); $statement->fetchColumn(); // vrací Mitsubishi Lancer $id = 2; $statement->execute(); // parametr předán hodnotou => proměnná $id již není čtena => spuštěn stejný dotaz, jako výše $statement->fetchColumn(); // vrací Mitsubishi Lancer
- Více informací o SQL injekci jsou k nalezení např. v materiálech k předmětu BI-BEK: Bezpečný kód. ↩
- Související s konfigurací PDO a databáze. Obvykle není možné spustit dva samostatné SQL příkazy (př. jeden SELECT a druhý DELETE). ↩