Skip to content

Repository files navigation

XSSveil

License: MIT Python Status

XSSveil — an open-source blind XSS tool built on interact.sh. Sprays per-field tokenized payloads whose out-of-band callbacks go to interact.sh, then correlates each callback back to the exact injection point and the vulnerable URL where it executed.

How injection point + vulnerable URL are detected

interact.sh correlates a session by the first 20 chars of the subdomain. Everything after that is ours, so each input field gets a unique token embedded right after the prefix:

<20-char-session-id><field-token>.oast.fun
  • Injection point — the field token is in the hostname, so it survives even DNS-only callbacks (script/img blocked). We strip the 20-char prefix → token → DB lookup → "ticket_subject on /support/new-ticket".
  • Vulnerable URL — the JS beacon exfils location.href in the HTTP path, so the UI shows where it actually rendered (e.g. admin.target.com/tickets/8823).

The dashboard joins both: where you injected ↔ where it executed.

Payload contexts

15 context-specific templates (HTML body, quoted/unquoted attributes, JS strings, template literals, <script> breakout, href javascript:, SVG, iframe srcdoc, markdown, DNS-only, and a polyglot). Spraying multiple contexts per field raises true positives. Each context gets its own token.

Run

python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
.venv/bin/python server.py

Two surfaces, two ports

