~/home/study/http-request-smuggling-basics

HTTP/2 Request Smuggling Basics

Learn how HTTP/2 framing and HPACK header compression enable request smuggling, explore common vectors, and master detection and mitigation techniques.

Introduction

HTTP/2 introduced multiplexed streams, binary framing, and HPACK header compression to speed up web traffic. These same features also give attackers a new playground for request smuggling-the art of making a single client request appear as multiple or altered requests to different servers in a reverse-proxy chain. Understanding how HTTP/2 frames, header compression, and framing rules interact is essential for detecting and preventing smuggling attacks.

Prerequisites

  • Basic knowledge of the HTTP/1.1 request/response model.
  • Familiarity with the TCP/IP stack and how proxies forward traffic.
  • Understanding of HPACK header compression basics.
  • Experience with network debugging tools (Wireshark, tcpdump).

Core Concepts

Before diving into smuggling vectors, let’s review the building blocks that enable them.

HTTP/2 Frame Types and Sequencing

HTTP/2 is a binary protocol built around a stream-based framing layer. Each frame starts with a 9-byte header:


<len> // Length (24 bits)
<type> // Type (8 bits)
<flags> // Flags (8 bits)
<R + Stream Identifier> // 32 bits

Key frame types relevant to request smuggling:

  • HEADERS - carries the request/response headers and may include the END_HEADERS flag.
  • CONTINUATION - used when a header block spans multiple frames; must follow a HEADERS frame on the same stream.
  • DATA - payload of the request body; may be split arbitrarily.
  • RST_STREAM - aborts a stream; useful for crafting malformed streams.

Because HTTP/2 streams are multiplexed over a single connection, mis-ordered or overlapping frames can lead to divergent interpretations by upstream and downstream proxies.

HPACK Header Compression Mechanics

HPACK compresses header blocks using a dynamic and static table. Each header field is represented as an index or a literal. The compression context is maintained per connection, not per stream, which means that a malicious client can exploit the shared state:

  1. Send a HEADERS frame that references a header field by index.
  2. On a subsequent stream, send a HEADERS frame that re-uses the same index but changes the value, relying on the server to merge or replace entries incorrectly.

Moreover, HPACK allows a client to send a CONTINUATION frame that is not properly linked to a preceding HEADERS frame, causing the receiver to interpret the header block as belonging to a different stream.

Common Smuggling Vectors (CL.TE, TE.CL, TE.TE)

These acronyms describe the placement of Content-Length (CL) and Transfer-Encoding (TE) headers across the request body and the effect on server parsing:

  • CL.TE - Content-Length appears before Transfer-Encoding in the header block.
  • TE.CL - Transfer-Encoding appears before Content-Length.
  • TE.TE - two Transfer-Encoding headers, one of which is malformed or uses non-standard tokens.

When these headers are split across HTTP/2 frames or mis-ordered via HPACK, a proxy that implements the HTTP/1.1 semantics may interpret the body length differently from the back-end, resulting in a smuggled request.

Attack Flow and Payload Construction

Below is a step-by-step construction of a typical HTTP/2 smuggling payload that exploits a CL.TE vector.

  1. Open a raw TCP connection to the front-end proxy on port 443 (TLS termination may be bypassed with a man-in-the-middle or by using a client that supports ALPN).
  2. Send a SETTINGS frame to negotiate max header size and frame size.
  3. Craft a HEADERS frame on stream 1 containing a Content-Length: 5 header and the Transfer-Encoding: chunked header but set the END_HEADERS flag only after a CONTINUATION frame that contains the actual header values.
  4. Send a DATA frame with five bytes of payload followed by a DATA frame that starts a chunked transfer (4 abcd 0 ). The front-end will see the body as 5 bytes and close the stream, while the back-end interprets the chunked body and processes the smuggled request.

In practice, you can automate this with h2c or custom netcat scripts. A minimal Python example follows.


import socket, struct

def send_frame(sock, typ, flags, stream_id, payload=b''): length = len(payload) header = struct.pack('!I', length)[1:]  # 24 bits header += struct.pack('!B', typ) header += struct.pack('!B', flags) header += struct.pack('!I', stream_id & 0x7FFFFFFF)  # clear reserved bit sock.sendall(header + payload)

s = socket.create_connection(('proxy.example.com', 443))
# SETTINGS frame (typ=4)
send_frame(s, 4, 0, 0, b'\x00\x00\x00\x00\x00\x00\x00\x00')
# HEADERS frame (typ=1)
# ... (build HPACK header block and send)

The full implementation is beyond the scope of this guide, but the above skeleton illustrates how to manipulate frame boundaries.

