v2.1 - Dynamic gadget oracle, response fingerprinting, RTT baseline, tiered confidence

HTTP Request Smuggling,
made approachable.

An HTTP desync / request-smuggling testing tool in pure Python 3. Classic TE.CL and CL.TE detection corroborated by three independent signals - a per-target dynamic gadget oracle, structural response fingerprinting, and a statistical RTT baseline - that combine into a tiered STRONG / CONFIRMED / Potential confidence label. Same stack of advanced scanners (CL.0, TE.0, pause-based, parser discrepancy, header removal, Expect, hop-by-hop, bare-LF chunked, connection-state, HTTP/2 downgrade), now with fingerprint-only detection paths that catch desyncs the old status-only oracle silently missed. Optional NiceGUI web frontend with a structured findings panel and Markdown report export.

~/smuggler โ€” python3 smuggler.py -u https://target/
  ______                         _              
 / _____)                       | |             
( (____  ____  _   _  ____  ____| | _____  ____ 
 \____ \|    \| | | |/ _  |/ _  | || ___ |/ ___)
 _____) ) | | | |_| ( (_| ( (_| | || ____| |    
(______/|_|_|_|____/ \___ |\___ |\_)_____)_|    
                    (_____(_____|               
     @l0lsec                                v2.1
$ python3 smuggler.py -u https://target.com/ --scan-type all
Why Smuggler

Built for finding real desyncs.

Every scanner in Smuggler pairs a timing/disconnect oracle with a positive confirmation probe (gadget surfacing, canary divergence, matched-pair comparison) so a finding has multiple signals behind it - not just a slow response.

โšก

Pure Python 3

No build step, no native deps for the core scanners. git clone and run. HTTP/2 support is a single optional pip install h2.

๐ŸŽฏ

11 scanner classes

Classic TE.CL / CL.TE plus CL.0, TE.0, bare-LF chunked, pause-based, parser-discrepancy, header-removal, Expect, hop-by-hop, connection-state, and HTTP/2 downgrade - each with its own oracle.

๐Ÿ”

Replay mode

Continuously replay a captured request, optionally interleaved with a baseline request for differential analysis. Live RPS, success/timeout/error counters, and request-ID tagging.

๐Ÿงช

Tiered confidence

Findings vote across timing + gadget + fingerprint + RTT-baseline signals. STRONG (timing + 2-3 corroborators), CONFIRMED (timing + 1), Potential (timing only). No more guessing whether a timing blip is real.

๐ŸŽฏ

Dynamic gadget oracle

Per-target gadget selection (OPTIONS *, random 404, robots, favicon, query reflection) with auto-derived look_for signatures and a per-run canary. Replaces the hard-coded /robots.txt + "llow:" pair that silently broke on Akamai / Cloudflare edges.

๐Ÿ”

Fingerprint diff

Six-axis structural diff (status, framing, headers, body length, body head/tail hashes) with a 3-sample noisy-axis baseline. Catches desyncs that flip Set-Cookie or Content-Length without changing status - HOPBYHOP_FP_*, HDRREMOVAL_FP, CONNSTATE_FP payload tags.

๐ŸŽจ

Web GUI

Optional NiceGUI frontend exposes every CLI flag, streams colorized output to your browser, and renders live replay stats with a graceful SIGINT โ†’ SIGKILL stop ladder.

๐Ÿฉบ

Findings triage

Real-time panel splits results into Confirmed (oracle fired) and Needs manual confirmation (single-signal heuristic). Each finding ships with a per-class confirmation / escalation playbook and a one-click Copy as Markdown report.

๐Ÿ”ฌ

Per-payload tools

Inline hex + annotated-text viewer (the invisible 0x09/0x0a byte that makes the mutation work is highlighted), one-click Replay this, and copy a byte-exact openssl s_client / ncat reproduction command - no -crlf, so bare-LF mutations survive.

๐Ÿ› ๏ธ

Tooling-friendly

Burp-style raw request files, proxy support, custom cookies, persistent TCP connections, mutation configs (default.py, exhaustive.py, doubles.py, http10.py, chunkext.py), and a tested pytest harness.

Get started

Installation & quickstart

Clone the repo and you have the CLI. Install the requirements file to unlock the optional HTTP/2 scanner and the web GUI.

CLI core

$ git clone https://github.com/l0lsec/smuggler.git
$ cd smuggler
$ python3 smuggler.py -h

Web GUI optional

$ pip install -r requirements.txt
$ python3 webgui.py
# open http://127.0.0.1:8765

Common invocations

# Default scan: TE.CL + CL.TE against a single URL
$ python3 smuggler.py -u https://target.com/

# Run every advanced scanner
$ python3 smuggler.py -u https://target.com/ --scan-type all

# Use a captured Burp request as a template (extracts host/method/cookies)
$ python3 smuggler.py -r request.txt

# Replay a smuggling POC continuously with a baseline diff
$ python3 smuggler.py -r poc.txt --baseline-request normal.txt --replay

# Pipe a list of hosts
$ cat hosts.txt | python3 smuggler.py

# Route everything through Burp
$ python3 smuggler.py -u https://target.com/ --proxy http://127.0.0.1:8080
Heads up: Smuggler does not guarantee no false positives or false negatives. Large fronting providers (CDNs, cloud LBs) can flap on the timing oracle. Treat findings as leads to validate manually, especially before reporting.
New in v2

A web GUI for every CLI flag.

The optional webgui.py wraps the CLI as a subprocess, streams its colorized output into your browser, and gives you a stop button - without hiding any options or behavior.

http://127.0.0.1:8765/
Screenshot of Smuggler web GUI showing Target, Mode and Output panels
  • Three target modes: single URL, list of hosts (piped via stdin), or request file (upload, path, or inline edit).
  • Full CLI parity - every flag from --scan-type down to --pause-timeout is a form control.
  • Structured Findings panel parses smuggler's stdout in real time and splits results into Confirmed vs Needs manual confirmation.
  • Each finding ships with a per-class confirmation / escalation playbook tailored to the scan that fired (CLTE, CL.0, ParserDisc, HopByHop, โ€ฆ).
  • Markdown report export โ€” copy the whole triage report (or a single finding) ready to paste into a bounty / pentest ticket.
  • Per-payload actions: hex + annotated text viewer (highlights the invisible 0x09 / 0x0a byte that makes the mutation work), one-click Replay, and byte-exact openssl s_client repro.
  • Live payload discovery โ€” new files dropped into payloads/ mid-run get a NEW badge and a toast notification.
  • Replay live stats: total / success / failed / timeout / error / RPS / baseline-ratio / latest request ID.
  • Colorized output with smuggler's ANSI rendered as styled HTML.
  • Stop button sends SIGINT โ†’ SIGTERM โ†’ SIGKILL in a graceful ladder.
  • "Copy command" surfaces the exact python3 smuggler.py โ€ฆ invocation.
Localhost only. The GUI binds to 127.0.0.1 by default. Smuggler is an offensive scanner - do not expose webgui.py on a public interface. The --public flag exists but prints a loud warning.
New in v2.1

Three signals, one tiered verdict.

Every scanner now consults three independent oracle signals on top of the original timing check. Findings combine them into a confidence tier so you know whether you're looking at a high-signal lead or a CDN blip.

Dynamic gadget oracle lib/Oracle.py

Per-target candidate probe (OPTIONS *, random-404, robots, favicon, query-reflect) with auto-derived look_for - canary reflection > status divergence > distinctive header > body n-gram. Replaces the hard-coded /robots.txt + "llow:" pair that silently broke on Akamai, sharded backends, and any target that doesn't serve robots.

Response fingerprint lib/Fingerprint.py

Six-axis structural snapshot diffed against a 3-sample baseline. The baseline records which axes are noisy on this target (Date, request IDs, cache tags) and excludes them from later diffs. A probe is "structurally different" when it diverges on the status axis OR on >=2 non-noisy axes.

statusframingheader_setbody_lenbody_headbody_tail

Statistical RTT baseline lib/Timing.py

N=5 RTT samples on fresh connections produce median + MAD. A response is flagged when |rtt - median| > k * MAD (k=3). MAD is robust to the very outliers we're detecting; a 50ms MAD floor stops localhost-flat baselines from classifying every wobble as anomalous. Augments - does not replace - the binary timeout deadline.

Confidence tiers

Reproducible timing anomaly is the entry ticket; the three corroborators decide the tier. Annotation in square brackets shows which fired.

Tier Required signals
STRONG Reproducible timeout + 2 or 3 of { gadget hit, fingerprint divergence, RTT anomaly }
CONFIRMED Reproducible timeout + exactly 1 corroborator
Potential Reproducible timeout only, no corroborator

Worked example - Akamai edge with Disallow: stripped

The old detector used /robots.txt + "llow:" as its only positive oracle. On a fronting edge that strips the Disallow: line, that oracle silently returned False - findings landed as Potential (timing-only) and got buried. Under the new pipeline:

  1. GadgetOracle walks the catalogue and picks OPTIONS / - baseline returns 405, gadget returns 200. Auto-derived look_for becomes "Allow:", matched header-only.
  2. CLTE timing oracle fires reproducibly.
  3. Smuggled OPTIONS / hits the backend; victim leg comes back with Content-Length flipped 4287 โ†’ 0 and Last-Modified missing from header_set.
  4. TimingBaseline.is_anomalous(rtt, k=3) flags the response RTT well outside median + MAD.
[postspace-09]: STRONG CLTE Issue Found - POST @ https://target.com/api - default.py [gadget,fp=header_set+body_len,rtt]

The old code would have called this Potential and you'd have ignored it. The annotation makes the upgrade auditable.

Detection capability

What each scanner actually does.

Every scanner pairs an anomaly oracle with one or more positive confirmation steps. Confidence reflects the strength of the corroboration, not severity.

Attack class --scan-type Oracle Confidence
TE.CL / CL.TE tecl, clte Timing anomaly + dynamic gadget probe + victim-leg fingerprint diff + statistical RTT baseline STRONG / CONFIRMED / Potential
CL.0 / 0.CL cl0 Pipelined victim observes the smuggled gadget or victim-leg fingerprint diverges from a clean baseline; tries user method + GET + POST High (3-of-5)
TE.0 te0 Victim observes the smuggled gadget or fingerprint diverges, after a zero-chunk terminator High (3-of-5)
Bare-LF / Bare-CR chunked bare-lf Pipelined victim observes the gadget or fingerprint diverges when framing uses bare LF/CR High (3-of-5)
Pause-based desync pause Send headers, pause N s, send body; pipelined victim observes gadget or fingerprint diverges Medium-high (2-of-3)
Connection-state attack connection-state Status flip vs fresh connection (CONNSTATE) or >=2-axis fingerprint divergence with confirmation (CONNSTATE_FP) Medium
Parser discrepancy parser-discrepancy Per-technique control + canary probe; findings annotate diff axes, HIDDEN downgrades to PARTIAL-HIDE when non-status axes also flip Medium-high
Header removal (Keep-Alive) header-removal Status / canary flip (HDRREMOVAL) or 3-of-5 reproducible fingerprint-only divergences (HDRREMOVAL_FP) Medium-high
Expect-based desync expect Multiple Expect variants pipelined with a victim; same gadget + fingerprint oracle as CL.0 High when confirmed
Hop-by-hop auth bypass hop-by-hop Status flip (HOPBYHOP_*) or reproducible non-status fingerprint divergence (HOPBYHOP_FP_*) High when reproducible
HTTP/2 downgrade h2 Send H2 attack stream, then open a parallel H1 connection; flag if victim sees gadget High
Pitfalls

Request file modes - they aren't interchangeable.

A surprising fraction of "Smuggler says OK but Burp shows desync" reports come from pasting a smuggling POC into -r without --replay. Here's the mental model.

๐Ÿ“„

Scan mode (default -r)

The request file is a template. Smuggler extracts method, endpoint, host, and cookies, then synthesizes its own smuggling payloads from your chosen config. Body bytes and embedded request lines are ignored. Use req_clean.txt.

๐Ÿ”

Replay mode (-r --replay)

The request file is sent verbatim, looping until you stop. Pair with --baseline-request to interleave a clean request for differential analysis. This is what you want for POC-shaped files like req_poc.txt.

Smuggler will warn you. The validator now emits a Notice: line when a -r file (without --replay) contains body bytes or embedded request lines.
Mutation configs

Pick the depth of your TE/CL fuzzing.

Configs control which header mutations the classic TE.CL / CL.TE scanners try. They are Python files in configs/ that you can read and edit.

# Faster, smaller mutation set (default)
python3 smuggler.py -u https://target.com/ -c default.py

# Wider mutation surface, slower
python3 smuggler.py -u https://target.com/ -c exhaustive.py

# Niche: doubled headers, chunk-extensions, HTTP/1.0 quirks
python3 smuggler.py -u https://target.com/ -c doubles.py
python3 smuggler.py -u https://target.com/ -c chunkext.py
python3 smuggler.py -u https://target.com/ -c http10.py
Configs execute as Python. They are exec()'d in the same process as Smuggler. Only load configs you trust - they have full process privileges.