0-RTT Replay: The High-Speed Flaw in HTTP/3 That Bypasses Idempotency 🏎️🔄

0-RTT Replay: The High-Speed Flaw in HTTP/3 That Bypasses Idempotency 🏎️🔄
In the relentless pursuit of web performance, the industry has shifted toward HTTP/3 (QUIC). By replacing the aging TCP/TLS stack with a UDP-based architecture, HTTP/3 promises near-instantaneous connection times. The “holy grail” of this speed is 0-RTT (Zero Round-Trip Time)—a feature that allows clients to send data before a cryptographic handshake even finishes.
However, speed often comes at the cost of safety. The 0-RTT mechanism introduces a critical security vulnerability: the Replay Attack. This flaw allows an attacker to intercept and “replay” requests, potentially bypassing the foundational web principle of idempotency.
This article provides a deep dive into the mechanics of 0-RTT, the anatomy of the replay flaw, and how developers can secure their backends without sacrificing the performance gains of the modern web.
1. The Need for Speed: Why HTTP/3 and 0-RTT Exist
To understand the flaw, we must first understand the problem it solves. In traditional HTTP/1.1 and HTTP/2 (running over TCP and TLS 1.2), establishing a secure connection is a multi-step “conversation”:
- TCP Handshake: SYN, SYN-ACK, ACK (1 Round-Trip)
- TLS Handshake: Exchanging certificates and keys (2 Round-Trips)
- HTTP Request: Finally sending the data (1 Round-Trip)
This adds up to 3-4 round-trips before the user sees a single byte of content. On high-latency mobile networks (3G/4G/5G) or satellite links, this delay is perceptible and frustrates users.
The QUIC Revolution
QUIC (Quick UDP Internet Connections), the backbone of HTTP/3, merges the transport and cryptographic handshakes.
- 1-RTT Handshake: For a first-time visitor, QUIC establishes a secure connection in just one round-trip
- 0-RTT Resumption: For a returning visitor, QUIC goes a step further. It uses a “Session Ticket” from a previous visit to encrypt data immediately. The client sends its data (like a GET or POST request) alongside the very first packet of the handshake
- Performance Gain: 0 ms of handshake latency. The data is “just there”
2. The High-Speed Flaw: What is a 0-RTT Replay?
The security weakness of 0-RTT lies in its “Early Data.” Unlike a standard handshake where the server and client agree on a unique, fresh key for every session, 0-RTT relies on a pre-shared key (PSK) derived from a previous session.
The Mechanism of the Attack
Because the “Early Data” is sent before the server has had a chance to confirm it is communicating with a fresh, live client, an attacker can perform the following:
- Interception: An attacker sits on the network (e.g., a compromised public Wi-Fi or a malicious router) and captures the 0-RTT packets sent by a user
- Buffering: The attacker stores these packets
- Replay: The attacker sends the exact same packets to the server again—potentially seconds or minutes later
Why Standard Encryption Doesn’t Stop This
You might think, “But the data is encrypted!” Yes, it is. But the attacker doesn’t need to decrypt the data to cause harm. They only need the server to accept and process it. If the packet contains a command like “Pay $100 to Alice,” replaying it twice results in a $200 transfer.
3. Bypassing Idempotency: The Core Danger
In web architecture, Idempotency is the property where an identical request can be made multiple times without changing the result beyond the initial application.
- GET, HEAD, OPTIONS: Generally considered idempotent. Refreshing a page shouldn’t change data on the server
- POST, PATCH, DELETE: Generally non-idempotent. Sending a “Submit Order” POST request twice should not result in two charges
The 0-RTT flaw effectively turns non-idempotent requests into a weapon.
Real-World Scenarios
- Financial APIs: Replaying a
/api/v1/transferrequest. Even if the body is encrypted, the server sees a valid cryptographic blob and executes the transfer again - E-commerce: Replaying a “Purchase” button click. The user might end up with two orders and two charges
- State Changes: Replaying a request to change a password or update a shipping address
- Social Media: Replaying a “Post Comment” or “Like” request, leading to spam-like duplication
4. Anatomy of the 0-RTT Handshake (Technical View)
To defend against the attack, developers must understand the packet flow defined in RFC 9001 and TLS 1.3.
The Normal 0-RTT Flow
- Previous Session: Client and Server establish a connection. The server sends a NewSessionTicket
- Resumption: The client wants to reconnect
- Client Packet: The client sends a QUIC packet containing a ClientHello and an Extension: early_data
- Early Data: Within the same packet, the client includes the HTTP/3 request (e.g., POST /pay)
- Server Processing: The server receives the packet, recognizes the session ticket, decrypts the early_data, and sends it to the backend
The Attack Flow
- Attacker: Captures the “Client Packet” from step 3
- Server: Processes the request (Success)
- Attacker: Sends the same “Client Packet” 10 seconds later
- Server: (If unprotected) Sees a valid session ticket, decrypts the data, and processes the request again (Success/Replay)
5. Mitigation Strategies: Securing the Speed
The IETF and major cloud providers (Cloudflare, Akamai, AWS) have developed several layers of defense.
Layer 1: Protocol-Level Restrictions (RFC 8470)
RFC 8470 introduces the Early-Data header and the 425 Too Early status code.
- The Header: When a load balancer or proxy (like Nginx) passes a 0-RTT request to your backend, it must add the header
Early-Data: 1 - The Response: If your backend determines that the request is “unsafe” (e.g., a POST request that isn’t idempotent), it should respond with
425 Too Early. This tells the client: “I won’t process this until the handshake is fully finished. Please retry after 1-RTT”
Layer 2: Strike Registers and Bloom Filters
To prevent the same ticket from being used twice, servers can implement a Strike Register.
- How it works: The server stores a record of every unique “Initial” packet or session ticket it has seen within a specific time window
- The Problem: In a distributed environment (multiple data centers), keeping this list synchronized in real-time is extremely difficult. Most providers use Bloom Filters—a memory-efficient way to check if an item has been seen before with a small chance of false positives (but zero false negatives)
Layer 3: Application-Level Idempotency Keys
The most robust defense is built into the application logic itself. Idempotency Keys (popularized by Stripe) involve the client generating a unique UUID for every state-changing request.
- Client: Sends
Idempotency-Key: 7b2a-4f91...in the headers - Server: Stores the result of that key for 24 hours. If a replayed request arrives with the same key, the server simply returns the cached result of the first request instead of executing the logic again
6. Implementation Guide: Enabling 0-RTT Safely
If you are using a major CDN or web server, here is how to handle 0-RTT correctly.
Cloudflare
Cloudflare allows 0-RTT but takes a conservative approach. By default, it only enables 0-RTT for GET requests with no query parameters.
- To enable for APIs: You can enable it in the “Speed” settings, but Cloudflare will automatically append
Early-Data: 1to the request. Your origin server must check for this header
Nginx (with QUIC/HTTP/3)
In Nginx, you can enable 0-RTT using the ssl_early_data directive.
server {
listen 443 quic reuseport;
ssl_protocols TLSv1.3;
ssl_early_data on; # Enables 0-RTT
location /api/secure {
# Check if the request is early data
if ($ssl_early_data = "1") {
# Optionally reject or handle specifically
add_header X-Handshake-Status "Early";
}
proxy_pass http://backend;
}
}
Backend Code Example (Node.js/Express)
app.post('/api/transfer', (req, res) => {
// Check if the request arrived via 0-RTT early data
if (req.headers['early-data'] === '1') {
// Reject and ask the client to retry after full handshake
return res.status(425).send('Too Early');
}
// Proceed with logic
const { amount, to } = req.body;
processTransfer(amount, to);
});
7. SEO Optimization: Best Practices for 0-RTT Security
If you are a developer or security researcher writing about this topic, keep these SEO and technical keywords in mind:
- Keywords: HTTP/3, QUIC Security, 0-RTT Replay Attack, TLS 1.3 Resumption, Idempotency Bypass, RFC 8470, 425 Too Early
- Internal Linking: Link to articles about “TLS 1.3 Handshakes,” “API Security Best Practices,” and “UDP vs TCP Performance”
- Meta Description: “Learn how HTTP/3 0-RTT performance boosts can lead to Replay Attacks. Discover how to protect your APIs from idempotency bypass using RFC 8470 and strike registers”
8. Summary: Speed vs. Security
The trade-off between 0-RTT and security is a classic example of “performance at any cost.” While 0-RTT can shave 100-500ms off a page load for returning users, it opens a window for attackers to manipulate stateful transactions.
The Golden Rules for 0-RTT
- Only for GET: Never allow 0-RTT for POST, PUT, or DELETE unless you have robust idempotency keys
- Watch the Headers: Configure your load balancer to pass the
Early-Dataheader and ensure your backend honors it - Use 425 Too Early: Don’t be afraid to tell the client to wait for 1-RTT. The performance loss of a single round-trip is better than the financial loss of a replayed transaction
As we move toward a “QUIC-first” internet, understanding these subtle transport-layer flaws is no longer optional—it is a requirement for building resilient, high-speed applications.