Detection Techniques and Tooling

  • Network traffic analysis - Use Wireshark to inspect the frame order and header block boundaries. Look for CONTINUATION frames that are not immediately following a HEADERS frame on the same stream.
  • Proxy logs - Many HTTP/2 aware proxies log stream IDs and frame types. Correlate anomalies with downstream request counts.
  • Custom IDS signatures - Deploy Suricata or Zeek scripts that flag mismatched Content-Length and Transfer-Encoding across frames.
  • Fuzzing tools - h2spec and rfc7540-fuzzer can generate malformed frame sequences that exercise edge-cases.

Practical Lab Setup and Exploitation Steps

  1. Set up a three-box environment: Client (Linux VM), Front-end Proxy (NGINX 1.19+ with HTTP/2 enabled), Back-end Server (Apache 2.4).
  2. Configure the front-end to proxy HTTP/2 to the back-end over HTTP/1.1.
  3. Use h2load or a custom script to send the smuggling payload described earlier.
  4. Verify that the back-end receives two distinct requests: one normal, one smuggled.
  5. Enable logging on both proxies to capture the divergent interpretations.

Practical Examples

Case Study 1: Content-Length: 0 + Transfer-Encoding: chunked smuggling via a single HEADERS frame that is split across two CONTINUATION frames. The front-end treats the request as an empty body, while the back-end reads the chunked data and executes unintended logic.

Case Study 2: Double Transfer-Encoding headers, one of which is gzip, the other chunked. The front-end honors only the first header, discarding the rest; the back-end processes the entire header block, leading to a decompression bomb.

Tools & Commands

  • h2spec -t 10 - run HTTP/2 conformance tests.
  • h2load -n 100 -c 10 - generate load.</li> <li><code>tcpdump -i eth0 -nn -s 0 -vvv 'tcp port 443' - capture frames.
  • suricata -c suricata.yaml -v - start IDS with custom HTTP/2 rules.
  • Custom Python script (see snippet above).

Defense & Mitigation

  1. Strict header parsing - Ensure the front-end validates the Content-Length and Transfer-Encoding headers per RFC 9110 before forwarding.
  2. Disable HTTP/2 for sensitive services - If smuggling is a high risk, consider falling back to HTTP/1.1 for critical endpoints.
  3. Implement header size limits - Configure client_header_buffer_size and large_client_header_buffers in NGINX to prevent oversized header blocks.
  4. Use TLS mutual authentication - Mitigate man-in-the-middle attacks that may inject smuggling frames.
  5. Continuous monitoring - Deploy logging and alerting for anomalous frame sequences.

Common Mistakes

  • Assuming all proxies forward HTTP/2 frames verbatim; many drop CONTINUATION frames.
  • Overlooking HPACK table reuse; a malicious client can corrupt the shared state.
  • Ignoring the fact that Content-Length may be overridden by Transfer-Encoding per RFC 9110.
  • Underestimating the need for TLS validation; plain HTTP/2 can be exploited over unencrypted connections.

Real-World Impact

Smuggling attacks can lead to:

  • Bypassing authentication - injecting a second request that re-uses session cookies.
  • Cache poisoning - manipulating the cache key by altering the request body.
  • Data exfiltration - embedding payloads in a smuggled request that bypasses WAF rules.

Recent studies show that 12% of corporate proxies had mis-configured HTTP/2 handling, exposing them to smuggling. As HTTP/2 adoption continues, the attack surface will grow unless mitigated.

Practice Exercises

  1. Build a minimal HTTP/2 client in Python that sends a HEADERS frame followed by a CONTINUATION frame on a different stream ID.
  2. Using Wireshark, capture a smuggled request and annotate the frame sequence that led to the split.
  3. Configure NGINX to reject Transfer-Encoding headers that appear after Content-Length and test with the provided script.
  4. Write a Suricata rule that flags a HEADERS frame with END_HEADERS missing on the same stream.

Further Reading

  • RFC 7540 - HTTP/2: A Framework for Multiplexed Connections
  • RFC 9110 - HTTP Semantics
  • OWASP HTTP/2 Security Cheat Sheet
  • Suricata HTTP/2 Ruleset
  • NGINX HTTP/2 Configuration Guide

Summary

HTTP/2’s binary framing and HPACK compression give attackers powerful new smuggling vectors. By mastering frame sequencing, header compression mechanics, and the CL.TE/TE.CL/TE.TE patterns, you can craft attacks that split requests across a proxy chain. Detecting smuggling requires careful traffic inspection and IDS rule tuning, while mitigation hinges on strict header validation, disabling HTTP/2 where necessary, and continuous monitoring.