Jdi na navigaci předmětu

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 980000

Kó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)
xkcd exploits of a mom
Obrázek 1. SQL injekce v praxi (zdroj: xkcd #327, Exploits of a Mom, CC-BY-NC 2.5)

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 980000

Př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 Octavia
  • PDOStatement::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

  1. Více informací o SQL injekci jsou k nalezení např. v materiálech k předmětu BI-BEK: Bezpečný kód.
  2. Související s konfigurací PDO a databáze. Obvykle není možné spustit dva samostatné SQL příkazy (př. jeden SELECT a druhý DELETE).