In August 2023, a botnet of 20,000 machines generated 201 million requests per second against Cloudflare. No link saturation. No volumetric flood. Just RST_STREAM frames: the HTTP/2 cancellation signal that every server is designed to honor instantly.
CVE-2023-44487 (CVSS 7.5) turns stream multiplexing efficiency into a DoS weapon. API gateways that terminate HTTP/2 and forward to HTTP/1.1 backends amplify the attack. The backend processes the full request before the RST_STREAM cancellation is handled by the gateway. The real mitigation is enforced configuration, not patching alone.
HTTP/2 Stream Multiplexing: Why RST_STREAM Is Cheap for Clients
HTTP/2 allows multiple concurrent streams over a single TCP connection. The server announces the limit via SETTINGS_MAX_CONCURRENT_STREAMS during the handshake. Client-initiated streams use odd-numbered IDs, incremented by 2. The server tracks per-stream state from the first frame received.
For each open stream, the server allocates HPACK context, flow control windows, and data buffers. This cost is paid before any response data is generated. The client sends a 13-byte RST_STREAM frame (a 4-byte error code payload on a 9-byte frame header) to cancel. The server has already paid the full allocation and header decompression cost.
RST_STREAM closes the stream without counting against the concurrent stream limit. The slot opens immediately for a new stream. The asymmetry is architectural: SETTINGS_MAX_CONCURRENT_STREAMS does not limit the rate of stream opens and cancellations, only how many remain open simultaneously. CVE-2023-44487, published October 10, 2023 with CVSS 7.5, classifies the issue as CWE-400 (Uncontrolled Resource Consumption). NVD confirmed active exploitation from August through October 2023. Active exploitation before disclosure indicates the attack pattern was independently discovered by malicious actors, not derived from public research.
The Attack: One Machine, Millions of Requests Per Second
The attack cycle requires no application state, authentication, or large payloads. The attacker opens stream N, sends a HEADERS frame with minimal payload and immediately sends RST_STREAM for stream N. HPACK compresses repetitive HTTP headers to a few bytes, minimizing bandwidth cost per cycle. The server allocated resources, started processing, and cancelled. The attacker opens stream N+2 and repeats.
The server never reaches SETTINGS_MAX_CONCURRENT_STREAMS because streams close before the threshold is hit. The rate of cycles per second is the vector. The number of active streams at any moment stays below the configured limit.
Google absorbed a peak of 398 million requests per second, 8.5x the prior DDoS record. That record was 46 million rps, set in 2022. Cloudflare mitigated 201 million rps from a 20,000-machine botnet: 10,000 rps per machine on average. AWS mitigated 155 million rps. All 3 providers disclosed coordinated on October 10, 2023. The zero-day window ran from late August: approximately 6 weeks of exploitation before any public disclosure. Cloudflare estimated that 2 minutes of peak traffic generated more requests than Wikipedia received during the entire month of September 2023. The attack requires no amplification from third-party servers. It exploits the allocation cost of the target server itself.
Gateway Amplification: HTTP/2 Frontend Dispatches to HTTP/1.1 Backend Before RST Arrives
Most API gateways terminate HTTP/2 client connections and forward requests to backend services via HTTP/1.1 or a separate HTTP/2 connection pool. Stream multiplexing exists only between client and gateway. Between gateway and backend, each request uses an independent HTTP/1.1 connection or pool slot.
The gateway receives the open stream and client headers. It starts forwarding the request to the upstream. Then it receives RST_STREAM from the client. The backend has already started processing. RST_STREAM does not propagate cancellation to the backend: the signal cancels only the gateway-to-client response path.
The backend opens a database connection, executes query planning, runs authentication middleware. No corresponding client at the other end. Cloudflare's post-mortem describes the pipe between the TLS proxy and business logic reaching saturation, generating 502 errors as internal server capacity was exhausted.
With NGINX as an API gateway, http2_max_concurrent_streams limits simultaneous active streams. An RST flood that cycles through stream IDs bypasses a naive active-stream counter. The stream closes before being counted against the limit, but the request was already dispatched to upstream. Network latency between the frontend proxy and backend creates the amplification window. Setting the upstream timeout shorter than the backend processing window creates a second layer of defense. When the client cancels, the timeout expires before the backend completes, releasing the resource server-side.
CVE Record: Go, Envoy, Apache Tomcat, Jetty, NGINX
CVE-2023-44487 is the protocol-level CVE (CVSS 7.5). Every HTTP/2 stack received its own downstream CVE. Patching only the OS package is not sufficient when the application framework is independently vulnerable.
CVE-2023-39325 covers Go net/http and golang.org/x/net. Fixed versions are Go 1.21.3 and x/net 0.17.0. Envoy was affected in versions 1.27.0, 1.26.4, 1.25.9, and 1.24.10; fixed in 1.27.2, 1.26.6, 1.25.11, and 1.24.12 (GHSA-jhv4-f7mr-xx76).
Apache Tomcat was affected in versions 8.5.0 through 8.5.93, 9.0.0 through 9.0.80, and 10.1.0 through 10.1.13. Fixed in 8.5.94, 9.0.81, and 10.1.14. Eclipse Jetty was affected in versions 9.3.0 through 9.4.52, 10.0.0 through 10.0.16, and 11.0.0 through 11.0.16, patched across all active branches. NGINX received a fix in version 1.25.3, limiting new streams per event loop to 2x http2_max_concurrent_streams. CISA issued an alert on October 10, 2023, the same day as the coordinated disclosure, recommending patches and configuration hardening. Red Hat published security bulletin RHSB-2023-003 covering all affected RHEL packages with an Important/CVSS 7.5 classification across RHEL 8 and RHEL 9.
Configuration Mitigations: The Patch Is Necessary but Not Sufficient
3 configuration controls reduce the attack surface before and independently of the patch.
For NGINX, reduce http2_max_concurrent_streams to 32 for API gateway deployments with connection pooling. The default of 128 allows many simultaneous open/RST cycles before any rate control engages. Add limit_conn and limit_req per source IP to complement stream-level controls.
For Envoy, overload.premature_reset_total_stream_count defaults to 500 premature resets per connection before closing. Reducing this value shortens the lifetime of abusive connections. http.max_requests_per_io_cycle set to 1 distributes CPU load fairly across connections per event loop cycle. For both Envoy and HAProxy, max_requests_per_connection forces connection recycling after N requests, limiting how many RST cycles fit within one connection's lifetime.
At the listener, rate-limiting new HTTP/2 connections per source IP per second reduces botnet amplification before stream-level controls engage. None of these controls prevent the attack from a sufficiently distributed source. The goal is to raise the cost per successful request for the attacker.
Detection and Monitoring
The access log signature: high volume of 499 status codes (client closed connection) with near-zero response body size, concentrated on the same source IPs. The request-to-response ratio skews toward requests with minimal or absent body.
Backend request logs show high load not reflected in gateway access logs. RST_STREAM cancels at the gateway after the request was dispatched to the backend; both log sets become desynchronized under attack. Monitoring the RST_STREAM rate per connection and closing connections above a threshold is Cloudflare's approach with the "IP Jail" mechanism. In NGINX logs, the combination of request_time below 0.01 seconds and status 499 in sequence from the same IP is the most reliable pattern. An alert with a threshold of 50 events per minute per IP reduces detection time from hours to seconds.
The MAGO Intel tool (intel.mago.team) tests API gateway configurations for stream concurrency limits and connection recycling enforcement during security assessments.
The mitigation controls existed before CVE-2023-44487. http2_max_concurrent_streams was in NGINX since HTTP/2 support was added. The vulnerability is not in the configuration control: it is in the deployment default that leaves it at 128 or uncapped.
Top comments (0)