Skip to content

OSTARA711/xss-lab

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

XSS Lab

Intentionally vulnerable static site used to test XSS scanners and manual testing techniques against DOM-based cross-site scripting patterns.

⚠️ This site is deliberately insecure. Every vulnerability here is intentional and exists for security testing and learning purposes only. Do not reuse this code in production.

Why this Lab exists?

Server-side reflected XSS test targets (like testphp.vulnweb.com) are plentiful, but static-site DOM XSS is a different attack surface: there's no backend to reflect input, so the vulnerability lives entirely in client-side JavaScript reading location.search or location.hash and writing it into the DOM unsafely.

This repository is a small, self-contained page with a few classic vulnerable patterns side by side with a fixed/safe equivalent, for comparison.

Vectors included

# Sink Source Notes
1 innerHTML ?q= query param Classic query-string DOM XSS
2 innerHTML # URL fragment Never sent to the server — purely client-side, invisible to server logs/WAFs
3 document.write ?name= query param Different sink behavior than innerHTML (can inject raw <script> tags)
4 textContent ?safe= query param Safe — same input, properly escaped, shown for comparison

Usage

Visit the deployed page and append a payload like:

https://xss-lab.pages.dev/?q=<img src=x onerror=alert(1)>

https://xss-lab.pages.dev/?name=<script>alert(2)</script>

https://xss-lab.pages.dev/#<img src=x onerror=alert(3)>

Dalfox

Or scan with Dalfox

dalfox scan "https://xss-lab.pages.dev/?q=<img src=x onerror=alert(1)>" --skip-discovery

This website is a good illustration of Dalfox's detection tiers. Since the DOM sinks here are written entirely by client-side JavaScript — never by the server — findings surface as [A] (static AST source→sink analysis), not [V]. [V] requires the payload to appear in a DOM tree parsed from the actual server response, which never happens for pure client-side DOM-XSS: the payload is only ever written into the page by JavaScript, after the browser has already received and rendered the (unchanging) static response. This is expected, structural behavior — not a scan failure — see Dalfox's Detection Model docs (added in hahwul/dalfox#1244) and the discussion below for the full explanation.

Findings & Discussion

This lab was used as a real test case in hahwul/dalfox#1238, which started as a reported inconsistency (--skip-mining-dom appearing to have no effect) and turned into a full clarification of Dalfox's detection architecture:

  • Dalfox has three independent subsystems: parameter mining (harvesting candidate param names from HTML), AST source→sink analysis (static parsing of inline JS to trace data flow from sources like location.search/location.hash to sinks like innerHTML/ document.write), and reflection + DOM verification (checking whether a payload appears in the real HTTP response). --skip-mining-dom only ever controlled the first of these — it was never the mechanism producing this lab's findings.

  • [V] (verified) does not mean a headless browser executed the payload — Dalfox v3.1.2 has no browser and no CDP client. It means the payload was matched in a DOM tree parsed from the real server response (via a CSS-selector-style check, or an executable scheme landing in a dangerous attribute). Still static analysis, just on a real response rather than the raw JS source.

  • Because this lab's vulnerabilities are all pure client-side DOM-XSS — written into the page only after the browser renders it — the payload never touches the actual server response bytes. That means [V] has structurally nothing to match here, and --only-poc v correctly returns zero findings on this site. This is by design, not a gap.

  • The issue directly led to hahwul/dalfox#1244, which fixed a misleading summary line (XSS found 0 XSS previously printed above real [A] findings), surfaced the AST-derived source→sink description in plain output instead of hiding it in JSON, fixed AST findings reporting - instead of the real parameter name, and added a permanent "Detection Model" documentation page defining what [V], [A], [R], and [I] actually mean.

Open follow-up: the discussion also led to hahwul/dalfox#1245, an open proposal for an opt-in headless verification pass (--headless-verify) that would actually load a finding's PoC URL in a real browser and promote genuinely-executing [A] findings to [V]. That is the real path from static inference to runtime confirmation for websites like this one — until it lands, treat every [A] finding from this lab (or any pure client-side DOM-XSS target) as something to confirm manually in a browser, not as a final verdict.

Deployment

This is a static website with no build step. Deployed via Cloudflare Pages directly from this repository.

License

MIT License, please check LICENSE

About

Intentionally vulnerable static website for testing DOM-based XSS detection tools, such as Dalfox. Educational and testing use only.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages