Smuggler 🔍 A T-Reqs Grammar-based HTTP/1.x request-smuggling fuzzer for Nim
Based on bahruzjabiyev/t-reqs (CCS 2021 T-Reqs paper)
nimble install smuggler
- Grammar-based request generation: define a context-free grammar for HTTP requests (request line, headers, CL/TE framing, whitespace/case variants) and generate valid + adversarial requests from a reproducible seed.
- Mutation engine (paper §4.3/§4.4):
- String mutations: byte-level (bit flips, insert/delete/replace, cross-over splice) seeded per iteration.
- Tree mutations: grammar-aware symbol insert / replace / delete on the generated parse tree (e.g. repeated method, missing protocol version) — the structural mutations that find non-CL/TE vectors.
- Request-smuggling detection:
- In-process CL/TE oracle: parses a request strictly by Content-Length and strictly by Transfer-Encoding and reports disagreement (a desync candidate), no network needed.
- Response-pairing (two-request technique): drives a live target with the attack followed by a tagged probe and detects swallowed probes / ghost responses.
- Raw-socket keep-alive delivery: speaks plain HTTP/1.0/1.1 over TCP, so it works against any target (powpow, nginx, a proxy, ...) with no HTTP-client dependency.
- Standalone CLI:
smuggler -g grammar.cfg -t host:port -n N -s seed. - Self-contained: portable core requires only the Nim standard library (no clang/libFuzzer); works on Linux, macOS and Windows.
Smuggler hunts for HTTP request smuggling (CL.TE / TE.CL). When a front-end and a back-end server disagree about where a request body ends, an attacker can "smuggle" a request past the front-end. The tool finds requests that make the two interpretations diverge and validates them against a live target.
Inspired by bahruzjabiyev/t-reqs (CCS 2021, paper): this is a self-contained Nim implementation.
# in-process CL/TE oracle: flag desync candidates, no network
smuggler -g configs/request-line.cfg -n 10000 -p
# fuzz a live HTTP server (two-request response-pairing)
smuggler -g configs/headers.cfg -t 127.0.0.1:9000 -n 1000 -s 0
# apply 2 tree mutations + 1 string mutation per request
smuggler -g configs/body.cfg -t 127.0.0.1:9000 -n 1000 -T 2 -m 1
# help
smuggler -hBundled grammars (per-request-component, mirroring the paper's experiments):
configs/request-line.cfg, configs/headers.cfg (all 67 standard headers),
configs/body.cfg (chunked, LF-terminated chunk extensions, HTTP/1.0+chunked).
Options: -g/--grammar, -t/--target host:port, -n/--count,
-s/--seed, -m/--mutations (string), -T/--tree (tree mutations),
-p/--probe (oracle only), -v/--verbose.
import smuggler
# load a grammar, generate a request, mutate it
let g = parseGrammarFile("configs/request-line.cfg")
let req = generate(g, seed = 42) # raw HTTP request bytes
var mutated = toBytes(req)
mutateRequest(mutated, seed = 42, mutations = 3)
let raw = toString(mutated)
# in-process desync oracle (no network)
if isDesyncCandidate(raw):
echo "CL/TE disagreement: ", raw.escape()
# live two-request test
let c = open("127.0.0.1", 9000)
let r = pairTest(c, raw, tag = "abc123")
echo classify(r) # detNormal / detProbeSwallowed / detGhostResponse
c.close()Rules use = or the paper's ::= separator; terminal strings may be
double-quoted (e.g. "HTTP/1.1", " /_URI_ ", "Content-Length: <cl>\r\n"),
and a rule may span multiple lines (indented continuation, or a line following
one that ends with |). A production is a sequence of symbols: <name>
expands to another rule, _TOKEN_ is an injected value slot, everything else
is a literal (\r / \n escapes supported). | separates alternatives, ""
is the empty production. The first rule is the start rule.
<start> = <request>
<request> = <request-line><headers><body>
<request-line> = <method> <ws> <uri> <ws> HTTP/1.1 <crlf>
<method> = GET | POST | PUT | DELETE | HEAD
<ws> = _WS_
<uri> = /_PATH_
<crlf> = \r\n
<headers> = Host: <host> <crlf> <framing-headers><crlf>
<host> = _HOST_
<framing-headers> = "" | <cl-header> | <te-header> | <cl-header><te-header> | <te-header><cl-header>
<cl-header> = Content-Length: <cl> <crlf>
<te-header> = Transfer-Encoding: <te> <crlf>
<cl> = _CL_
<te> = _TE_
<body> = "" | <value>
<value> = _VALUE_
Token slots are injected from built-in pools tuned for smuggling / parser
edge cases (content-lengths including 9223372036854775808, chunk sizes,
whitespace and case variants, odd hosts/paths, ...). Ship a grammar like the one
above and run smuggler against your server or proxy.
| Module | Purpose |
|---|---|
smuggler/grammar |
CFG grammar model + file parser (=/::=, quotes, continuations) |
smuggler/generate |
seed-driven generation, parse trees, render, tree mutations |
smuggler/mutate |
string mutation + cross-over splice |
smuggler/send |
non-blocking keep-alive socket delivery + response reader |
smuggler/detect |
CL/TE oracle + response-pairing desync detection |
- Grammar-format compatibility with the T-Reqs paper (
::=, quoted terminals, multi-line rules) - Tree-level (grammar-aware) mutation of parsed requests
- Per-component grammars (request line / headers / body) incl. the paper's LF-chunk and HTTP/1.0+chunked vectors
- Coverage-guided adapters: nim-drchaos / libFuzzer targets for structured fuzzing of HTTP parsers
- Differential fuzzing against multiple targets (feedback-provider mode)
- 🐛 Found a bug? Create a new Issue
- 👋 Wanna help? Fork it!
![]() |
Switch to Open-Source LLMs via OpenCode GO, choosing from a variety of powerful models such as DeepSeek, Qwen, Kimi, GLM-5, MiniMax, MiMo. 🍕 Use our referral link to get started! |
MIT license. Made by Humans from OpenPeeps.
Copyright OpenPeeps & Contributors — All rights reserved.
