MODRACXKENNETH D'SILVA

← Archive & Insights

HTTP/2 Rapid Reset (CVE-2023-44487): The 398M RPS DDoS Exploit

Abusing the HTTP/2 RST_STREAM frame to generate 398 million requests per second. How a protocol-level architectural design flaw crippled edge proxies, CDNs, and web servers worldwide.

By Kenneth D'SilvaReading Time: 26 min readCategory: Security & Compliance

1. October 2023: The Largest DDoS Attack in Internet History

In October 2023, Google, Cloudflare, and Amazon Web Services revealed they had mitigated the largest distributed denial-of-service (DDoS) attacks ever recorded in history. Google observed peaks exceeding 398 million requests per second (RPS) — more than seven times the volume of the previous record. The attack was driven not by massive botnets of millions of infected devices, but by a relatively modest cluster of just 20,000 compromised machines exploiting a structural design flaw in the HTTP/2 protocol standard: CVE-2023-44487, dubbed HTTP/2 Rapid Reset.

2. CVE Metadata Overview

CVE ID CVSS Score Protocol Layer Vulnerability Type Impact
CVE-2023-44487 7.5 (High) HTTP/2 Transport Framing (RFC 7540) Protocol Loop Denial of Service (Stream Cancellation Flood) Massive CPU/Memory exhaustion across CDNs, reverse proxies, and origins

3. Technical Root Cause: The RST_STREAM Multiplexing Loophole

HTTP/1.1 required a separate TCP connection for every parallel request (or pipelining, which was rarely supported). HTTP/2 introduced multiplexing: multiple logical request/response streams interleaved over a single persistent TCP connection. To prevent clients from opening infinite concurrent requests, servers enforce the SETTINGS_MAX_CONCURRENT_STREAMS parameter (typically 100).

HTTP/2 also introduced the RST_STREAM frame, allowing either client or server to immediately cancel an active stream without terminating the underlying TCP connection (useful when a user navigates away before an image finishes downloading).

The critical design oversight in RFC 7540 was that when a client sent a request (HEADERS frame) immediately followed by a cancellation (RST_STREAM frame):

  1. The server allocated request context memory, parsed HTTP headers, and initiated upstream proxy routing.
  2. The server received the RST_STREAM frame and closed the stream locally.
  3. The active stream count decremented back down, immediately freeing the concurrency slot.

Client                                  Server
  |                                        |
  |--- HEADERS (Stream 1: GET /cart) ----->|  (Allocates request buffer)
  |--- RST_STREAM (Stream 1: CANCEL) ----->|  (Frees concurrency slot, burns CPU)
  |--- HEADERS (Stream 3: GET /cart) ----->|  (Slot is open! Allocates buffer)
  |--- RST_STREAM (Stream 3: CANCEL) ----->|  (Frees concurrency slot)
  |--- HEADERS (Stream 5: GET /cart) ----->|  ... Repeated 100,000x per second

Because the client never waited for the server's response before sending RST_STREAM, the client could send hundreds of thousands of request/cancel pairs in a single TCP window. The server burned huge amounts of CPU cycles allocating and tearing down request structures without ever hitting concurrency thresholds.

4. Detection and Edge Mitigation


# Nginx access log inspection: Search for massive 499 (Client Closed Request) status codes
awk '$9 == 499 {print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head -20

5. Remediation & Web Server Configuration

  1. Nginx: Upgrade to Nginx 1.25.3+ or apply the keepalive_requests restriction:
    
    # Limit maximum requests per keepalive connection to mitigate rapid reset loops
    keepalive_requests 1000;
    http2_max_concurrent_streams 128;
    
  2. Cloudflare / Fastly: Enable Edge Rapid Reset heuristic protection, which terminates TCP connections exceeding anomalous RST_STREAM-to-DATA ratios.
  3. Go Applications: Update golang.org/x/net/http2 to version 0.17.0+, which automatically rate-limits client-initiated stream resets.

Suggested & Related Reading

Explore related engineering guides from Kenneth D'Silva: