Apache HTTP Server 2.4.66 HTTP/2 Double-Free (CVE-2026-23918): A Single-Version Bug With RCE Potential
Introduction
The Apache Software Foundation has shipped httpd 2.4.67 to fix CVE-2026-23918, a double-free in mod_http2's stream cleanup logic that can be triggered remotely with crafted HTTP/2 traffic and is rated CVSS 8.8 with explicit "possible RCE" wording in the upstream advisory. Only httpd 2.4.66 is affected — the bug was introduced in that release and patched a day after report — but 2.4.66 is exactly the version most fast-moving operators have been running for the past four months.
What Happened
The flaw lives in the early-reset path of HTTP/2 stream handling. When a client opens a stream and resets it before the server has finished cleaning up, multiple code paths inside mod_http2 can each attempt to add the same stream pointer onto the master connection's purge list (m->spurge). When the purge list is then processed, the same memory block is freed twice. On modern allocators that double free either crashes the worker outright (DoS) or, more interestingly, gets the allocator into a state where a subsequent allocation hands an attacker-controlled pointer back to the server — the textbook setup for heap-grooming code execution.
The bug was reported by Bartlomiej Dmitruk (striga.ai) and Stanislaw Strzalkowski (isec.pl) on December 10, 2025. Apache's developers had a fix (add_for_purge() deduplication helper, commits r1930444 and r1930796) in the tree the very next day; coordinated public disclosure waited until May 4, 2026. The advisory affects only 2.4.66, because that is the version the regression shipped in. Anything older and anything 2.4.67 or newer is fine.
There are two practical consequences of "only one version is affected." First, distributions that follow tip-of-tree (Alpine, Arch, rolling images, fresh containers built since December) almost certainly have 2.4.66 baked in. Second, anyone who deferred upgrading from 2.4.65 is, ironically, safe — until they take the routine step of pulling the latest minor.
Why It Matters
mod_http2 is enabled by default in modern Apache builds and is the front-door HTTP/2 implementation behind a non-trivial slice of the public internet. The exploit requires no authentication; any client that can negotiate HTTP/2 with the server can drive the broken code path. Even if RCE proves hard in the wild, a reliable remote crash inside the worker process is, on its own, a cheap denial-of-service vector against any public-facing 2.4.66.
The other reason this story matters is the disclosure cadence. Five months elapsed between fix and public CVE so the patch could roll through every downstream packager. That window is the kind of thing red teams use to map who shipped what when — anyone running httpd 2.4.66 from a base image they pulled in February has been running an exploitable HTTP/2 stack for a quarter without knowing it.
Who Is Affected
- Any Apache HTTP Server build at version
2.4.66, source-built or distro-packaged, on any operating system. - Container images (
httpd:2.4.66,httpd:2.4, downstream images that pin2.4.66) and rolling-distro packages built between mid-December 2025 and the May 4 disclosure. - Reverse-proxy and frontend deployments using
httpdwithmod_http2enabled (the default), including most virtual-host fleets serving HTTPS to browsers. - Indirectly, any application sitting behind an exposed
2.4.66reverse proxy — a remote crash there takes down the front end regardless of how healthy the backend is.
How to Protect Yourself
First, find every 2.4.66 you actually run, not just the ones you remember:
# system packages
apachectl -v
# look for: Server version: Apache/2.4.66 (...)
# RPM-based hosts
rpm -qa | grep -iE 'httpd|apache2'
# Debian/Ubuntu
dpkg -l 'apache2*' 'libapache2*' | awk '/^ii/{print $2,$3}'
# every container image in the local Docker daemon that uses 2.4.66
docker image ls --format '{{.Repository}}:{{.Tag}}' \
| xargs -I{} sh -c 'echo "=== {} ==="; docker run --rm --entrypoint apachectl {} -v 2>/dev/null | grep "Server version"'
Upgrade to 2.4.67 from your packager or rebuild the container:
# Debian/Ubuntu
apt-get update && apt-get install --only-upgrade apache2 apache2-bin
# RHEL/Rocky
dnf upgrade httpd mod_http2
# Alpine
apk update && apk upgrade apache2
# Container: rebuild against the patched base
docker pull httpd:2.4.67
# or pin a digest in your Dockerfile:
# FROM httpd:2.4.67@sha256:<your-pinned-digest>
Restart cleanly and confirm the running process is the new build, not a leftover worker:
systemctl restart apache2 || systemctl restart httpd
ss -tlnp 'sport = :443 or sport = :80'
# verify the server header on the wire
curl -sI --http2 https://localhost/ | grep -i server
If you cannot patch this hour, disable HTTP/2 as a temporary workaround. The bug is in mod_http2's early-reset cleanup; downgrading to HTTP/1.1 closes the path entirely:
# /etc/apache2/mods-enabled/http2.conf or equivalent
# remove or comment:
# Protocols h2 h2c http/1.1
# and set:
Protocols http/1.1
# then disable the module:
# a2dismod http2 (Debian/Ubuntu)
# sed -i 's|^LoadModule http2_module|#LoadModule http2_module|' /etc/httpd/conf.modules.d/*.conf (RHEL)
apachectl configtest && apachectl graceful
For threat hunting, rapid stream resets are the smoking gun. A burst of RST_STREAM frames against a single connection is the trigger condition for the double free; the easiest signal is mod_http2 log noise plus worker crashes:
# enable info-level http2 logs temporarily
LogLevel http2:info
tail -F /var/log/apache2/error.log | grep -E 'AH034[0-9]{2}|H2_ERR_|RST_STREAM|spurge'
# evidence of crashes
journalctl -u apache2 --since '24 hours ago' | grep -iE 'segfault|core dumped|signal 11'
coredumpctl list apache2 httpd 2>/dev/null
If you maintain base images other teams consume, ship a pinned 2.4.67 digest today and bump the minimum-version requirement in your image-policy controller. Anything still pulling httpd:2.4 without a digest is currently a moving target.