Obrana proti útoku Mass assignment v PHP
V predchádzajúcej lekcii, Úvod do bezpečnosti webových aplikácií , sme sa dozvedeli, čo si pod pojmom webová aplikácia predstaviť, tiež sme si predstavili princíp fungovania trojvrstvovej architektúry a tiež aplikačnú vrstvu. Poznáme tiež Web application firewall, nástroj ktorý chráni aplikačnú vrstvu aplikácie.
Mass assignment je útok, ktorý sa do angličtiny prekladá ako hromadné priradenie. Predstavme si nasledujúcu situáciu:
Máme na webovej stránke registrovaných užívateľov a chceme im poskytnúť možnosť zmeny údajov. V databáze máme tabuľku s používateľmi:

S týmto jedným testovacím užívateľom:

Vytvoríme jednoduchý formulár (premenné sa vytiahnu z databázy).
<h2>Editace údajů</h2> <form method="post"> <table> <tr><th>Jméno</th></tr> <tr><td><input type="text" name="firstname" value="<?= htmlspecialchars($firstname) ?>" /></td></tr> <tr><th>Příjmení</th></tr> <tr><td><input type="text" name="lastname" value="<?= htmlspecialchars($lastname) ?>" /></td></tr> <tr><th>Přezdívka</th></tr> <tr><td><input type="text" name="nick" value="<?= htmlspecialchars($nick) ?>" /></td></tr> <tr><td><input type="submit" value="Změnit údaje" /></td></tr> </table> </form>
Vyzerá asi takto:

Predpokladajme, že pracujeme s nejakým databázovým Wrapper (ono to bez neho v dnešnej dobe už ani veľmi nejde), ktorý obsahuje metódu pre update:
class Database extends PDO { // hromada kódu /** * @param string * @param array * @param string|NULL * @return PDOStatement */ public function update($table, $data, $where = NULL) { $sql = "UPDATE {$table} SET "; $counter = 0; $dataCount = count($data); foreach ($data as $column => $value) { $value = $this->quote($value); $sql .= "{$column} = {$value}"; if ($counter < $dataCount - 1) { $sql .= ", "; } $counter++; } if ($where !== NULL) { $sql .= " WHERE {$where}"; } return $this->query($sql); } }
Ak napríklad zavoláme
$db = new Database("přihlašovací údaje"); $db->update("user", array( "firstname" => "jméno", "lastname" => "příjmení", "nick" => "přezdívka" ), "id = 1");
, Vygeneruje nám to tento SQL dotaz:
UPDATE user SET firstname = 'jméno', lastname = 'příjmení', nick = 'přezdívka' WHERE id = 1
Teraz, pretože sme veľmi leniví, spracujeme formulár týmto spôsobom:
<?php $db = new Database("přihlašovací údaje"); $userId = (int) $_SESSION["userId"]; $errors = array(); if ($_POST) { if (empty($_POST["firstname"])) { $errors[] = "Nebylo vyplněno jméno."; } if (empty($_POST["lastname"])) { $errors[] = "Nebylo vyplněno příjmení."; } if (empty($_POST["nick"])) { $errors[] = "Nebyla vyplněna přezdívka."; } if (empty($errors)) { $db->update("user", $_POST, "id = {$userId}"); // Uložení zprávy o editaci, přesměrování } }
Upravíme údaje v textových poliach a formulár odošleme.

Metóda Database :: update () zostaví SQL dotaz nasledovne:
UPDATE user SET firstname = 'Jméno', lastname = 'Příjmení', nick = 'nick' WHERE id = 1
Také volanie metódy je veľmi jednoduché, stačí vyplniť 2-3 krátke parametre. Lenže zápis druhého parametra je veľmi nebezpečný. Premenná $ _POST pochádza od užívateľa a môžeme jej dáta podstrčiť a to hneď niekoľkými spôsobmi.
Vráťme sa späť na editačné formulár. Ukážeme si jeden primitívny spôsob, ako dáta podstrčiť, a to cez úpravu HTML formulára. Podstrčené input vytvoríme ako textové pole, aby bol pre ukážku vidieť:

Ak následne formulár odošleme, metóda nám zrazu vygeneruje taký SQL dotaz:
UPDATE user SET firstname = 'Martin', lastname = 'Konečný', nick = 'pavelco1998', admin = '1' WHERE id = 1
Keď sa následne pozrieme do tabuľky 'user', uvidíme upravený údaj v stĺpci 'admin'.

Tento príklad útoku by išiel vykonať aj tak, že jednoducho prepíšeme atribút niektorého z textových polí (napr. Z name = "firstname" na name = "admin") a zmenili hodnotu na 1.
Ako útoku predchádzať?
Možností je hneď niekoľko.
1. Vypísanie hodnôt ručne
// zpracování formuláře $data = array( "firstname" => $_POST["firstname"], "lastname" => $_POST["lastname"], "nick" => $_POST["nick"] ); $db->update("user", $data, "id = {$userId}");
2. Vytvoriť si zoznam povolených hodnôt
$allowed = array("firstname", "lastname", "nick");
Vďaka tomuto poli možno aj skrátiť našu kontrolu vyplnených hodnôt:
$errors = array(); if ($_POST) { foreach ($allowed as $postKey) { if (empty($_POST[$postKey])) { $errors[] = "Nebyla vyplněna hodnota pole '{$postKey}'"; } } }
Pre samotný update využijeme dvoch funkcií:
- array_flip () - prehodí v poli kľúče a hodnoty
- array_intersect_key () - vyberie iba tie hodnoty, ktorých kľúče sú rovnaké v oboch poliach
$data = array_intersect_key($_POST, array_flip($allowed)); $db->update("user", $data, "id = {$userId}");
3. Využiť formulárové knižnicu, ktorá automaticky vracia len správne hodnoty
Pre ukážku využijeme Dávidovmu triedu Form:
$form = new Form("userUpdate"); $form->addTextBox("firstname", "Jméno", TRUE); $form->addTextBox("lastname", "Příjmení", TRUE); $form->addTextBox("nick", "Přezdívka", TRUE); $form->addButton("update", "Změnit údaje"); // hodnoty se opět vyberou z databáze $form->setData(array( "firstname" => $firstname, "lastname" => $lastname, "nick" => $nick )); if ($form->isPostBack()) { try { $db->update("user", $form->getData(), "id = {$userId}"); } catch (UserException $e) { echo "<span style='color: red;'>" . nl2br($e->getMessage()) . "</span>"; } }
Teraz sa nemôže stať, že by sa v tabuľke upravili hodnoty iných stĺpcov.
Záverom
Nikdy, ale naozaj nikdy neverte užívateľovi, ktorý navštívi vaše stránky. Vždy dbajte na ochranu vašej aplikácie, pretože nikdy neviete, aký problém z toho môže vzniknúť. HTML si každý môže vo svojom prehliadači upraviť, preto by pre tento príklad nefungovala ani kontrola v JavaScripte - jednoducho by si ho mohol užívateľ zo stránky odstrániť alebo vypnúť.
V ďalšej lekcii, Ako sa brániť proti SQL injection , si uvedieme spôsoby, ako pred útokom SQL injection svoju aplikáciu chrániť.