Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

XSS Lab

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

⚠️ This website 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 does this Lab exist?

Server-side reflected XSS test targets (like testphp.vulnweb.com) are plentiful, but a static website's DOM XSS is a different attack surface: there is 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 webpage with a few classic vulnerable patterns side by side with a fixed (or 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 useful illustration of how Dalfox v3 actually classifies DOM-based findings. The sinks here are written entirely by client-side JavaScript — never by the server — so findings surface as [A] (source→sink flows identified through static analysis of JavaScript), rather than [V] or [R].

A precise distinction, confirmed directly by Dalfox's maintainer: [A] still requires fetching a real HTTP response, since Dalfox analyzes the JavaScript contained in that response (and external bundles too, if --analyze-external-js is used). The actual difference between [A] and most [V]/[R] findings is not "was there a request," but whether the payload itself needs to appear in the response. Request-based [V]/[R] findings generally involve the payload showing up there in some form; [A] findings don't need to, since they're identified by tracing the code's logic rather than matching the payload's literal presence. (One exception: out-of-band [V] findings — blind XSS confirmed via a callback — don't require the payload in the response either, since the callback itself is the evidence. That mechanism doesn't apply to this lab, which has no out-of-band sinks, but it's worth knowing "the payload must appear in the response" isn't a universal rule across every [V] case.)

[V] findings themselves are supported by several kinds of evidence — including marker selectors, executable schemes landing in dangerous attributes, injected elements with attached sink handlers, AST-scoped sink calls found by inspecting the response's own script content, and inline-handler breakouts — plus the out-of-band callback mechanism noted above. None of this involves a browser: Dalfox v3 has no headless browser and no CDP client, by deliberate design — dropping that dependency was one of the core changes from v2 to v3, and the project's maintainer has stated there are no plans to reintroduce it.

Because every vulnerability on this page is written into the DOM only after the browser has already received and rendered a static, unchanging response, the payload never appears in the actual HTTP response bytes. That's why this site's findings surface as [A] rather than [V]/[R], and why --only-poc v correctly returns nothing here — not because the scan failed, but because none of the evidence types above have anything to match on a page whose vulnerability exists entirely after the point where Dalfox reads the response.

Scripting note: Dalfox now attaches a detection_method field to each finding (e.g. ast, reflection, dom-verification, oob) alongside the existing [V]/[A]/[R]/[I] tier. The tier lettering is expected to change as Dalfox's finding model evolves further, but detection_method == "ast" is intended to remain a stable selector for this category of finding through that transition — worth using instead of the tier letter if you're scripting against this lab's output long-term.

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 on this site's results) and became a detailed clarification of Dalfox's detection architecture, directly from its maintainer:

  • Dalfox has three independent subsystems: parameter mining (harvesting candidate parameter names from HTML attributes), AST source→sink analysis (tracing data flow from sources like location.search/location.hash to sinks like innerHTML/document.write within a fetched response's JavaScript), and reflection/DOM-based classification (evaluating whether and how a payload appears in that response, or — for blind XSS — whether an out-of-band callback confirms it). --skip-mining-dom only ever controlled the first of these — it was never the mechanism producing this lab's findings, which is why the flag appeared to have no effect in the original report.
  • [V] does not mean a browser executed the payload — Dalfox has no browser and no CDP client. It means the available evidence (several categories of it, from marker matching to AST inspection of the response's own scripts to out-of-band callbacks) was judged sufficient to classify the finding as vulnerable.
  • The distinction that actually matters, per Dalfox's maintainer, is not "static vs. dynamic" — it's how much confidence the available evidence supports. This lab's findings land at [A] because their vulnerability is written into the DOM only by client-side JavaScript, after the page's static response has already been fully received.
  • The issue directly led to hahwul/dalfox#1244, which shipped a fix for a misleading summary line (XSS found 0 XSS previously printed above real [A] findings with no indication they existed), surfaced the AST-derived source→sink description in plain terminal output instead of hiding it in JSON only, fixed AST findings reporting a placeholder - instead of the real parameter/source name, and added a permanent "Detection Model" documentation page defining what [V], [A], [R], and [I] mean.
  • The discussion also led to hahwul/dalfox#1246, merged, which added explicit detection_method and confidence fields to every finding, separating "how was this found" from "how confident is Dalfox that it's real." As of this merge, the type field (and the [A] tier itself) is unchanged — the new fields currently preview where each finding is expected to land once a future migration completes, but nothing about this lab's [A] classification has changed yet.

An earlier open proposal in the same thread (hahwul/dalfox#1245) suggested an opt-in headless verification pass. Dalfox's maintainer has since clarified there are no plans to build it: dropping a headless browser dependency was a deliberate, core design decision made moving from v2 to v3, not a gap awaiting a future fix. If you're evaluating any DOM-XSS finding — from this lab or elsewhere — that isn't backed by a [V]/[R] classification, treat it as something to confirm manually in a real browser, since that confirmation step is expected to remain manual by design, not a temporary limitation.

Deployment

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

License

MIT License, please read 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