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 6 - Obrana proti útoku Mass assignment v PHP

V předchozí lekci, Útok CSRF (Cross Site Request Forgery) a jak se bránit, jsme se seznámili s útokem Cross Site Request Forgery a také jsme si uvedli způsoby, jak před tímto útokem svou aplikaci chránit.

Mass assignment je útok, který se z angličtiny překládá jako hromadné přiřazení. Představme si následující situaci:

Máme na webové stránce registrované uživatele a chceme jim poskytnout možnost změny údajů. V databázi máme tabulku s uživateli:

Tabulka s uživateli v databázi - Bezpečnost webových aplikací v PHP

S tímto jedním testovacím uživatelem:

Uživatelé v PHP - Bezpečnost webových aplikací v PHP

Vytvoříme jednoduchý formulář (proměnné se vytáhnou z databáze).

<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>

Vypadá asi takto:

Editační formulář v PHP - Bezpečnost webových aplikací v PHP

Předpokládejme, že pracujeme s nějakým databázovým wrapperem (ono to bez něj v dnešní době již ani moc nejde), který obsahuje metodu pro 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);
    }
}

Pokud napří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

Nyní, protože jsme velmi líní, zpracujeme formulář tímto způsobem:

<?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 polích a formulář odešleme.

Odeslání editačního formuláře v PHP - Bezpečnost webových aplikací v PHP

Metoda Database::update() sestaví SQL dotaz následovně:

UPDATE user SET firstname = 'Jméno', lastname = 'Příjmení', nick = 'nick' WHERE id = 1

Takové volání metody je velmi snadné, stačí vyplnit 2-3 krátké parametry. Jenže zápis druhého parametru je velmi nebezpečný. Proměnná $_POST pochází od uživatele a můžeme jí data podstrčit a to hned několika způsoby.

Vraťme se zpět na editační formulář. Ukážeme si jeden primitivní způsob, jak data podstrčit, a to přes úpravu HTML formuláře. Podstrčený input vytvoříme jako textové pole, aby byl pro ukázku vidět:

Editace HTML formuláře - Bezpečnost webových aplikací v PHP

Pokud následně formulář odešleme, metoda nám najednou vygeneruje takový SQL dotaz:

UPDATE user SET firstname = 'Martin', lastname = 'Konečný', nick = 'pavelco1998', admin = '1' WHERE id = 1

Když se následně podíváme do tabulky 'user', uvidíme upravený údaj ve sloupci 'admin'.

Mass assignment v PHP - Bezpečnost webových aplikací v PHP

Tento příklad útoku by šel provést i tak, že jednoduše přepíšeme atribut některého z textových polí (např. z name="firstname" na name="admin") a změnili hodnotu na 1.

Jak útoku předcházet?

Možností je hned několik.

1. Vypsání hodnot ručně

// zpracování formuláře

$data = array(
    "firstname" => $_POST["firstname"],
    "lastname" => $_POST["lastname"],
    "nick" => $_POST["nick"]
);
$db->update("user", $data, "id = {$userId}");

2. Vytvořit si seznam povolených hodnot

$allowed = array("firstname", "lastname", "nick");

Díky tomuto poli lze i zkrátit naši kontrolu vyplněných hodnot:

$errors = array();
if ($_POST) {
    foreach ($allowed as $postKey) {
        if (empty($_POST[$postKey])) {
            $errors[] = "Nebyla vyplněna hodnota pole '{$postKey}'";
        }
    }
}

Pro samotný update využijeme dvou funkcí:

  • array_flip() - přehodí v poli klíče a hodnoty
  • array_intersec­t_key() - vybere pouze ty hodnoty, jejichž klíče jsou stejné v obou polích
$data = array_intersect_key($_POST, array_flip($allowed));
$db->update("user", $data, "id = {$userId}");

3. Využít formulářovou knihovnu, která automaticky vrací jen správné hodnoty

Pro ukázku využijeme Davidovu třídu 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>";
    }
}

Nyní se nemůže stát, že by se v tabulce upravily hodnoty jiných sloupců.

Závěrem

Nikdy, ale opravdu nikdy nevěřte uživateli, který navštíví vaše stránky. Vždy dbejte na ochranu vaší aplikace, protože nikdy nevíte, jaký problém z toho může vzniknout. HTML si každý může ve svém prohlížeči upravit, proto by pro tento příklad nefungovala ani kontrola v JavaScriptu - jednoduše by si ho mohl uživatel ze stránky odstranit nebo vypnout.

V další lekci, Útok Clickjacking a jak se před ním bránit, se seznámíme s útokem Clickjacking a uvedeme si způsoby, jak se před tímto typem útoku bránit.


 

Předchozí článek
Útok CSRF (Cross Site Request Forgery) a jak se bránit
Všechny články v sekci
Bezpečnost webových aplikací v PHP
Přeskočit článek
(nedoporučujeme)
Útok Clickjacking a jak se před ním bránit
Článek pro vás napsal Martin Konečný (pavelco1998)
Avatar
Uživatelské hodnocení:
22 hlasů
Autor se o IT moc nezajímá, raději by se věnoval speciálním jednotkám jako jsou SEALs nebo SAS. Když už to ale musí být něco z IT, tak tvorba web. aplikací v PHP. Také vyvýjí novou českou prohlížečovou RPG hru a provozuje osobní web http://www.mkonecny.cz
Aktivity