xssmaze
XSSMaze is an intentionally vulnerable web application for measuring and improving XSS detection in security testing tools. It covers a wide range of XSS contexts: basic reflection, DOM, header, path, POST, redirect, decode, hidden input, in-JS, in-attribute, in-frame, event handler, CSP bypass, SVG, CSS injection, template injection, WebSocket, JSON, advanced techniques, polyglot, browser-state, opener, storage-event, stream, channel, service-worker, history-state, reparse, referrer, jQuery DOM sinks, dynamic code/module execution sinks, client-state (web storage/cookie/history) sinks, and async fetch/XHR API DOM sinks.

Installation
From Source
shards install
shards build
./bin/xssmaze
From Docker
docker pull ghcr.io/hahwul/xssmaze:main
docker run -p 3000:3000 ghcr.io/hahwul/xssmaze:main
Usage
./bin/xssmaze
Options:
-b HOST, --bind HOST Host to bind (defaults to 127.0.0.1; pass 0.0.0.0 to expose on the network)
-p PORT, --port PORT Port to listen for connections (defaults to 3000)
-s, --ssl Enables SSL
--ssl-key-file FILE SSL key file
--ssl-cert-file FILE SSL certificate file
-h, --help Shows this help
Dynamic Security Headers (Query Params)
For calibrating scanners against different defensive configurations, XSSMaze can override common security headers per-request via URL query parameters (works on any endpoint).
set_csp: setsContent-Security-Policy(URL-encode spaces/quotes)set_xcto: setsX-Content-Type-Options(e.g.nosniff)set_xfo: setsX-Frame-Options(e.g.DENY)
Examples:
curl -i "http://localhost:3000/basic/level1/?query=a&set_xcto=nosniff"
curl -i "http://localhost:3000/basic/level1/?query=a&set_xfo=deny"
curl -i "http://localhost:3000/basic/level1/?query=a&set_csp=default-src%20%27self%27"
Endpoint Map
curl http://localhost:3000/map/text # newline-separated URLs
curl http://localhost:3000/map/json # full metadata (also: ?type=dom or ?q=csp)
curl http://localhost:3000/map/markdown # markdown table
curl http://localhost:3000/map/categories # categories with counts
curl http://localhost:3000/map/openapi # OpenAPI 3.0 catalog
curl http://localhost:3000/sitemap.xml # sitemap of all maze paths
curl http://localhost:3000/health # liveness probe
curl http://localhost:3000/version # version + counts
curl -L http://localhost:3000/random # 302 to a random maze
The index page (/) provides a client-side filter, per-category counts, and links to all of the maps above. Map endpoints serve a payload that is built once at startup, cached, and gzip pre-compressed (Accept-Encoding: gzip cuts the index payload by ~85%), so they're safe to poll from tooling.
XS-Leaks (Cross-Site Leaks)
XS-Leaks are cross-origin side-channels that let an attacker infer state-dependent data without directly reading the response body. XSSMaze includes xsleak-* levels that intentionally vary response size, subresource composition, load/error behavior, timing, and redirect chains based on a "secret" state.
The state can be controlled either by:
q=admin(simple stateless demos for scanners), or- the
xsleak_role=admincookie (set viaGET /xsleak/login?as=admin).
Levels
GET /xsleak/search?q=admin(xsleak-level1): body-size oracle (admin returns more HTML/results)GET /xsleak/frame?q=admin(xsleak-level2): frame-count oracle (admin includes more iframes)GET /xsleak/avatar.gif?q=admin(xsleak-level3): load/error oracle (admin returns an image, guest is 404)GET /xsleak/timing?q=admin(xsleak-level4): timing oracle (guest path sleeps longer)GET /xsleak/redirect?q=admin(xsleak-level5): redirect-chain oracle (admin has more hops)
Measuring side-channels
To validate dynamically, host an "attacker" page on a different origin and probe the victim endpoints using load/error handlers and timing:
<script>
// Load/error oracle (200 vs 404)
const img = new Image();
img.onload = () => console.log("loaded");
img.onerror = () => console.log("error");
img.src = "http://127.0.0.1:3000/xsleak/avatar.gif?q=admin";
// Timing oracle (measure duration)
const t0 = performance.now();
fetch("http://127.0.0.1:3000/xsleak/timing?q=admin", { mode: "no-cors" })
.finally(() => console.log("ms:", performance.now() - t0));
// Frame-count oracle (browser-dependent)
const f = document.createElement("iframe");
f.src = "http://127.0.0.1:3000/xsleak/frame?q=admin";
f.onload = () => console.log("subframes:", f.contentWindow.length);
document.body.appendChild(f);
</script>
You can also spot differences via CLI:
curl -i "http://localhost:3000/xsleak/avatar.gif?q=guest" # 404
curl -i "http://localhost:3000/xsleak/avatar.gif?q=admin" # 200 image/gif
curl -s "http://localhost:3000/xsleak/frame?q=guest" | wc -c
curl -s "http://localhost:3000/xsleak/frame?q=admin" | wc -c
curl -sL -o /dev/null -w "%{time_total}\n" "http://localhost:3000/xsleak/timing?q=guest"
curl -sL -o /dev/null -w "%{time_total}\n" "http://localhost:3000/xsleak/timing?q=admin"
Benchmarking Scanner Tools
XSSMaze includes an automated benchmark tool to measure how well XSS scanners perform against the lab's diverse vulnerability scenarios. The tool retrieves all endpoints from /map/json, runs scanner tools against them, and generates a detection scorecard.
Requirements
- Python 3.x
requestslibrary (pip install requests)- Scanner tools (e.g., Nuclei)
Quick Start
# Start XSSMaze (in one terminal)
./bin/xssmaze -b 0.0.0.0
# Run benchmark (in another terminal)
cd scripts
./benchmark.sh http://localhost:3000
Usage Examples
# Basic benchmark with console output
python3 benchmark.py http://localhost:3000
# Verbose mode (shows detailed progress)
python3 benchmark.py http://localhost:3000 -v
# Generate markdown report
python3 benchmark.py http://localhost:3000 -o report.md
# Run specific scanner only
python3 benchmark.py http://localhost:3000 --scanner nuclei
# Use a custom scanner command
python3 benchmark.py http://localhost:3000 \
--custom-scanner "myxss {URL}" \
--custom-scanner-name "MyXSSScanner"
Output Format
The benchmark tool provides:
- Console Scorecard: Summary table showing detection rates for each scanner
- Detailed Statistics:
- Total registered endpoints
- Endpoints successfully detected (True Positives)
- Endpoints missed (False Negatives)
- Overall detection rate percentage
- Breakdown of missed detections by category
- Markdown Report (optional): Exportable report with full benchmark results
Example output:
======================================================================
XSSMaze Scanner Benchmark Results
======================================================================
Target: http://localhost:3000
Total Registered Endpoints: 450
Categories: 45
Scanner Detected Missed Rate Time (s)
----------------------------------------------------------------------
Nuclei 234 216 52.0% 125.3s
--- Nuclei Detailed Results ---
✓ True Positives: 234/450
✗ False Negatives (Missed): 216/450
Missed by category:
advanced: 6/6 missed
csp-bypass: 5/5 missed
template-injection: 6/6 missed
Supported Scanners
The benchmark tool currently supports:
- Nuclei: Uses XSS-related templates from the Nuclei template library
- Custom Scanners: Any tool that can accept a URL and output results
Adding New Scanners
To add support for a new scanner:
- Use the
--custom-scanneroption with command template - Or extend
benchmark.pywith a new scanner method
Example for adding a scanner permanently:
def run_my_scanner(self) -> ScannerResult:
# Implementation here
pass