Obrana proti útoku Mass assignment v PHP

PHP Bezpečnost Obrana proti útoku Mass assignment v PHP

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živatlei v databázi

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

Uživatelé 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

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

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

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

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.


 

  Aktivity (1)

Článek pro vás napsal Martin Konečný (pavelco1998)
Avatar
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.

Jak se ti líbí článek?
Celkem (9 hlasů) :
4.777784.777784.777784.777784.77778


 


Miniatura
Všechny články v sekci
Bezpečnost webových aplikací v PHP
Miniatura
Následující článek
Nová reCaptcha - Jak ji použít?

 

 

Komentáře
Zobrazit starší komentáře (17)

Avatar
asanos
Člen
Avatar
asanos:

1. Máš v plánu pokračovat dalšími články v téhle sekci?
2. Nezdá se ti to jako "návod" pro útočníka?
3. Nechtěl by jsi napsat zmínku o OWASP Top Ten a dál pokračovat například podle něj, nebo je to velké sousto?

  • Vím, že například o XSS by se dalo mluvit hodiny a hodiny. A jeden článek by teda vše podstatné asi nevystihl.
  • Máš teda už nějaké představy, kam to spěje?

Předem děkuji za odpověďi.

Odpovědět 29.3.2014 21:17
Na světě je 10 typů lidí. Ti, kteří rozumí binárce a ti co nerozumí.
Avatar
Odpovídá na asanos
Michal Žůrek (misaz):

návod pro útočníka = návod pro obránce. Aby ses dokázal ubránit musíš vědět jak se útočí, stejně tak pokud chceš točit musíš vědět jak se brání.

Odpovědět  +3 29.3.2014 21:20
Nesnáším {}, proto se jim vyhýbám.
Avatar
asanos
Člen
Avatar
Odpovídá na Michal Žůrek (misaz)
asanos:

Jj, tohle si také myslím. Sám jsem si říkal, že bych něco napsal. Ale zase je tady problém, že se najde určitě někdo, kdo toho zneužije.

Odpovědět 29.3.2014 21:25
Na světě je 10 typů lidí. Ti, kteří rozumí binárce a ti co nerozumí.
Avatar
Odpovídá na asanos
Michal Žůrek (misaz):

to se najde vždycky, nemusíš se bát.

Odpovědět 29.3.2014 21:56
Nesnáším {}, proto se jim vyhýbám.
Avatar
Odpovídá na asanos
Martin Konečný (pavelco1998):
  1. Ano, mám v plánu ještě nějaké články připsat, jen to z určitých důvodů nemohu udělat teď.
  2. Zneužít se toho dá - a právě kvůli tomu tato sekce vůbec nemusela vzniknout. Podle mého názoru je ale lepší ten útok ukázat, aby web vývojáři věděli, co je může postihnout a jak tomu mají předcházet.
  3. O OWASP jsem bohužel neslyšel.
 
Odpovědět  +1 30.3.2014 14:32
Avatar
kuba_kubikula:

Ahoj,
trošku tápu.
Je aplikace ochráněna proti tomuto útoku když předané hodnoty filtruji
$data1 = filter_input(IN­PUT_POST,'data')
a používám předpřipravenou funkci
$dbh->prepare('SELECT * FROM fruit WHERE ID = ? AND colour = ?');
$dbh->execute(arra­y($data1,$data2);

Děkuji, Jakub

 
Odpovědět 9.5.2014 0:47
Avatar
Odpovědět 9.5.2014 6:24
Nesnáším {}, proto se jim vyhýbám.
Avatar
BulDozer Diwinorum:

Ahoj, ahoj.... ja by som sa spytal....

1. je nejaka moznost zistit nazov db alebo nazvy tabuliek alebo stlpcov ak sa tieto nazvy vyskytuju len v mysql dopytoch?

a

2. je v niecom lepsie pouzivat prikaz mysqli_ od mysql_ ?
3. ak uz pouzijem id ako cislo tak to predsa staci overit prikazom is_numeric....alebo sa mylim?

totiz to, moc mi nesadlo OOP a robim radsej proceduralne a priznavam, ze moje programovanie nie je nic extra :D ,kazdopadne v mojich projektoch potrebujem osetrit citlive data

vopred dakujem za rady

Editováno 11.4.2015 18:49
 
Odpovědět 11.4.2015 18:48
Avatar
Odpovídá na BulDozer Diwinorum
Martin Konečný (pavelco1998):

zdar,

  1. neznám jiný způsob, kterým by to šlo zjistit, než zobrazením názvu v chybovém hlášení (to by mělo být na ostrém serveru zakázáno).
  2. mysqli neznám, ale má umožňovat např. parametrizované dotazy. Spíš se podívej na PDO (tady je o tom hodně tutoriálů).
  3. is_numeric() moc nepoužívám - když už chci mít v dotazu číslo, tak to přetypuji, je to jistější. Pokud ale jako parametry posíláš uživatelské vstupy ručně, pak je to OK - tento článek mluví spíše o tom, že do DB dotazu vložíš rovnou celé pole (třeba $_POST), aniž by sis zkontroloval, jestli obsahuje pouze to, co očekáváš.
 
Odpovědět 11.4.2015 18:56
Avatar
Odpovídá na Martin Konečný (pavelco1998)
BulDozer Diwinorum:

tak to by ma ani vo sme nenapadlo :D :D dakujem teda za ukludnenie mojej paranoje :D :D :D ... pozrem teda aj na to OOP, uz niektore kniznice v podstate aj vyuzivam.... hlavne html do pdf alebo posielanie mailov ... a velmi sa mi pacili aj clanky, co napisal David Čápka o vlastnom frameworku... skoda len, ze mi ich neukazalo vsetky do konca.... kazdopadne dik aj za to... tento web je paradny, prehladny a ako jeden z mala aj zrozumitelny

Editováno 11.4.2015 19:11
 
Odpovědět 11.4.2015 19:10
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 27. Zobrazit vše