Link Search Menu Expand Document

Write-up

Looking at the challenge page, we see a helpful text claiming that the html HTTP parameter is used to set the displayed text in the page.

Trying to specify a custom text for the html parameter and testing for HTML injection (let’s inject <a href="#">):

Observe that the additional text that were originally below are gone. Furthermore, notice that the injected <a> tag appears nested within a <h1> tag.

Because this challenge has CSP:

<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-eval' 'strict-dynamic' 'nonce-1b5da4bc13f845058faff87ef9e13ccc'; style-src 'nonce-8db3c84b276004e62513b91128fb3794'">

We are unable to inject arbitrary <script> or trigger any event handling functions (such as onerror=alert()) as it requires a valid nonce.

Time to look at the JS source on the page then.


The only code that stood out in the page was the following:

<script nonce="1b5da4bc13f845058faff87ef9e13ccc">
    window.addEventListener("DOMContentLoaded", function () {
        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>

It appears that this script triggers a function once all DOM content has loaded. We see that the variable e is set to )]}' concatenated with any value that we specify in the xss HTTP parameter. Then, the variable c is set to the last HTML element within the <body> tag. By default, this sets it to the <div> container (the one with the neon INTIGRITI letters).

We see that the “id” of c is checked to see if it equals to “intigriti” before it executes a few more lines of code. Since the “id” of c is currently “container”, the condition will not be met. Finally, s is set as a <script> tag which will append the contents of e as a child within itself.

Setting the HTTP parameter xss to be -alert(1) since we are inside a <script> tag:

Fails since the there are the random )]}' characters at the start of the line.

Looking at the if block in the code:

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;
}

If we can find a way to enter the loop, the code will take the last 4 characters of the last child in c and prepend it to e. This means that we can close the meddlesome single quote.

It is time to combine our findings.


Back to the html HTTP parameter, it appears that we are able to inject a closing </div> tag in order to escape from the current <div> container.

?html=</div>hello results in:

The </h1> tag is automatically inserted by the browser to conform to a valid HTML structure.

What happens if we insert additional HTML elements after closing?

?html=</div><a>hello results in:

Notice that the <div id="container"> is now a child of the injected <a> tag.

Once again, this is due to the browser mutating the HTML elements to ensure that it conforms to the standard. Since <a> is opened and </a> is not found, when the browser detects new opening tags <div>, it will be considered as a child of <a>.

We have now successfully controlled the last element within the <body> tag. Time to satisfy the if condition clause by setting id=intigriti. Set the payload to ?html=</div><a id=intigriti>hello, a breakpoint in the source within the if block and confirm that execution reaches within it:

We can now control the variables l i f e to ensure that the )]}' can be negated. To do so, we just have to insert two nested child elements in which the inner-most child has its 4th last character as a single-quote '. This results in the output becoming: ')]}'.

Did you know: single-quotes can be part of a HTML tag name?

Let’s try the payload: ?html=</div><a id=intigriti><b><c'de>hello

The mutation done by the browser will automatically close the child tags, which means that the inner-most tag <c'de> will be closed with </c'de> and the 4th last character is '.

We have managed to close the single quote. It is time to insert the xss HTTP parameter back and tidy up the current html parameter payload. The final payload will thus be:

?html=</div><a><b><c'de>&xss=-alert(document.domain)

Or check it out here

Finally, the XSS is triggered at long last: