Skip to main content
Cross-site scripting challenges are a staple of web CTF categories because they mirror one of the most prevalent vulnerabilities in real-world applications. In a CTF context, XSS almost always means one thing: there’s a bot — a headless browser running as an admin — visiting pages you influence, and your job is to steal its cookie or force it to perform an action that reveals the flag. This writeup walks through a challenge that chains all three XSS variants to ultimately exfiltrate an admin session cookie containing the flag.

Challenge Background

The challenge hosts a simple message board at http://target.ctf. Users can post messages, and an admin bot periodically visits every new post to “moderate” it. The flag is stored in the admin’s session cookie. Your goal is to craft a payload that causes the admin bot to send its cookie to a server you control. You have access to http://attacker.com (your exfiltration server) where you can receive incoming HTTP requests and read their query parameters.

Identifying XSS Injection Points

Before crafting any payload, map every location where user-supplied input is reflected in the page. Submit the string xsstest123 into every input field and search for it in the rendered HTML source. Look for it appearing:
  • Unquoted in an HTML attribute: <input value=xsstest123>
  • Inside a JavaScript string: var q = "xsstest123";
  • As raw HTML content: <p>xsstest123</p>
  • Inside an HTML attribute value: <input value="xsstest123">
Each reflection context requires a different payload structure. Knowing the context before you write your payload is the difference between a one-shot solve and hours of confused trial-and-error.

XSS Variants

Reflected XSS occurs when your input is immediately echoed back in the response without being stored. The classic vector is a search or error parameter in the URL.On the message board, the search endpoint at /search?q= reflects your query directly into the page:
Response HTML:
The reflection is inside an HTML paragraph with no encoding — a perfect injection point. Test with the most basic payload first:
Navigating to /search?q=<script>alert(1)</script> triggers an alert box, confirming execution. However, reflected XSS requires the victim to click a crafted link — it won’t hit the admin bot, which only visits post URLs. Use this as proof-of-concept and move to stored XSS for the actual exploit.

Exploitation Chain

1

Confirm the Injection Context

Submit "><h1>test into the post body. If the rendered page shows a large heading and the HTML structure breaks, the input is reflected raw inside an HTML attribute or element without encoding. Identify exactly where in the DOM your input lands before choosing a payload.
2

Test Basic Script Execution

Start with the simplest possible proof-of-concept. If <script> tags are allowed:
If scripts are filtered but HTML attributes are not, fall back to event handlers:
If angle brackets are filtered but you’re inside a JavaScript string context (e.g., var q = "INPUT"), break out of the string:
Confirm execution before adding complexity. An alert popup tells you JavaScript runs — everything after this is just payload refinement.
3

Set Up Your Exfiltration Server

You need a server that logs incoming requests. For CTF purposes, use one of these options:
Replace http://attacker.com in all payloads with your actual exfiltration URL. Test it by visiting the URL yourself to confirm it’s reachable.
4

Craft the Cookie Theft Payload

With execution confirmed and your server ready, deploy the full exfiltration payload as a stored post:
If the document.location redirect approach is too disruptive (the bot might navigate away before the request completes), use a more reliable async approach:
Or use an image tag for maximum compatibility across bot environments:
5

Wait for the Admin Bot and Capture the Flag

Submit the post, then monitor your exfiltration server. Most CTF bot challenges run the admin visit on a 30–60 second cycle. When the bot visits your post, your server logs the request:
The flag is the value of the session cookie. Submit it to the challenge platform.

CSP Bypass Techniques

A Content Security Policy can block inline scripts and restrict which domains JavaScript may contact. If your payload is blocked, check the CSP header first:
A common CTF CSP looks like:
This blocks inline <script> tags and onerror handlers. Common bypasses: 1. Whitelisted CDN abuse. If a CDN like cdn.jsdelivr.net is whitelisted, load a JavaScript file hosted there:
2. JSONP endpoints. If a whitelisted domain has a JSONP callback endpoint, use it to inject arbitrary code:
3. unsafe-inline with a nonce. If the CSP uses nonces and you can read the nonce value from a reflected script tag elsewhere on the page, include it in your injection. 4. Dangling markup for CSP bypass with img-src. When scripts are fully blocked but img-src * is allowed, use an <img> tag with a data-exfiltrating src attribute to leak page content character by character.
When the CSP blocks your exfiltration domain, check whether connect-src permits * or specific domains. If img-src is set to * but connect-src is restrictive, use <img src="..."> instead of fetch() for exfiltration — image loads bypass connect-src restrictions.

Key Takeaways

  • Identify the reflection context first. A payload that works inside <p> tags will fail inside a JavaScript string or an HTML attribute — tailor your syntax to the context.
  • Stored XSS wins bot-based CTF challenges. Reflected XSS requires user interaction; stored XSS fires automatically when the admin bot visits.
  • onerror and onload event handlers are your fallback when <script> tags are filtered.
  • Set up your exfiltration server before deploying the payload. There’s nothing worse than a successful cookie theft you can’t receive.
  • Always check the CSP header. A well-configured policy forces you to be creative, but almost every CTF CSP has at least one bypass vector.
XSS exploitation outside of an explicitly authorized CTF environment or your own systems is illegal and unethical. The techniques in this writeup are for educational purposes and authorized competition use only. Always confirm scope before testing any vulnerability.
Last modified on July 23, 2026