March 2023 - Intigriti's XSS Challenge - Author Writeup

Due to time constrains, I was not able to deliver the write-up for my challenge when it ended, but plenty of people decided to share how they shared it. So, I highly recommend checking their write-ups for more detailed write up!
https://ltsirkov.medium.com/intigriti-challenge-0323-solution-1141ef0fda6a
https://github.com/h43z/writeups/blob/master/0323-intigriti.md
https://medium.com/@antonio341375/intigriti-challenge-0323-writeup-5ba6a09ee951
https://github.com/abishekvashok/writeups/blob/main/intigriti/challenge-0323/WRITE_UP.md
https://z4ki-blog.vercel.app/blog/intigriti-monthly/challenge-0324
AI Generated Summary
Goal: Steal the adminās flag (stored in a note) in spite of strict CSP and sanitization.
Recon & Source-Code Review
⢠You can create notes via the /note/:id?id=<id> endpoint; content is sanitized with DOMPurify.
⢠A special /debug/<authorāID>/<noteāID> endpoint returns raw note content in text/html only when the custom header mode: read is setāotherwise returns 404.
Client-Side Path Traversal to Access Debug Endpoint
⢠The frontend fetches /note/<id>, with id coming from URL parametersāand always includes the header mode: read.
⢠By using directory-traversal (e.g. ?id=../debug/<authorāID>/<noteāID>), you can force the browser to request the debug path with correct header even though the UI makes it impossible normally.
CSP Bypass via 404-based Script Injection
⢠A strict CSP prevents inline scripts and external untrusted sources.
⢠But the custom 404 page reflects paths unsanitizedāwith ability to inject <script src=...> tags into error message via path encoding tricks.
⢠By inserting a crafted path containing JavaScript in the error message (e.g. ending in /...;alert(...)), you can bypass CSP and cause script execution.
Exploitation via Browser Cache (bfcache / disk cache) ā (Author Note: This part was inspired by ArkArkās writeup)
⢠The debug response is fetched with text/html, which can be stored in browser cache (e.g. disk cache).
⢠By navigating (e.g. using window.open) to the pathātraversal note endpoint, then using history.back(), the previously fetched debug response is loaded without sanitization and exec context persistsātriggering the injected script.
Final Attack Chain Overview
1. Submit a note with payload or trigger stored content indirectly via path traversal.
2. Trigger fetch to /debug/... through traversal path (with forward history), which stores the raw HTML response in cache.
3. Navigate back to cached view, where CSP bypass path injection executes JSārevealing the flag.