Surface Default bind Auth Purpose
Operator 127.0.0.1:5050 login required dashboard + /api/*
Collector 0.0.0.0:5051 public (by necessity) /collect, /agent/*, /probe.js

Operator routes are refused (404) on the collector port and vice-versa, so exposing the collector to the internet never exposes the console. On startup a login password is generated and printed (or set BX_PASSWORD). Set BX_SECRET to keep sessions valid across restarts.

Environment:

BX_PASSWORD=...        # operator login password (else generated + printed)
BX_SECRET=...          # Flask session secret (else random per start)
BX_OPERATOR_PORT=5050  # dashboard (bind loopback)
BX_COLLECTOR_PORT=5051 # victim-facing (bind 0.0.0.0)

Put TLS in front of the collector (Caddy/nginx). Payloads beacon over the collector's scheme; on an HTTPS target an http:// collector is mixed-content blocked. Terminate HTTPS at a reverse proxy → the collector port.

(Operator port 5050 — macOS AirPlay squats on 5000.)

Console UI

An enterprise-style single-page console with a sidebar nav, KPI tiles, and light/dark themes (toggle top-right; choice is remembered). Views:

  • Overview — KPI tiles (fires, fields, vulnerable URLs, captures, payloads), the live Interactions table, search, and Burp/CSV export.
  • Session & Payloads — start the interact.sh session, set the collector, and generate per-field payloads.
  • Live Sessions — the reverse-proxy / JS-shell console (online agents show a live count badge in the nav).
  • Alerts & Filters — notifications, block/whitelist filters, custom JS.

Workflow

  1. Start session — interact.sh server (default oast.fun) + optional Collector URL. The collector must be a host the victim browser can reach (a public VPS in a real engagement), since that's where the screenshot and DOM are POSTed. Leave blank to run OOB-only (no capture).
  2. Generate payloads for a target URL + field name → copy → plant them. With a collector set, a Full capture payload (screenshot + DOM + cookies) is added on top of the 15 context payloads.
  3. Watch the Interactions table light up live (SSE). Rows with a 📷/📄 in the Evidence column open a viewer with the screenshot, DOM snapshot, cookies and storage of the vulnerable page.

The Flask template is cached in memory, so restart the server after editing templates/index.html.

Screenshot + DOM capture (XSSHunter-style)

interact.sh confirms that a payload fired and which field (token in the hostname). The collector grabs the heavy evidence: the capture payload loads /probe.js, which pulls html2canvas from the collector, screenshots document.body, and POSTs the PNG + outerHTML + cookies + local/session storage to /collect. If html2canvas is blocked it still sends DOM + cookies.

Deploy the collector on a public host. Modern browsers' Private Network Access blocks a public page (e.g. a real target) from calling 127.0.0.1, so a localhost collector only works when the victim page is itself on localhost. In a real engagement the collector is a public VPS, which the victim reaches normally.

ezXSS-style features

  • Notifications — on every new fire, alerts go to Slack, Discord, Telegram, and/or a generic webhook (raw JSON). So you hear about a blind XSS that fires days later without watching the dashboard.
  • Block / whitelist filtering — drop noisy reports by domain or source IP (e.g. your own testing host), or whitelist so only in-scope domains are kept. Filtering applies to both interact.sh callbacks and collector captures.
  • Custom JavaScript — inject your own JS into the victim context (runs inside the probe), e.g. to exfil a CSRF token or hit an internal admin endpoint.
  • Stats overview — header counters: fires, distinct fired fields, distinct vulnerable URLs, captures, payloads generated.
  • Report search — live filter the interactions table by field / URL / IP / UA.
  • Fire deduplication — repeated fires of the same payload on the same page (an admin reloading a ticket 50×) collapse into one row with a ×N hit count and first/last-seen, so a genuinely new sink is never buried in noise.
  • Internal-IP flagging — callbacks from RFC1918 / loopback / reserved addresses get an INTERNAL badge. An internal source firing your payload is the strongest signal it reached a real internal tool (admin panel, log viewer), not your own external testing box. (netutil.is_internal)
  • Burp / ZAP / ffuf export — download all generated payloads as a newline wordlist (?fmt=burp, for Burp Intruder / ffuf) or CSV with token↔context↔ target mapping (?fmt=csv, for ZAP / spreadsheets) to spray them at scale.

Configured in the dashboard's "Notifications & filters" panel (persisted in the settings table). Notifications and filters need no restart.

Persistent session — live reverse proxy / JS shell

The "Persistent session" payload injects an agent that keeps polling your collector. Each fired agent becomes a live session in the dashboard's "Live sessions" panel (green dot = online, INTERNAL badge for RFC1918 sources). Select one to drive the victim's browser in real time:

  • Reverse proxy — enter a target URL; the agent runs fetch(url, {credentials:'include'}) in the victim's own origin, so the request carries their authenticated session — HttpOnly cookies included — and the response is streamed back and rendered in a sandboxed iframe (or raw). You effectively browse the target as the victim.
  • JS console — run arbitrary JavaScript in the victim context, see the return value.
  • Screenshot / cookies — on-demand live html2canvas snapshot and cookie/storage dump.
  • Command history — every command run against a session, with its type, timestamp, argument, and result (or a pending indicator), shown below the console (/api/sessions/<sid>/history).

Flow: agent.js/agent/poll (heartbeat + command pickup) → command runs in victim → /agent/result. Operators enqueue via /api/sessions/<sid>/cmd and read answers via /api/cmd/<id>. Sessions are in-memory (live connections).

This is powerful and intrusive — only use it against systems you are explicitly authorized to test.

Files

  • interactsh_client.py — register/poll/deregister + RSA-OAEP/AES-CFB decrypt
  • payloads.py — context templates + per-field token generation
  • db.py — SQLite correlation store (injections ↔ interactions)
  • server.py — Flask API + background poller + SSE feed + collector endpoints
  • collector.py — probe.js generator (screenshot + DOM exfil + custom JS)
  • notifications.py — Slack / Discord / Telegram / webhook alerts
  • netutil.py — internal / RFC1918 source-IP classification
  • agent.py — persistent agent.js (reverse proxy / JS shell)
  • sessions.py — in-memory live-session + command/result store
  • static/html2canvas.js — served to victims for screenshots
  • templates/index.html — dashboard UI + capture viewer

Use responsibly

XSSveil is for authorized security testing only — penetration tests, bug bounty programs within scope, and your own lab. It captures session cookies, DOM, and can drive a victim's browser via the reverse proxy; using it against systems you do not have explicit written permission to test is illegal in most jurisdictions. You are solely responsible for how you use it.

License

MIT — see LICENSE. Bundles html2canvas (MIT) in static/.

About

Blind XSS detection built on interact.sh — per-field tokenized payloads, injection-point↔vulnerable-URL correlation, screenshot/DOM capture, and a persistent-session reverse proxy. Authorized testing only.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages