Automatické zabezpečení

Nespoléhejte se na to, že do kódu nezapomenete na všechna místa připsat ošetření dat. Snažte se aplikaci navrhnout tak, aby se na nic zapomenout nedalo. Za cenu o něco složitějšího jádra bude veškerý kód, který ho používá, obvykle taky mnohem jednodušší.

Článek vyšel na blogu Jakuba Vrány, na Zdrojáku ho přetiskujeme s autorovým souhlasem.

Úroveň zabezpečení aplikace bych rozdělil do tří úrovní:

  1. Aplikace zabezpečená není, neošetřuje uživatelské vstupy ani své výstupy.
  2. Aplikace se o zabezpečení snaží, ale takovým způsobem, že na ně lze zapomenout.
  3. Aplikace se o zabezpečení stará sama, prakticky se nedá udělat chyba.

Jak se tyto úrovně projevují v jednotlivých oblastech?

XSS

Druhou úroveň představuje ruční ošetřování pomocí htmlspecialchars. Třetí úroveň zdánlivě reprezentuje automatické ošetřování v šablonách, např. v Nette Latte. Proč píšu zdánlivě? Problém je v tom, že ošetření se dá obvykle snadno zakázat, např. v Latte pomocí {!$var}. Viděl jsem šablony plné vykřičníků i na místech, kde být neměly. Autor to vysvětlil tak, že psaní {$var} někde způsobovalo problémy, které po přidání vykřičníku zmizely, tak je začal psát všude.

Je to samozřejmě hloupá chyba, ale přesto – jak jí zamezit? Řešením je informaci o ošetření dat uložit přímo do proměnné a v šablonách {!$var} úplně zakázat. V Nette jde data označit jako ošetřená takto:

<?php
$safeHtml = $texy->process($content_texy);
$content = Html::el()->setHtml($safeHtml);
// v šabloně pak můžeme použít {$content}
?>

Ideální by bylo, když by už samotná metoda process() vracela instanci  Html.

Samozřejmě lze namítnout, že chybu lze stále udělat nevhodným použitím Html. Nicméně už alespoň šablony jsou neprůstřelné a jejich autor (který může být míň zkušený) nemusí přemýšlet, která data ošetřit má a která ne.

SQL Injection

Druhou úroveň představuje ruční ošetřování pomocí mysql_real_escape_string nebo obdobné funkce. Třetí úroveň zdánlivě reprezentuje vázání proměnných, např. v PDO. Proč píšu zdánlivě? Problém je v tom, že napsat $pdo->prepare("... WHERE id = $_GET[id]") je funkční a ještě jednodušší než $pdo->prepare("... WHERE id = ?", $_GET["id"]). V některých případech to je dokonce jediné možné řešení, alternativu k $pdo->prepare("... ORDER BY $_GET[order]")  vázání proměnných nenabízí.

Jedno „dokonalé“ řešení jsem před časem nabídl, i když je poněkud nepraktické. NotORM šlo praktičtější cestou, ale výsledek z pohledu SQL Injection je jen o málo lepší než u PDO. Chybu pořád lze udělat, i když bezpečná verze je ve většině případů alespoň jednodušší: where("id", $_GET["id"]) je jednodušší než  where("id = $_GET[id]").

Správné řešení by mohlo vypadat nějak takhle: where("id", "=", $_GET["id"])  – první parametr by byl ošetřen jako identifikátor, druhý by byl z whitelistu a třetí jako hodnota. I třídění podle uživatelem zvoleného sloupce by bylo jednoduché: order($_GET["order"]). Alternativou je identifikátor předat přímo v názvu funkce, např. jako whereIdEquals($_GET["id"]), tam je ale použití obecného sloupce krkolomné ( {"orderBy$_GET[order]"}()).

Problém nastává u složitějších výrazů, např. s operátorem OR, což je jeden z důvodů, proč jsem toto řešení v NotORM nepoužil.

Neautorizovaný přístup k datům

Další běžná chyba, kde je řešení druhé úrovně jednoduché: stačí místo WHERE id = ? použít WHERE id = ? AND user_id = ?  – při získávání dat, zobrazování jejich detailu, jejich aktualizaci a mazání. Problém je, že na tohle se dá zapomenout velmi snadno.

Řešení třetí úrovně se v tomto případě hledá hůř. Místo jednoho ID platného pro všechny můžeme použít dvojici user_id, number. Když má uživatel třeba články a články mají komentáře, tak u komentáře bude primární klíč user_id, article_number, number. Některé dotazy to dokonce může zjednodušit – např. pro vypsání všech komentářů daného uživatele se obejdeme bez použití tabulky článků.

Pokud žádné ID uživatele uložené nemáme, dá se místo snadno uhodnutelného číselného ID použít dlouhé náhodné GUID.

Označování dat

Do PHP byla kdysi navržena podpora pro značkování řetězců, která nakonec vznikla jako extenze Taint. Za její zásadní nevýhodu považuji především to, že jako nebezpečná označuje jen data, která pochází bezprostředně od uživatele. Takže když vezmu $_POST parametr, ošetřím ho pro uložení do databáze, uložím a načtu, tak je najednou bezpečný i třeba pro výstup do HTML nebo pro nové uložení do databáze. Já bych byl radikálnější a jako nebezpečná data označil všechno, to by ale samozřejmě zcela zlikvidovalo zpětnou kompatibilitu.

Ošetřování dat

Za ideální považuji aplikaci, kde vývojář vůbec nemusí vědět, na jaké speciální znaky si musí dát pozor a jaké má k jejich ošetření použít funkce. Třeba dotaz prepare("WHERE name = 'admin'") je z tohoto pohledu špatně, protože musím vědět, že pro ohraničení řetězců se používají apostrofy. Dotaz prepare("WHERE name = ?", "admin") je lepší, pořád si ale musím dávat pozor na správné ošetření sloupce. U where("name", "=", "admin")  nemusím myslet na nic.

Závěr

Nespokojte se s druhou úrovní zabezpečení a nespoléhejte se na to, že na ošetření určitě nezapomenete. Snažte se aplikaci navrhnout tak, aby se na nic zapomenout nedalo. Za cenu o něco složitějšího jádra bude veškerý kód, který ho používá, obvykle taky mnohem jednodušší.

Ono se to ostatně netýká jen bezpečnosti, ale třeba i výkonnosti. V NotORM je docela těžké napsat kód tak, aby se dotazy nepokládaly optimálně nebo téměř optimálně (při správně vytvořených indexech v databázi). Naproti tomu třeba framework Laravel jde opačnou cestou – základ je pomalý a když chcete zrychlit, musíte napsat kód navíc.

Autor pracuje jako Software Engineer v týmu Gmail Security. V minulosti se zabýval především PHP, o kterém napsal knihu a podílel se na oficiální dokumentaci. Je autorem nástroje pro správu databáze Adminer. Poznámky si zapisuje na weblog PHP triky.

Komentáře: 10

Přehled komentářů

mhlavac Frameworky
Jakub Vrána Re: Frameworky
okbob trochu jiný přístup
Aleš Roubíček Re: trochu jiný přístup
Futrál Re: trochu jiný přístup
Futrál Re: trochu jiný přístup
Monty Jaké ošetření sloupce?
Jakub Vrána Re: Jaké ošetření sloupce?
bene Re: Automatické zabezpečení
5o ACL assertion
Zdroj: https://www.zdrojak.cz/?p=3773