HTTP/3 and QUIC attack surface
TL;DR: HTTP/3 runs HTTP semantics over QUIC, which runs over UDP. Attack surface differs from HTTP/2: (1) connection migration via connection IDs — token theft scenarios change, (2) 0-RTT replay, (3) different request-smuggling primitives because frames vs streams differ, (4) amplification via initial-packet UDP, (5) load-balancer / WAF gaps because many vendors still don’t fully parse QUIC, (6) new fingerprinting primitives. Companion to http-smuggling-modern-variants and http2-h2-downgrade-desync-v3.
What changed from HTTP/2
| Layer | HTTP/2 | HTTP/3 |
|---|---|---|
| Transport | TCP + TLS | UDP + QUIC (TLS 1.3 built-in) |
| Connection re-establish | new TCP handshake | Connection ID migration, no rehandshake |
| Frame loss | TCP head-of-line blocking across streams | per-stream loss isolation |
| 0-RTT | TLS session resumption | QUIC 0-RTT (replayable) |
| Header compression | HPACK | QPACK (different state model) |
| Frame layer | HTTP/2 frames over TCP | HTTP/3 frames over QUIC streams |
Attack class 1 — Connection ID and migration
QUIC connections aren’t tied to a 5-tuple (src IP/port → dst IP/port). They use Connection IDs — opaque blobs in each packet. A client can switch networks (Wi-Fi → cellular) and the same connection survives.
Bug shapes:
- Server logs by source IP — attacker migrates, evades IP-based throttling / blocking.
- Load balancer sticky-routing breaks when Connection ID and src IP disagree.
- Token-bound-to-IP schemes (legacy session-IP binding) trivially defeated by migration.
Attack class 2 — 0-RTT replay
QUIC’s 0-RTT lets clients send application data on the first packet using a key from a previous session. That data has no replay protection by spec — replays are possible.
Bug shapes:
- Non-idempotent endpoints (POST that creates state) reachable in 0-RTT → replay creates duplicate state.
- Authorization checks that depend on counter values can be replayed.
Defence: server-side reject 0-RTT for state-changing endpoints. Most modern servers (nginx-quic, h2o, cloudflare quiche) allow opt-in.
Attack class 3 — HTTP/3 smuggling
HTTP/3 frames a single request as a sequence of frames on one stream. Smuggling primitives that worked at HTTP/1.1 (CL vs TE) don’t apply directly. New ones:
- Header-list compression confusion — QPACK uses dynamic table; attacker manipulates table state to inject extra headers a downstream parser sees differently.
- Stream multiplexing parsing gaps — middlebox decodes streams independently, server reassembles; sequence differences create smuggle.
- Pseudo-header injection —
:authority,:method,:path— different normalisation across stack tiers.
Bug research is younger than HTTP/2 smuggling; James Kettle / PortSwigger have published initial findings.
Attack class 4 — UDP amplification
The initial QUIC packet is large (≥ 1200 bytes) and the server replies with up to 3x the client’s data (Anti-Amplification limit). Misconfigured servers exceed the limit → reflection amplifier.
Modern servers respect the 3x rule and reject malformed Initial packets. Test with --check-amplification flags in QUIC scanners.
Attack class 5 — load balancer / WAF visibility gaps
Many enterprise WAFs and load-balancers still:
- Don’t parse HTTP/3 at all (passthrough).
- Parse HTTP/3 but skip stream/frame validation that they do for HTTP/2.
- Apply rate-limits per src-IP, defeated by Connection ID migration.
The attacker who knows the WAF terminates HTTP/3 differently from origin can craft requests that one side sees one way, the other sees another.
Attack class 6 — fingerprinting
QUIC includes a transport-parameters Initial packet whose layout varies by implementation. JA4 (Wireshark)’s QUIC fingerprint can identify Chrome vs Firefox vs Go vs curl — useful both for blocking (defender) and detecting evasion (attacker).
Tooling
1
2
3
4
5
6
7
8
# Send HTTP/3 requests
curl --http3 https://example.com/
# nghttp3 / quiche / chrome with --enable-quic
chromium --enable-quic --quic-version=h3 https://example.com
# Capture + decrypt (with SSLKEYLOGFILE)
SSLKEYLOGFILE=keys.log curl --http3 ...
# Wireshark loads keys.log and decrypts QUIC payload
Server inventory
1
2
3
# Detect HTTP/3 support
curl -sIv --http3 https://example.com 2>&1 | grep -i alt-svc
# Alt-Svc: h3=":443"
Defence
- Pin behaviour: server config decides 0-RTT replay-protection vs replay-allowed per endpoint.
- WAF / LB chain validates QUIC: same vendor end-to-end is safer than mixed-vendor.
- Rate-limit by Connection ID + token + IP, not IP alone.
- Logging includes Connection ID for correlation post-migration.
OSCP/OSEP relevance
Low — bug-bounty and research relevance high.