VULNERABILITY

CVE-2026-23918: Tearing Down the Apache mod_http2 Early-Reset Double-Free

Ishmael Chibvuri — Cybersecurity Strategist

Ishmael Chibvuri, CISM

Cybersecurity Strategist

MAY 14, 2026
12 MIN READ
CVE-2026-23918: Tearing Down the Apache mod_http2 Early-Reset Double-Free

Apache HTTP Server is still a load-bearing piece of the public web, and mod_http2 is the module most installations rely on for HTTP/2. CVE-2026-23918, disclosed on May 5, 2026, is a double-free in that module's stream cleanup path — reachable, unauthenticated, with the classic preconditions of a real RCE primitive on the heap. A working proof-of-concept is already public, the fix shipped in Apache 2.4.67, and "disable mod_http2" is the only credible mitigation if you can't upgrade today.

TL;DR

  • CVE-2026-23918 is a double-free in Apache httpd 2.4.66 mod_http2, specifically in the stream cleanup path of h2_mplx.c. The bug triggers when a client sends an HTTP/2 HEADERS frame immediately followed by RST_STREAM with a non-zero error code on the same stream, before the multiplexer has registered the stream.
  • The vulnerability was introduced in Apache HTTP Server 2.4.66 following allocator-related changes inside mod_http2 v2.0.33, and the only complete fix is to upgrade the Apache HTTP Server to version 2.4.67 or later, which contains the official fix for CVE-2026-23918.
  • CVSS 8.8, unauthenticated, network-reachable. Apache classifies impact as "Double Free and possible RCE." Treat it as RCE until your environment proves otherwise.
  • If upgrading is not immediately possible, disabling mod_http2 will prevent exploitation. Falling back to HTTP/1.1 is the only safe interim posture.
  • As of the public advisory date (May 5, 2026), there are no confirmed reports of active exploitation in production environments, but a working PoC is already on GitHub and the trigger sequence is trivially scriptable from any HTTP/2 library.

Background

Apache httpd is still the long-tail of the public web — particularly for hosting providers, on-prem appliances vending management UIs over TLS, and the surprisingly long list of vendor products that statically embed httpd as their HTTP front-end. Anything you've ever scanned that returned Server: Apache/2.4.x in a banner is a candidate target.

mod_http2 is the module that bolts HTTP/2 (RFC 9113) onto httpd. Architecturally it sits as a connection-level filter that demultiplexes incoming HTTP/2 frames into per-stream pseudo-requests, hands them off to httpd's normal request pipeline, then re-muxes responses back onto the connection. The component that owns this bookkeeping is h2_mplx — the multiplexer — and its state machine lives in mod_http2/h2_mplx.c.

That state machine has been a recurring source of memory-safety pain. The 2023 "HTTP/2 Rapid Reset" disclosure (CVE-2023-44487) burned every major HTTP/2 implementation by abusing the asymmetry between cheap client-side stream cancellation and expensive server-side stream setup. CVE-2026-23918 is in the same family — same protocol primitive (RST_STREAM), same window of server-side state — but the bug class is worse. Rapid Reset was a resource-exhaustion DoS. This one corrupts the heap.

The reason the regression landed at all is a refactor. mod_http2 moves to version 2.0.39 [in 2.4.67], which strips out the stream specific memory allocator after third party module conflicts caused widespread instability. In other words, somewhere between mod_http2 2.0.32 and 2.0.33, upstream added a per-stream allocator that interacts badly with the cleanup ordering when a stream is reset before it has been fully registered with the multiplexer. The fix in 2.0.39 doesn't patch the double-free in place — it removes the allocator entirely.

Technical breakdown

The trigger sequence

Per RFC 9113, an HTTP/2 client opens a stream by sending a HEADERS frame with a fresh, odd-numbered stream ID. It can cancel that stream at any time by sending an RST_STREAM frame on the same stream ID with an error code. Normal clients use this for things like aborting a fetch when the user navigates away.

