$ cat writeup.md…
$ cat writeup.md…
gpnctf
Task: XSS on a voting page protected by DOMPurify 3.4.7 with reCAPTCHA and GLightbox loaded; bot sets flag cookie and visits attacker URL. Solution: chain reCAPTCHA data-error-callback auto-fire gadget with GLightbox video innerHTML sink and page's random() click function to achieve no-interaction XSS bypassing DOMPurify.
$ cat /etc/rate-limit
Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.
We hosted a cookoff please vote. The best dish will be added to the menu
A static voting page ("Karlsruhe Cook-Off") is served at http://localhost:1337. It accepts a ?shareText= query parameter, sanitizes it with DOMPurify.sanitize() (v3.4.7, default config), and injects the result into #shared-text.innerHTML. The page loads three external libraries: GLightbox (lightbox/gallery), DOMPurify 3.4.7 (HTML sanitizer), and Google reCAPTCHA (api.js async defer).
A Playwright bot (admin.js) sets a cookie "flag" + FLAG on localhost:1337, then navigates to an attacker-provided URL (must start with http://localhost:1337), waits 10 seconds, and closes. The bot does not click anything. No CSP is set.
The goal is to achieve XSS to exfiltrate document.cookie.
const shareText = new URLSearchParams(location.search).get("shareText"); if (shareText !== null) { document.getElementById("shared-section").hidden = false; document.getElementById("shared-text").innerHTML = DOMPurify.sanitize(shareText); }
DOMPurify 3.4.7 with default config strips all event handlers, <script>, <iframe>, <object>, <base>, <meta>, SVG event attributes, and dangerous URI schemes. However, it preserves class and all data-* attributes on standard HTML elements. This is the key to the exploit.
http://localhost:1337 — payload goes in ?shareText=The page defines/loads these callable window functions:
GLightbox() — re-initializes the lightbox libraryrandom() — clicks a random input[name="vote"] elementDOMPurify — the sanitizergrecaptcha — reCAPTCHA object (methods are dotted, not directly callable)Gadget 1: reCAPTCHA data-error-callback (auto-fire primitive)
...
$ grep --similar