IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

3. diel - Ako sa brániť proti SQL injection

V predchádzajúcej lekcii, Technika útoku SQL injection , sme sa zoznámili s útokom SQL injection.

V dnešnej lekcii si ukážeme, ako sa pred útokom SQL injection chrániť.

Ako aplikáciu chrániť?

Problém je v tom, že vkladáme priamo premennú do SQL dotazu. A je jedno, či je priamo od užívateľa alebo nie, vždy je riziko, že môže dotaz nejakým spôsobom rozbiť.

Zastaralý spôsob ochrany je ošetrovania premenných (tzv. Uvádzacích), ktoré však v niekoľkých prípadoch zlyháva. Jediná správna ochrana proti tomuto útoku je nevkladať premenné do otázok vôbec a používať tzv. Prepared Statements (parametrizované otázky).

Parametrizované otázky (Prepared Statements)

SQL dotazy najprv pripravíme tak, že namiesto hodnôt napíšeme zástupné znaky. Hodnoty a otázka odovzdáme databázu úplne oddelene a ona si ich tam sama automaticky vloží tak, aby to bolo bezpečné. Automat na rozdiel od ľudí neprehrešuje a my si môžeme byť istí, že sa nevystavujeme žiadnemu riziku.

PDO pre parametrizované otázky ponúka dve metódy:

  • 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));

Miesto premenných použijeme v dotaze otázniky. Premenné odovzdáme neskôr naraz v poli. Zástupné znaky môžeme aj pomenovať:

$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
));

Pomenované zástupné znaky sa môžu hodiť ako pre prehľadnosť dopytu, ako aj v prípade, že jednu hodnotu chceme použiť viackrát (nemusíme písať toľko otáznikov, koľkokrát chceme hodnotu použiť).

Niektoré databázové nadstavby (wrappery) umožňujú zápis prepare() a execute() skrátiť. Naše tri metódy pre získanie ID používateľa by sme mohli vymeniť za jednu:

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

Prvý parameter je SQL dotaz, ostatné sú parametre, ktoré sa odovzdajú metóde PDOStatement::execute().

Manuálny uvádzacích

Druhým, zastaraným a nebezpečným spôsobom obrany proti SQL injekciu je premennej ručne ošetrovať (tzv. Escapovat). Tento spôsob nepoužívajte, ukážeme si ho len pre úplnosť.

Každá databáza má trochu inú štruktúru SQL dotazu, preto má aj iný algoritmus pre ošetrenie reťazca. Databáza MySQL pre to má funkciu s názvom mysqli_real_escape_string(). Funkcia Predsadí nebezpečné znaky ako úvodzovky spätným lomítkom, databázy je potom berie ako text a nie ako časť 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 ovládač má pre tieto účely metódu 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
");

Sme teda zabezpečenia? Nie, je to len ilúzia. Predstavte si tento dotaz:

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

Útočník môže do parametra GET zadať reťazec:

1 OR 1=1

A hľa, nie sú v ňom žiadne úvodzovky ani iné škodlivé znaky. Escapovací funkcie teda nevykoná nič a útočník rovnako vymaže všetkých užívateľov namiesto jedného. Ako sa tomu brániť? Skúsme dať hodnotu do úvodzoviek, aj keď je to číslo:

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

Otázka funguje, databáza sa s číslom zadaným ako text v tomto prípade pobije. Keď by sme takto však zadali číslo v klauzule LIMIT, máme problém. Jediné riešenie je pretypovať parametre na dátový typ int:

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

Musíme premýšľať nad typom dát a podľa toho ručne ošetrovať. Máme veľa priestoru na to, aby sme vytvorili nejakú bezpečnostnú chybu. Preto vždy používame parametrizované otázky, nikdy premenné radšej neošetrujte sami.

Ochrana na strane databázy

Ochranu by sme nemali zanedbať ani na strane databázy, kde by malo byť správne nastavené oprávnenia užívateľa. Keby sa teda podarilo útočníkovi odoslať jeho vlastné SQL dotaz, databáza by mu zabránila v prístupe k tabuľkám. Medzi najdôležitejšie oprávnenie patrí príkazy pre úpravu tabuliek napríklad DROP a ALTER, ale taky práva pre zobrazenie systémových tabuliek uchovávajúci štruktúru databázy. Vhodné je tiež použiť databázové pohľady, ktoré uchovávajú zúžený pohľad tabuľky.

Databázové knižnice

Pre PHP existujú knižnice, ktoré otázky automaticky generujú pomocou volanie metód. Takéto knižnice v vnútri obvykle používajú parametrizované otázky. Rozdiel je v tom, že zostaví SQL dotaz presne podľa druhu databázy. Ak by sme dotaz písali ručne, museli by sme ho po zmene databázy upraviť (= práce navyše). Napríklad vo frameworku Nette by šlo otázku zostaviť pomocou zreťazenie metód:

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

Záver

Každý vstup od užívateľa znamená pre našu aplikáciu potenciálne nebezpečenstvo. Nikdy nevkladáme premenné do SQL dotazu, inak sa vystavujeme bezpečnostnému riziku. V SQL otázkach sa smie vyskytovať iba zástupné znaky. Premenné odovzdáme až v druhom kroku a oddelene

V ďalšej lekcii, Útok CSRF (Cross Site Request Forgery) a ako sa brániť , sa zoznámime s útokom Cross Site Request Forgery a uvedieme si spôsoby ako sa pred týmto typom útoku brániť.


 

Predchádzajúci článok
Technika útoku SQL injection
Všetky články v sekcii
Bezpečnosť webových aplikácií v PHP
Preskočiť článok
(neodporúčame)
Útok CSRF (Cross Site Request Forgery) a ako sa brániť
Článok pre vás napísal Jan Hranický
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Aktivity