Testing request smuggling: the bug is between two servers
When two HTTP servers sit in series and parse a request's boundaries differently, the
front end forwards what it thinks is one request while the back end sees two, and the
smuggled second request is attributed to the next client on the reused connection.
That desync lets an attacker prefix a victim's request, bypass front-end controls,
poison the connection, and capture other users' traffic. The bug is a disagreement
between parsers, not a single flaw in one.
When to use
- You are reviewing a reverse proxy, load balancer, CDN, or gateway in front of an
origin.
- Requests pass through more than one HTTP parser in series.
- Connections between hops are kept alive and shared across clients.
Scope check
Test chains you own or are authorized to test, and keep every probe on your own
traffic. Do not target other users' live requests. If you can't name the
authorization, stop.
The loop
Map the server chain and the reused connections. Identify every hop a request
passes through (edge, proxy, balancer, cache, origin) and where connections between
hops are kept alive and shared across clients. A desync only bites where a
downstream connection is reused for another user, so map that reuse first.
Find conflicting length signals. A request's body length can be stated more
than one way, and smuggling happens when two hops resolve a conflict differently:
one honors one signal, the other honors the other. Check whether the chain
normalizes or rejects conflicting or duplicated length signals, or passes them
through for the next hop to reinterpret.
Probe for desync safely. Using benign, self-targeted probes, test whether a
crafted boundary makes the back end treat trailing bytes as the start of the next
request, observable as a delayed or mis-attributed response. Prefer timing-based
and single-packet detection that confirms the split without harming other users. A
measurable difference between "rejected" and "the next request was affected" is the
signal.
Escalate to impact on your own traffic. If a split is confirmed, demonstrate
the concrete effect against your own second request: prefixing it, bypassing a
front-end path restriction, or capturing the response to a request you control. Do
not target other users' live traffic; prove the primitive on requests you own.
Check adjacent boundary confusion. Does the chain hand off to another protocol
or downgrade a connection where the two ends parse framing differently (a proxy and
origin disagreeing on a message boundary)? The same parser-disagreement class
extends beyond one protocol version. Note any hop that reframes requests.
Rate and record. A confirmed desync that bypasses controls or captures
cross-user requests is critical; a reliably rejected boundary is a kill. Record the
exact conflicting signals and the observed split, and recommend the fix: normalize
or reject ambiguous framing at the edge, prefer one connection per client to the
origin, and reject conflicting length signals outright.
Where the chain desyncs
- The vulnerability is between two servers. Neither is wrong alone; they disagree,
and the disagreement is the bug.
- Connection reuse is what makes it dangerous. Without a shared downstream
connection, a split affects only you.
- Ambiguous framing should be rejected, not resolved. Any hop that helpfully
reinterprets a conflicting boundary is a desync source.
- Detection can be safe. Timing and single-packet methods confirm a split without
poisoning real users.
Worked example (a confirm and a kill)
Confirm. An edge proxy and the origin resolve a conflicting length signal
differently; a crafted request leaves trailing bytes the origin treats as the next
request. A benign timing probe shows the following self-owned request is prefixed by
the smuggled bytes, and a front-end-blocked path becomes reachable through the
smuggled request. Confirmed request smuggling, critical, remediation = reject
conflicting length signals at the edge, normalize framing, and avoid sharing origin
connections across clients.
Kill. The edge rejects any request with duplicated or conflicting length signals
with a 400, normalizes all forwarded requests to a single unambiguous framing, and
uses a fresh origin connection per client. Every desync probe is rejected and no
trailing bytes reach the origin as a new request. Killed, kill_reason =
"ambiguous framing rejected and normalized at the edge; no shared downstream
connection to poison."
Rationalizations to reject
- "Both servers are standards-compliant." → Compliance still leaves boundary
ambiguities they can resolve differently. Test the pair, not each alone.
- "There's a filter at the edge." → Smuggling bypasses the edge by hiding the request
from it. The control it enforces is exactly what gets skipped.
- "We use modern HTTP." → Newer versions have their own desync and reset classes. The
parser-disagreement problem moves, it does not vanish.
- "We couldn't reproduce it once." → Desync can be timing-sensitive. Use single-packet
and timing detection before calling it clean.
Executing this in practice
You need to see the server chain, control a client that can send precisely-framed
requests, and observe response timing and attribution on connections you own. Any
tooling that sends byte-exact requests and measures timing works; the
connection-reuse map and the conflicting-signal analysis are the method. Keep every
probe on your own traffic.
Related
mapping-attack-surface - enumerating the proxy, cache, and origin hops in the
chain.
testing-web-cache-attacks - a smuggled response can poison a shared cache entry.
- FINDING-SCHEMA.md - source = the ambiguously-framed
request, sink = the back-end request it splits into or the control it bypasses.
1---2name: testing-request-smuggling3description: Test whether a chain of HTTP servers disagrees about where one request ends and the next begins, letting an attacker smuggle a request past the front end into the back end. Covers front-end and back-end desync from conflicting length signals, connection-reuse poisoning, single-packet and timing detection, and adjacent boundary confusion where a proxy and origin parse framing differently. Use when reviewing a reverse proxy, load balancer, CDN, or any multi-hop HTTP path where two parsers sit in series. The bug is a disagreement between parsers, not one flaw.4license: MIT5---67# Testing request smuggling: the bug is between two servers89When two HTTP servers sit in series and parse a request's boundaries differently, the10front end forwards what it thinks is one request while the back end sees two, and the11smuggled second request is attributed to the next client on the reused connection.12That desync lets an attacker prefix a victim's request, bypass front-end controls,13poison the connection, and capture other users' traffic. The bug is a disagreement14between parsers, not a single flaw in one.1516## When to use1718- You are reviewing a reverse proxy, load balancer, CDN, or gateway in front of an19 origin.20- Requests pass through more than one HTTP parser in series.21- Connections between hops are kept alive and shared across clients.2223## Scope check2425Test chains you own or are authorized to test, and keep every probe on your own26traffic. Do not target other users' live requests. If you can't name the27authorization, stop.2829## The loop30311. **Map the server chain and the reused connections.** Identify every hop a request32 passes through (edge, proxy, balancer, cache, origin) and where connections between33 hops are kept alive and shared across clients. A desync only bites where a34 downstream connection is reused for another user, so map that reuse first.35362. **Find conflicting length signals.** A request's body length can be stated more37 than one way, and smuggling happens when two hops resolve a conflict differently:38 one honors one signal, the other honors the other. Check whether the chain39 normalizes or rejects conflicting or duplicated length signals, or passes them40 through for the next hop to reinterpret.41423. **Probe for desync safely.** Using benign, self-targeted probes, test whether a43 crafted boundary makes the back end treat trailing bytes as the start of the next44 request, observable as a delayed or mis-attributed response. Prefer timing-based45 and single-packet detection that confirms the split without harming other users. A46 measurable difference between "rejected" and "the next request was affected" is the47 signal.48494. **Escalate to impact on your own traffic.** If a split is confirmed, demonstrate50 the concrete effect against your own second request: prefixing it, bypassing a51 front-end path restriction, or capturing the response to a request you control. Do52 not target other users' live traffic; prove the primitive on requests you own.53545. **Check adjacent boundary confusion.** Does the chain hand off to another protocol55 or downgrade a connection where the two ends parse framing differently (a proxy and56 origin disagreeing on a message boundary)? The same parser-disagreement class57 extends beyond one protocol version. Note any hop that reframes requests.58596. **Rate and record.** A confirmed desync that bypasses controls or captures60 cross-user requests is critical; a reliably rejected boundary is a kill. Record the61 exact conflicting signals and the observed split, and recommend the fix: normalize62 or reject ambiguous framing at the edge, prefer one connection per client to the63 origin, and reject conflicting length signals outright.6465## Where the chain desyncs6667- **The vulnerability is between two servers.** Neither is wrong alone; they disagree,68 and the disagreement is the bug.69- **Connection reuse is what makes it dangerous.** Without a shared downstream70 connection, a split affects only you.71- **Ambiguous framing should be rejected, not resolved.** Any hop that helpfully72 reinterprets a conflicting boundary is a desync source.73- **Detection can be safe.** Timing and single-packet methods confirm a split without74 poisoning real users.7576## Worked example (a confirm and a kill)7778> **Confirm.** An edge proxy and the origin resolve a conflicting length signal79> differently; a crafted request leaves trailing bytes the origin treats as the next80> request. A benign timing probe shows the following self-owned request is prefixed by81> the smuggled bytes, and a front-end-blocked path becomes reachable through the82> smuggled request. **Confirmed** request smuggling, `critical`, remediation = reject83> conflicting length signals at the edge, normalize framing, and avoid sharing origin84> connections across clients.85>86> **Kill.** The edge rejects any request with duplicated or conflicting length signals87> with a 400, normalizes all forwarded requests to a single unambiguous framing, and88> uses a fresh origin connection per client. Every desync probe is rejected and no89> trailing bytes reach the origin as a new request. **Killed**, `kill_reason` =90> "ambiguous framing rejected and normalized at the edge; no shared downstream91> connection to poison."9293## Rationalizations to reject9495- *"Both servers are standards-compliant."* → Compliance still leaves boundary96 ambiguities they can resolve differently. Test the pair, not each alone.97- *"There's a filter at the edge."* → Smuggling bypasses the edge by hiding the request98 from it. The control it enforces is exactly what gets skipped.99- *"We use modern HTTP."* → Newer versions have their own desync and reset classes. The100 parser-disagreement problem moves, it does not vanish.101- *"We couldn't reproduce it once."* → Desync can be timing-sensitive. Use single-packet102 and timing detection before calling it clean.103104## Executing this in practice105106You need to see the server chain, control a client that can send precisely-framed107requests, and observe response timing and attribution on connections you own. Any108tooling that sends byte-exact requests and measures timing works; the109connection-reuse map and the conflicting-signal analysis are the method. Keep every110probe on your own traffic.111112## Related113114- `mapping-attack-surface` - enumerating the proxy, cache, and origin hops in the115 chain.116- `testing-web-cache-attacks` - a smuggled response can poison a shared cache entry.117- [FINDING-SCHEMA.md](../../FINDING-SCHEMA.md) - source = the ambiguously-framed118 request, sink = the back-end request it splits into or the control it bypasses.