HTTP request smuggling: what passive scanners miss
HTTP request smuggling exploits parser disagreement between two HTTP servers in series — typically a CDN/reverse-proxy in front of an origin. The 2019 Burp Suite paper made it widely known; the technique has been around since 2005. It remains, in 2026, an extremely high-impact bug class that passive scanners (including ours) cannot reliably detect.
The core ambiguity
HTTP/1.1 specifies two ways to delimit the body of a POST: Content-Length
and Transfer-Encoding: chunked. RFC 7230 says: if both are present, the
recipient must process Transfer-Encoding and ignore Content-Length.
In practice, different servers implement this rule differently:
| Stack | If both headers present |
|---|---|
| nginx (older) | Used CL, ignored TE |
| HAProxy | Used TE |
| AWS ALB | Historically used TE; CL after parsing edge cases |
| IIS | Used TE |
| Apache 2.4 | Used TE; obscure cases used CL |
| Tomcat | Used CL in some versions |
| Node.js (older) | Used CL |
An attacker crafts a request the front-end parses as one shape (one request) and the back-end parses as two requests. The "smuggled" second request is concatenated to the next legitimate request's start.
The CL.TE attack
POST / HTTP/1.1
Host: vulnerable.example.com
Content-Length: 13
Transfer-Encoding: chunked
0
SMUGGLED
Front-end uses Content-Length=13 (sees the whole thing as one request). Back-end uses
Transfer-Encoding (sees the 0\r\n\r\n as end-of-body, leaves
SMUGGLED in the connection buffer). The next legitimate request from
anyone gets SMUGGLED prepended to its start.
What you can do with that
- Cache poisoning. Smuggle a request whose response gets cached at the front-end edge under another user's key. Victim user fetches the URL and gets the attacker's response.
- Authentication bypass. Smuggle a request that lands inside another user's authenticated TCP connection (HTTP/1.1 keepalive). The smuggled request inherits their session.
- WAF bypass. The WAF sees the front-end view (clean). The back-end sees the malicious payload.
In 2024 a major US healthcare provider had a CL.TE bug exploited that exposed PHI for ~2 million users. Bug bounty payouts of $5K-$30K for HRS findings on PortSwigger Labs. This is not theoretical.
HTTP/2 doesn't save you
HTTP/2 forbids both Content-Length and Transfer-Encoding in
the wire format — frames have explicit lengths. But: most edges accept HTTP/2 from
clients and downgrade to HTTP/1.1 to talk to the origin. The downgrade re-introduces
the ambiguity and adds new ones (e.g., :path pseudo-header to
Host+URI conversion, header-name newline injection).
James Kettle's 2021 "HTTP/2: The Sequel" paper documented H2.CL, H2.TE, and H2.X classes of smuggling on H2-to-H1 downgrades. Vulnerable stacks at the time: AWS ALB, Atlassian, Cisco WebEx.
Why we don't probe for it
Our scanner is passive by design (Basic + Extended profiles). Probing for smuggling requires:
- Sending malformed HTTP/1.1 requests (some are interpreted as concatenated requests).
- Time-based detection (front-end accepts, back-end times out → smuggling confirmed).
- Risk of crashing the load balancer (rarely) or polluting a shared cache (sometimes).
This is the line between passive and active we don't cross. Active profile checkers in UnveilScan have a triple-gate: ownership, ack, non-destructive. A smuggling probe that risks corrupting a CDN cache for other users on the same edge isn't non-destructive in any meaningful sense.
What you should run instead
- Burp Suite Pro Smuggler. Active probe against your own infrastructure during pentest engagements.
- smuggler.py by defparam. Open source, comprehensive payload list.
- Internal CI test. Spin up your full edge → origin path in CI, send 30 known smuggling payloads, assert all fail.
Mitigation patterns
- Front-end normalizes. Configure the edge proxy to reject requests with both CL and TE. nginx
proxy_http_version 1.1; proxy_set_header Connection "close";partly helps but doesn't catch all variants. - Back-end strict mode. Apache
HttpProtocolOptions Strict, nginx withhttp2_body_preread_size 0, Node.jshttp.maxHeaderSize. - End-to-end HTTP/2 or HTTP/3. No 1.1 downgrade = no ambiguity-based smuggling. Deploy this where you can.
- Connection-per-request. Disable keepalive between front-end and back-end. Smuggling requires connection reuse to inherit between users. Performance hit, sometimes acceptable for sensitive apps.
What we DO check
We check upstream stack hygiene that often correlates with smuggling exposure: the
protocols checker reports HTTP/2 + HTTP/3 support; the
headers checker flags Server banners that reveal old versions
(nginx 1.13, HAProxy 1.7) where smuggling bugs were unfixed; the techcve
checker cross-references those banners against OSV.dev for known CVEs.
These are weak signals. They tell you "look here" — not "you have HRS." For the actual HRS audit, pay a pentester or run smuggler.py in CI.
Audit your edge stack hygiene
Free Basic scan reports your front-end stack version and known CVEs. Pair it with a real HRS test.
Run a scan