In CVE-2026-23918, the double-free is triggered in Apache's HTTP/2 stream handling code in mod_http2, specifically in the stream cleanup path of h2_mplx.c. The vulnerability is triggered during an "early stream reset" sequence when a client sends an HTTP/2 HEADERS frame immediately followed by RST_STREAM with a non-zero error code on the same stream, before the multiplexer has registered the stream.

The "before the multiplexer has registered the stream" qualifier is doing all the work. Under normal flow, the connection-level reader thread parses HEADERS, allocates a per-stream pool, inserts the stream into m->streams (the multiplexer's hash), and only then hands work off. RST_STREAM, when it arrives later, looks up the stream by ID, marks it for cleanup, and a worker thread releases the pool.

The race window is the gap between allocation and insertion. If RST_STREAM arrives inside that window, two code paths converge on the same partially-initialized stream object:

  1. The HEADERS path, which completes initialization and then sees that the stream has been marked aborted, so it runs cleanup.
  2. The RST_STREAM path, which observes the stream as cleanable and runs its own cleanup.

Both call into the per-stream allocator teardown, which frees the same pool. Hence CWE-415 (Double Free).

Why it's exploitable beyond DoS

A naive double-free in apr_pool teardown will usually land on a SIGSEGV inside the worker — which is exactly what the public PoC observes. If you time it right, you can get the server to try and clean up the same stream twice, which usually leads to a SIGSEGV and a worker crash. That alone is a remote unauthenticated DoS against a worker process. With multi-process MPMs the parent will respawn, but with the right cadence you can pin workers below the configured MaxRequestWorkers and stall the listener.

The "possible RCE" framing isn't marketing. Double-free primitives become exploitable when the freed allocation can be reclaimed by attacker-controlled data before the second free runs the allocator's metadata writes. In the mod_http2 case the request body, HPACK dynamic table, and header field allocations all flow through the same connection arena, giving an attacker plenty of opportunity to spray controlled bytes between the two frees on the connection thread. The pool teardown path then writes through corrupted free-list metadata, which is the standard precursor to a write-what-where on glibc malloc and a function-pointer overwrite on jemalloc-backed builds.

Whether someone has actually weaponized it to code execution in the wild is a separate question — see "Impact." But "possible RCE" should be your planning assumption.

Affected configurations

CVE-2026-23918 affects systems running Apache HTTP Server 2.4.66 with mod_http2 enabled under multi-threaded MPM configurations such as worker and event. Risk increases in environments where HTTP/2 is accessible to external or untrusted clients.

A few non-obvious deployment notes:

  • The prefork MPM is not in scope in the same way because the race window relies on threading inside a single process. If you're still on prefork, you have other problems but not this one.
  • mod_proxy_http2 (back-end HTTP/2 to an upstream) runs the same multiplexer code. If your edge terminates HTTP/2 but proxies HTTP/2 to a vulnerable origin, the origin is exposed via attacker-influenced framing that the edge passes through.
  • TLS termination doesn't help. ALPN-negotiated h2 runs identically regardless of cipher suite or client cert.
  • Docker images: anything based on the official httpd:2.4.66 tag, including httpd:2.4 if it was pulled after the 2.4.66 release date and before 2.4.67 hit Docker Hub.

A minimal trigger in Python

The exploit primitive is small. Using hyper-h2 (sans-IO HTTP/2), the entire pre-condition is two frames on a single connection, sent without waiting for SETTINGS ack and without giving the server time to dispatch the request:

# Authorized testing only. Crashes worker processes.
import socket, ssl, h2.connection, h2.config
 
ctx = ssl.create_default_context()
ctx.set_alpn_protocols(["h2"])
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
 
sock = ctx.wrap_socket(socket.create_connection(("target", 443)),
                       server_hostname="target")
c = h2.connection.H2Connection(h2.config.H2Configuration(client_side=True))
c.initiate_connection()
sock.sendall(c.data_to_send())
 
# Open a stream and reset it in the same flight, before the server
# multiplexer has a chance to register stream 1.
c.send_headers(1, [
    (":method", "GET"), (":scheme", "https"),
    (":authority", "target"), (":path", "/"),
])
c.reset_stream(1, error_code=8)  # CANCEL, non-zero error code
sock.sendall(c.data_to_send())

Repeating this on fresh connections at moderate concurrency reliably segfaults workers on a vulnerable build. The PoC published on GitHub adds a "slow-drip" mode that paces the resets to evade naive rate-limiters and a passive fingerprinting mode that flags vulnerable banners without sending the trigger.

The fix

The 2.4.67 changeset bumps the bundled mod_http2 to 2.0.39 and eliminates the per-stream allocator that made the cleanup ordering ambiguous. Streams now share the connection pool with conventional refcounted cleanups, which is slower per-stream but eliminates the class of bug. There is no compiler flag or runtime knob that retro-fixes 2.4.66; there is no configuration-level workaround that eliminates the vulnerability while preserving HTTP/2 functionality.

Impact

Three things make this worse than the headline CVSS suggests.

1. Banner-grep targeting is trivial. Apache prints its version in Server: by default. Shodan and Censys queries for apache/2.4.66 will hand attackers a list of internet-exposed candidates in seconds. Many appliance vendors embed httpd and forget to disable ServerTokens, so management interfaces on industrial gear, load balancers, and storage controllers are equally enumerable.

2. The patch window is narrow. CVE-2026-23918 affects Apache 2.4.66. It was patched in 2.4.67 earlier this month (May 2026). It's a race condition in how Apache handles early RST_STREAM frames. 2.4.66 only shipped a few weeks before disclosure, so the population of vulnerable hosts is people who pulled the latest release into production quickly — i.e., the orgs with the best patch hygiene. Distro builds (RHEL, Ubuntu LTS) are largely safe because they're still on older 2.4.x trains, but custom builds, container images, and anything compiled from upstream tarballs are squarely in scope.

3. The exploit class scales horizontally. Unlike Rapid Reset, which needed sustained throughput to cause damage, this one needs exactly two frames per connection. WAFs that throttle on RST_STREAM volume (a common Rapid Reset mitigation) won't see this — the attacker only needs one RST_STREAM per connection to corrupt the heap.

As of the public advisory date (May 5, 2026), there are no confirmed reports of active exploitation in production environments. However, proof-of-concept code has been demonstrated in laboratory conditions, and the low barrier to triggering the bug means weaponization is a matter of days, not weeks. Treat this as "exploited soon" rather than "exploited never."

Defenses

Patch path (do this first)

# Build from source
curl -O https://downloads.apache.org/httpd/httpd-2.4.67.tar.bz2
# Verify against the ASF release signing keys, not just sha256
gpg --verify httpd-2.4.67.tar.bz2.asc
 
# Container path: pin the exact patched digest, not the floating tag
docker pull httpd:2.4.67
docker inspect --format='{{index .RepoDigests 0}}' httpd:2.4.67

If you're on a distro package, check apachectl -v and httpd -M | grep http2. The CVE only applies if http2_module is loaded.

Emergency mitigation if you can't patch in 24 hours

Disable mod_http2 cleanly. Don't just remove Protocols h2 h2c http/1.1 — the module's code paths still load:

# /etc/httpd/conf.modules.d/00-base.conf (RHEL-style)
# Comment out the LoadModule line entirely:
# LoadModule http2_module modules/mod_http2.so
 
# /etc/apache2/mods-enabled/  (Debian-style)
sudo a2dismod http2
sudo systemctl restart apache2

Clients will fall back to HTTP/1.1 over the same TLS connection. You'll lose multiplexing and HPACK; you'll keep your worker processes.

Detection — log-level

mod_http2 logs early stream resets at info. Crank it temporarily:

LogLevel http2:info

Then look for streams that go from "opened" to "reset" with no intervening request processing. The signal isn't pretty but the absence of a request received line between an opened stream and a reset is suspicious.

Also instrument the OS. A spike of SIGSEGV on httpd workers is the cheapest exploitation signal you have:

# Live tail of worker crashes via journald
journalctl -u httpd -f | grep -E 'segfault|core dump|signal 11'
 
# Or via systemd-coredumpctl
coredumpctl list httpd --since "1 hour ago"

Detection — Sigma rule

title: Apache httpd Worker Repeated SIGSEGV (Possible CVE-2026-23918)
id: 9a7d4b30-1d4f-4f0f-9e1b-2026239180a1
status: experimental
description: >
  Detects repeated worker crashes consistent with the mod_http2
  early-reset double-free.
logsource:
  product: linux
  service: syslog
detection:
  segfault:
    Message|contains|all:
      - 'httpd'
      - 'segfault'
  timeframe: 5m
  condition: segfault | count() by host > 3
falsepositives:
  - Unrelated module crashes (mod_php, mod_wsgi)
level: high

Detection — KQL for Azure / Defender for Cloud

DeviceProcessEvents
| where Timestamp > ago(1d)
| where InitiatingProcessFileName == "httpd" or InitiatingProcessFileName == "apache2"
| where ProcessCommandLine has_any ("coredump", "abrt")
| summarize crashes = count() by DeviceName, bin(Timestamp, 5m)
| where crashes >= 3

Network-layer mitigations

If you sit behind a modern reverse proxy that terminates HTTP/2 (Cloudflare, NGINX with http2_module, Envoy, HAProxy), the proxy will not forward the raw HEADERS+RST_STREAM frame sequence to the Apache origin — it terminates and re-originates as HTTP/1.1 or its own HTTP/2 implementation. This effectively neutralizes the bug for proxied origins, assuming the proxy itself isn't vulnerable.

Direct-to-origin Apache deployments and mod_proxy_http2 upstream-HTTP/2 paths do not get this protection.

A WAF rule that drops connections where RST_STREAM arrives within N milliseconds of HEADERS on the same stream will catch the trivial PoC. Tune N conservatively — legitimate browser cancellation can be fast, but rarely sub-50ms on the same connection without intervening DATA.

Hunting questions to ask now

  • Which of my external-facing Apache instances are on 2.4.66? (curl -sI https://host | grep -i server, plus internal CMDB cross-check.)
  • Which container images in my registry pin httpd:2.4.66 or httpd:2.4 last pulled in the affected window?
  • Do any vendor appliances on my network embed httpd? Check management UIs on non-standard ports.
  • Are my mod_http2 worker crash rates normal over the last 14 days, or is somebody already testing?

The honest defender posture: patch to 2.4.67, and if that's blocked, disable mod_http2 today. There's no clever firewall rule that beats removing the vulnerable code path.

Sources

  1. Critical Apache HTTP/2 Flaw (CVE-2026-23918) Enables DoS and Potential RCE — The Hacker News
  2. CVE-2026-23918: Apache HTTP/2 Double-Free Vulnerability with Possible RCE — Security Boulevard
  3. Apache CVE-2026-23918 HTTP/2 Double-Free RCE Explained — Hadrian
  4. CVE-2026-23918: Apache HTTP Server HTTP/2 Double Free With Possible RCE — SOCRadar
  5. CVE-2026-23918: Apache HTTP Server Double-Free RCE — appsecmaster
  6. Critical Apache HTTP Server 2.4.66 Vulnerability (CVE-2026-23918) — Rescana
  7. Apache HTTP Server 2.4.67 released — linuxcompatible.org
  8. xeloxa/CVE-2026-23918-Apache-H2-PoC — GitHub

Discussion

Continue the conversation

Share your take, ask a follow-up question, or push back on the analysis — head over to LinkedIn where the discussion lives.

Discuss on LinkedIn

Related Deep Dives