Last month, I created an XSS challenge on my sandbox domain and named it myClock. The page contains JavaScript code that checks if the clock has been paused for 3 seconds. There are multiple ways to solve the challenge, and I allowed user interaction to encourage creative solutions. Let’s get started with an explanation of the challenge.

Note: If you are here to see solutions only, jump to the bottom of the page.

Note 2: In this write-up, I will explain the intended solution only.

Introduction

Initially, the challenge is only a client-side JavaScript code:

<!DOCTYPE html>
<html>
<head>
    <title>My Clock v 1.0</title>
    <script src="https://cure53.de/purify.js"></script>
    <script src="http://sandbox2.ahussam.me/url.php?code=var Url='//ahussam.me';"></script>
    <style>
        #myClock {
            font-size: 4em;
        }
    </style>
</head>
<body>
    <h2>Rules</h2>
    <ul>
        <li>Execute <i>alert(domain)</i> on this page.</li>
        <li>Hint: your payload is going to be clicked at <b>XX:XX:01</b>.</li>
        <li>Only the latest browsers are supported.</li>
        <li>User interaction is allowed.</li>
    </ul>
    <center>
        <div id="myClock"></div>
        <div id="myComment"></div>
    </center>
    <h2>Solvers</h2>
    <ul>
        <li><a href="https://twitter.com/Abdulahhusam">YOU! Send a DM.</a></li>
    </ul>
    <script>
        var dirty = new URL(location).searchParams.get('comment');
        var cleanHTML = DOMPurify.sanitize(dirty, { FORBID_TAGS: ['a'] });
        document.getElementById('myComment').innerHTML = cleanHTML;
        var link = new URL(location).searchParams.get('link');

        if (link === "1") {
            var myURL = url || "https://ahussam.me";
            var newWindow = window.open(null, null, "toolbar=no,location=no,dialog=yes,personalbar=no,status=no,dependent=yes,menubar=no,resizable=yes,scrollbars=no");

            if (newWindow) {
                newWindow.opener = null;
                newWindow.location = myURL;
            }
        }

        function updateTime() {
            var time = new Date();
            var hours = time.getHours();
            var minutes = time.getMinutes();
            var seconds = time.getSeconds();
            var clock = document.getElementById('myClock');

            if (clock.innerHTML.indexOf(':') > -1) {
                var sec = clock.innerHTML.split(':')[2];
                if (parseInt(sec) + 3 < seconds) {
                    if (window.opener) {
                        exit();
                    } else {
                        location = location.hash.substr(1);
                    }
                }
            }

            clock.innerHTML = placeHolder(hours) + ":"  + placeHolder(minutes) + ":" + placeHolder(seconds);

            function placeHolder(input) {
                if (input < 10) {
                    return '0' + input;
                } else {
                    return input;
                }
            }
        }

        document.addEventListener("DOMContentLoaded", function() {
            window.setInterval(updateTime, 100);
        });
    </script>
</body>
</html>

I used DOMPurify to sanitize the comment before injecting the content into the DOM, which is safe unless @SecurityMB breaks it. I only forbade the anchor tag:

FORBID_TAGS: ['a']

The code is a very basic clock that checks if the DOM (clock) has been paused or not. In case it is paused for more than 3 seconds, it will set the location to the location.hash (sink). So the main idea is to freeze the DOM for a while since the payload will be clicked at XX:XX:01.

Technical Details

To begin with, we must find a way to freeze the DOM. I was expecting some new tricks to be submitted, and I was right! At the page head, we can find a way to XSS sandbox2.ahussam.me.

However, we must XSS sandbox.ahussam.me, so let’s put that in our notes:

  • We can XSS sandbox2.ahussam.me.

If you try to XSS sandbox2.ahussam.me, there is a simple WAF that could be bypassed very easily. It wasn’t part of the challenge at the planning phase, but I expect you to bypass it though!

Another interesting thing is that the url variable isn’t defined because JavaScript is case-sensitive, so Url isn’t the same as url. Add this to our notes:

  • url isn’t defined.

Let’s put them all together:

  • We must freeze the DOM for 3 seconds.
  • We have an XSS on sandbox2.ahussam.me.
  • We must define url in sandbox.ahussam.me. We can define it using DOM Clobbering.

Well, the challenge has been solved at this point! How? Say welcome to Site-Isolation.

Chrome started using Multi-process Architecture to prevent a crashing tab to hang the whole browser’s process and to create a secure context to each tab so they don’t share the same thread. This could take a lot of time explaining the Process-Thread-Task on the operating systems. I will share resources about them. Let me quote from Chromium:

To support a site-per-process policy in a multi-process web browser, we need to identify the smallest unit that cannot be split into multiple processes. This is not actually a single page, but rather a group of documents from the same website that have references to each other. Such documents have full script access to each other’s content, and they must run on a single thread, not concurrently. This group may span multiple frames or tabs, and they may come from multiple sub-domains of the same site. So, a SiteInstance of sandbox.ahussam.me and sandbox2.ahussam.me is running on a single thread! A JavaScript code on sandbox2.ahussam.me can block the JavaScript on sandbox.ahussam.me! If we create iframes to sandbox.ahussam.me and sandbox2.ahussam.me on a page, they will share the same thread. The intended solution is the following:

Host it on your server:

var t = Date.now(); 
while (Date.now() - t < 4000) {
    nop();
}
function nop() {}

Steps

  1. Since there is no X-Frame-Option in both pages, we can open them with iframes.
  2. We block the JavaScript thread with a No-Operation loop.
  3. The DOM of sandbox.ahussam.me will be blocked, and when the 4 seconds pass, the DOM clock will be late by more than 3 seconds.
  4. The JavaScript location window will be set to location.hash. Use the challenge page to understand how to reproduce. BTW, there is a way to solve this with window.open that you can check out here.

Solvers

  • RootEval
  • RenwaX23
  • MichaB Bentkowski
  • Guilherme Keerok
  • Alex

Conclusion

We had fun and learned new tricks to freeze the DOM and playing around with site-isolation.

AVideo < 8.9 Privilege Escalation and File Inclusion that led to RCE

In this article, we will cover security issues in the **AVideo** open-source project that led to RCE. We contacted the project manager, a...… Continue reading

Careem AWS S3 Bucket Takeover

Published on June 01, 2020

Moodle DOM Stored XSS to RCE

Published on May 25, 2020