Intigriti October 2021 XSS challenge explained

Mr Homer
5 min readOct 30, 2021

--

In this blog post, I explain how I solved the Intigiti October XSS challenge and what lessons I learned from it. This is not a quick write-up of the challenge, but I aim to explain step by step how I figure it out and the technique behind each step. This is my first post in Medium, so if there is any issue with it, please feel free to share your thoughts in the comments :)

The challenge page

TL;DR

https://challenge-1021.intigriti.io/challenge/challenge.php?html=</h1></div><div%20id=”intigriti”><div%20id=”last”><test%27qq><script>&xss=;alert(document.domain)

The challenge and first steps

As with all intigriti XSS challenges, the landing page has a list of instructions on it. However, the real challenge is on the iframe inside the page.

The first things I look for in this kind of challenge are possible sources and sinks that could lead to the DOM XSS. In this case, it is easy to spot the 2 sink/sources:

  1. The challenge text itself tells you the following: “I USE ?html= TO CONVEY THESE MESSAGES”. Thus, the value of the html query variable is injected into the HTML of the page.
  2. Looking at the HTML code, we can see a JavaScript tag with the following code:

In line 2 the source is the query parameter xss and, in line 13, the sink is appending a script tag to the body.

Trying (and failing) to exploit each source/sink separately

In theory, exploiting the first source/sink should be easy, it is just a classic reflected XSS: ?html=<img%20src=1%20onerror=alert(1)> Nevertheless, the content security policy (CSP) is there to block us…

Content security policy error

I will not go into the CSP of the challenge as it is not bypassable

The second source/sink looks more promising. If I re-write the code to understand it better, we have the following:

To sum up the code snippet, at the end, the injected script tag will look like this: <script>jsPrefix)]}'xss</script> , being jsPrefix the variable of the code with the same name and xssthe query parameter we control.

The problem exploiting this is that, by just entering ?xss=;alert(1) we get the following JS code: )]}';alert(1) which triggers a syntax error and therefore nothing is executed.

We need to do something in order to make the jsPrefix not empty and look something like '___ . This way, the syntax error is avoided as all the parenthesis will now be part of a string '___)]}';alert(1).

For that, I have to take a step back to explain how browsers deal with unclosed tags. Believe me, it will all make sense in the end.

How do browsers deal with unclosed tags?

This is the main outcome of this challenge, understanding how unclosed tags are handled in web browsers :)

In general, unclosed tags that cannot be self-closed (like div, h1, form…) will be closed by the browser before they get out of their parent. For example:

<div>
<div>
<h1>title</h1>
</div>

The second div is closed before the first div, as it's its parent.

<div>
<div>
<h1>title</h1>
</div>
</div>

However, there are two (that I know of) HTML tags that are not treated the same: script and style. Because what they contain is JavaScript or CSS code, when they are left unclosed, the rest of the HTML of the page is considered as part of their contained code. The following example is self-explanatory:

<html><body>
<div>
<script>
</div>
</body></html>

The browser will render the following:

<html><body>
<div>
<script>
</div></body></html>
</script>
</div>
</body></html>

Notice how everything between the two script tags is treated as JavaScript code. The rest of the HTML tags (div, body, html) are also closed outside the script tag, following the first rule of unclosed tags.

Solving the challenge

Now that we have this knowledge, we can solve the challenge.

Going back to the JS code I presented before, we need to somehow control the content of jsPrefix . After reading the code line by line, we conclude we need to do the following:

  1. Make the last element of the HTML body have the id intigriti.
  2. From this intigriti element, we take its last child element again.
  3. Finally, the jsPrefix variable will be the last 4 characters of the HTML of the element in point 2.

The initial HTML code we have to work with, after some simplification, is the following:

The first thing to be done is to close the previous div and h1 to escape from them: ?html=</h1></div>

Now, in order to control the last element of the page, we open a new div which will become the last element of the page:?html=</h1></div><div id="intigriti">. After this we have achieved objective #1:

The next step is to control the last element of the div intigriti . For that, we will use the magic of the script tag to contain all the remaining HTML inside it. This will make this script tag the last element of the div intigriti, unless we add another div in front of it: ?html=</h1></div><div id="intigriti"><div id="last"><script>.

Objective #2 is now achieved. We control div last:

Finally, we just need to control the last 4 characters of the HTML code inside the div last (objective #3). This means the following: ?html=</h1></div><div id="intigriti"><div id="last"><test'qq><script>

The final solution is:
?html=</h1></div><div id="intigriti"><div id="last"><test'qq><script>&xss=;alert(document.domain)

Let’s go!

Conclusion

Doing these intigriti XSS challenges (every month) is really fun and it always makes you learn a new technique or browser behavior. This challenge was great for finding out how unclosed tags are handled by web browsers.

For those who did the challenge and have the same doubt I had: Any HTML code outside the body tag is inserted inside and at the end of it.

Thanks for reading!

--

--