myClock XSS Challenge Solution Write-Up
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 consists only of 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 will 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) {
return input < 10 ? '0' + input : 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, making it secure—unless @SecurityMB finds a way around it! The only forbidden tag is:
FORBID_TAGS: ['a']
The code is a basic clock that checks whether the DOM (clock) has been paused. If it is paused for more than 3 seconds, it sets the location
to location.hash
. The main idea is to freeze the DOM while waiting for the payload to be triggered at XX:XX:01.
Technical Details
To begin, we must find a way to freeze the DOM. I expected some creative solutions, and I was right! Upon inspecting the page head, it was possible to XSS sandbox2.ahussam.me
.
However, the real target was sandbox.ahussam.me
, so we noted:
- We can XSS
sandbox2.ahussam.me
.
If you try to XSS sandbox2.ahussam.me
, there is a simple WAF that can be bypassed. This wasn’t part of the original challenge, but I expected participants to bypass it!
Another interesting detail is that the url
variable isn’t defined because JavaScript is case-sensitive, and Url
is not the same as url
. Adding this to our notes:
url
isn’t defined.
Putting Everything Together:
- We must freeze the DOM for 3 seconds.
- We have an XSS vulnerability on
sandbox2.ahussam.me
. - We need to define
url
insandbox.ahussam.me
using DOM Clobbering.
Welcome to Site Isolation
Chrome uses multi-process architecture for security and stability. It ensures that different tabs and subdomains do not share the same thread. However, documents from the same site that reference each other must run on the same thread.
This means sandbox.ahussam.me
and sandbox2.ahussam.me
share a single thread! By blocking JavaScript execution on sandbox2.ahussam.me
, we also block it on sandbox.ahussam.me
.
The intended solution is as follows:
Host the following payload on your server:
var t = Date.now();
while (Date.now() - t < 4000) {
nop();
}
function nop() {}
Steps to Exploit
- Since there is no
X-Frame-Options
header, open both pages in<iframe>
elements. - Block the JavaScript thread with a No-Operation (NOP) loop.
- The DOM in
sandbox.ahussam.me
will be frozen, causing the clock to lag by more than 3 seconds. - The script will execute, setting
window.location
tolocation.hash
.
You can try an alternative solution using window.open
, detailed here.
Solvers
- RootEval
- RenwaX23
- MichaB Bentkowski
- Guilherme Keerok
- Alex
Conclusion
This challenge demonstrated techniques to freeze the DOM, manipulate site isolation, and exploit JavaScript execution in different security contexts.
I hope you enjoyed this write-up and learned something new!