Vulnerability Analysis

CVE-2026-23918: Apache HTTP/2 Double-Free Vulnerability Enables DoS and Remote Code Execution

Executive Summary

CVE-2026-23918 is a double-free memory corruption vulnerability in Apache HTTP Server 2.4.66's mod_http2 module that can be triggered remotely by sending a crafted HTTP/2 HEADERS + RST_STREAM sequence, resulting in a worker process crash (DoS). On Debian-derived systems and official Apache Docker images — where the Apache Portable Runtime (APR) uses an mmap-based allocator — attackers can escalate this into full Remote Code Execution (RCE). Active exploitation for DoS has been confirmed in the wild as of May 2026; organizations running Apache 2.4.66 should upgrade to 2.4.67 immediately or disable HTTP/2 as an interim control.


1. What Is This Vulnerability?

CVE-2026-23918 is a double-free flaw in the stream cleanup path of Apache's mod_http2 module, specifically in modules/http2/h2_mplx.c. A double-free occurs when the same memory address is passed to the allocator's free() function (or equivalent) more than once, corrupting the heap's internal state. In Apache's case, the second free hits an apr_pool_destroy() call on memory that has already been returned to the allocator — a condition that can produce crashes, memory leaks, or — under the right conditions — arbitrary code execution.

Root Cause: The Early Reset Race

The bug is activated by an "early stream reset" sequence: the client opens an HTTP/2 stream with a HEADERS frame and immediately sends RST_STREAM with a non-zero error code before the server's multiplexer has finished registering the stream. This is a legitimate HTTP/2 flow supported by RFC 9113 §5.4, so the server cannot simply reject it.

When this sequence arrives, two nghttp2 library callbacks fire back-to-back on the connection thread:

  1. on_frame_recv_cb (handling the RST_STREAM frame) → calls h2_mplx_c1_client_rst() → calls m_stream_cleanup(), which pushes the h2_stream * pointer onto the spurge (stream-purge) cleanup array.
  2. on_stream_close_cb (handling the stream close) → calls h2_mplx_c1_client_rst() → calls m_stream_cleanup() again for the same stream, pushing the same pointer onto spurge a second time.

Later, when c1_purge_streams() iterates the spurge array and calls h2_stream_destroy()apr_pool_destroy() on each entry, the second iteration destroys a pool that was already freed on the first pass — triggering the double-free.

/* Vulnerable path in h2_mplx.c (simplified) */
static void m_stream_cleanup(h2_mplx *m, h2_stream *stream)
{
    /* ... bookkeeping ... */
    H2_MPLX_STREAM_LIST_REMOVE(m, stream);
    /* BUG: stream can be pushed here even if already added */
    h2_iq_append(m->spurge, stream);   /* push to purge queue */
}

/* Later, in c1_purge_streams() */
while ((stream = h2_iq_shift(m->spurge)) != NULL) {
    h2_stream_destroy(stream);  /* second call = double-free */
}

Attack Vector

Exploitation is straightforward and requires no authentication:

# Minimal trigger (HEADERS + immediate RST_STREAM)
HEADERS frame  (stream id = 1, END_HEADERS)
RST_STREAM     (stream id = 1, error_code = CANCEL)

A public proof-of-concept (xeloxa/CVE-2026-23918-Apache-H2-PoC) implements both Rapid-RST (high-rate DoS) and Slow-Drip (stealthy low-rate DoS) modes, plus passive RCE/vulnerability detection for Apache 2.4.66 targets.

RCE Path (Debian / Docker)

On systems where APR uses the mmap allocator (the default on Debian, Ubuntu, and official Apache Docker images), the double-free opens a practical RCE path:

  1. The freed h2_stream virtual address becomes available for mmap() reuse.
  2. An attacker-controlled mmap() call places a fake h2_stream struct at that address with its pool cleanup function pointer replaced with system().
  3. Apache's scoreboard — which resides at a fixed address throughout the server's lifetime even under ASLR — is used as a stable staging area for the fake structs and command strings.
  4. When the next cleanup fires on the poisoned pool, system(attacker_command) executes in the Apache worker's security context.

Real-World Impact

As of May 2026, exploitation for Denial of Service is confirmed in the wild, with threat actors crashing Apache worker processes to cause repeated service disruptions. While RCE exploitation has not been publicly confirmed, the PoC-level technical primitives are documented and the mmap path is considered practical on affected Linux distributions.


2. Who Is Affected?

Factor Details
Affected version Apache HTTP Server 2.4.66 only
Fixed version Apache HTTP Server 2.4.67 (released May 4, 2026)
Module required mod_http2 must be loaded and HTTP/2 enabled
MPM requirement Multi-threaded MPM required: worker or event (default on most modern installs)
Not affected MPM prefork (single-threaded), all versions ≤ 2.4.65, all versions ≥ 2.4.67
RCE elevated risk Debian, Ubuntu, and official Apache Docker images (APR mmap allocator)
No auth required Attacker needs only network access to port 443/80 with HTTP/2

Given that Apache HTTP Server powers roughly 20–25% of all active websites globally, and 2.4.66 was the current stable release for several months before the patch, the potential blast radius is extremely large.


3. How to Detect It (Testing)

Step 1 — Identify Vulnerable Servers

# Check Apache version
httpd -v
# or
apache2 -v

# Expected vulnerable output:
# Server version: Apache/2.4.66 (Unix)

# Check if mod_http2 is loaded
httpd -M 2>&1 | grep http2
# Vulnerable output:
#  http2_module (shared)

# Check active MPM
httpd -V | grep MPM
# Vulnerable if output is "worker" or "event"

Step 2 — Confirm HTTP/2 is Enabled on the Server

# Use curl to verify HTTP/2 support
curl -sI --http2 https://your-server.example.com | head -5
# Vulnerable output includes: HTTP/2 200

# Using nmap with http2 script
nmap -p 443 --script http2-enabled your-server.example.com

Step 3 — Test for the Double-Free Trigger (Safe DoS Probe)

Use the public PoC in a controlled, authorized environment:

# Clone PoC
git clone https://github.com/xeloxa/CVE-2026-23918-Apache-H2-PoC
cd CVE-2026-23918-Apache-H2-PoC

# Passive detection only (no crash) — checks version & HTTP/2 header
python3 poc.py --target https://your-server.example.com --mode detect

# Expected output if vulnerable:
# [!] Target Apache/2.4.66 detected
# [!] HTTP/2 enabled — mod_http2 present
# [VULNERABLE] CVE-2026-23918 conditions met

⚠️ Only run active crash tests against systems you own or have written authorization to test. The Rapid-RST mode will crash worker processes.

Step 4 — Check for Signs of Exploitation in Logs

# Look for repeated worker restarts (sign of DoS exploitation)
grep -i "caught SIGTERM" /var/log/apache2/error.log | tail -20

# Look for unexpected child exits
grep -i "child pid.*exit signal" /var/log/apache2/error.log | tail -20

# High-frequency RST_STREAM patterns in access log (requires HTTP/2 access logging)
grep "RST_STREAM" /var/log/apache2/access.log | wc -l

Automated Scanning

Nuclei template:

id: CVE-2026-23918-detect
info:
  name: Apache HTTP/2 Double-Free (CVE-2026-23918)
  severity: critical

http:
  - method: GET
    path:
      - "{{BaseURL}}"
    headers:
      Upgrade: h2
    matchers-condition: and
    matchers:
      - type: word
        part: header
        words:
          - "Apache/2.4.66"
      - type: word
        part: header
        words:
          - "HTTP/2"

Shodan query to find exposed 2.4.66 instances:

"Apache/2.4.66" http.component:"mod_http2"

Code Review Checklist

  • Confirm ServerTokens is not set to Full or OS (limit version disclosure)
  • Verify mod_http2 is only loaded where HTTP/2 is genuinely required
  • Confirm the MPM is listed in httpd -V output and matches expectations
  • Review Dockerfile/Ansible/Puppet configs — confirm base image does not pin to Apache 2.4.66

4. How to Fix It (Mitigation)

Step-by-Step Remediation

Option A — Upgrade to Apache 2.4.67 (Recommended)

  1. Identify your package manager and current version:

    httpd -v   # or apache2 -v
    
  2. Upgrade via package manager:

    # Debian / Ubuntu
    sudo apt update && sudo apt install --only-upgrade apache2
    
    # RHEL / CentOS / AlmaLinux
    sudo dnf update httpd
    
    # Compile from source
    wget https://downloads.apache.org/httpd/httpd-2.4.67.tar.gz
    tar xzf httpd-2.4.67.tar.gz && cd httpd-2.4.67
    ./configure --with-mpm=event --enable-http2
    make && sudo make install
    
  3. Restart Apache:

    sudo systemctl restart apache2   # Debian/Ubuntu
    sudo systemctl restart httpd     # RHEL/CentOS
    
  4. Verify the new version:

    apache2 -v
    # Server version: Apache/2.4.67 (Unix)  ← patched
    

Option B — Disable mod_http2 (Interim)

If upgrading immediately is not possible, disable HTTP/2:

# In httpd.conf or your virtual host config:
# Comment out or remove mod_http2 loading
# LoadModule http2_module modules/mod_http2.so

# Also disable the protocol in VirtualHost blocks
<VirtualHost *:443>
    # Remove or comment out this line:
    # Protocols h2 http/1.1
    Protocols http/1.1
</VirtualHost>

Reload Apache:

sudo systemctl reload apache2

Option C — Switch to MPM Prefork (Not Recommended for Production)

# In /etc/apache2/mods-available/mpm_event.conf (disable event)
# Enable prefork instead:
sudo a2dismod mpm_event
sudo a2enmod mpm_prefork
sudo systemctl restart apache2

⚠️ Prefork is single-threaded and significantly less performant. Use only as a last resort in low-traffic environments.

Configuration Hardening (Apply Regardless of Option Chosen)

# Limit HTTP/2 streams per connection to reduce attack surface
H2MaxSessionStreams 100

# Reduce the timeout window for stream resets
H2StreamTimeout 15

# Restrict H2 to TLS connections only (not plain text)
Protocols h2 http/1.1
# Remove 'h2c' if present — prevents cleartext HTTP/2

# Limit server version disclosure
ServerTokens Prod
ServerSignature Off

5. How to Test the Fix (Validation)

Regression Test Scenarios

  • Scenario A: After patching to 2.4.67, run the PoC's --mode detect — it should report NOT VULNERABLE.
  • Scenario B: Send a legitimate HTTP/2 request to confirm normal service continues: curl --http2 -sI https://your-server.example.com should return HTTP/2 200.
  • Scenario C: Run your application's full regression suite to confirm no functionality is broken by the upgrade.

Security Test Cases

Test Case 1 — Vulnerability No Longer Present

Field Value
Precondition Apache upgraded to 2.4.67, mod_http2 loaded, event MPM
Steps Send HEADERS + RST_STREAM sequence using PoC --mode detect
Expected Result No crash; PoC reports "NOT VULNERABLE"

Test Case 2 — Attack Path Fails Safely

Field Value
Precondition Apply patch or disable mod_http2
Steps Send Rapid-RST sequence (10 streams/sec) for 30 seconds
Expected Result Workers remain stable; no SIGTERM in error.log; service responds normally

Test Case 3 — HTTP/2 Still Functions Normally

Field Value
Precondition Apache 2.4.67 with mod_http2 loaded
Steps Load test with 100 concurrent HTTP/2 connections using h2load
Expected Result All requests succeed; no worker crashes

Automated Validation

# Confirm version with automated check
python3 - <<'EOF'
import subprocess, sys
result = subprocess.run(["apache2", "-v"], capture_output=True, text=True)
version_line = result.stdout.strip()
print(version_line)
if "2.4.67" in version_line or "2.4.6" not in version_line:
    print("[PASS] Apache version is patched or unaffected")
    sys.exit(0)
elif "2.4.66" in version_line:
    print("[FAIL] Apache 2.4.66 detected — CVE-2026-23918 present")
    sys.exit(1)
EOF

# Confirm HTTP/2 still works post-patch
curl -sv --http2 https://localhost/ 2>&1 | grep -E "< HTTP/|< server:"

6. Prevention & Hardening

Best Practices

Patch cadence: Apache HTTP Server is actively maintained and releases security patches through its minor version series. Subscribe to the Apache httpd-announce mailing list or monitor the Apache security page to receive timely notification of new releases.

Principle of least module: Only load Apache modules you actively use. HTTP/2 (mod_http2) should be disabled on servers where HTTP/2 is not a business requirement. Review httpd -M output periodically to audit loaded modules.

Container base images: If you use official Apache Docker images, pin to httpd:2.4.67 or later in your Dockerfile rather than httpd:latest. Establish an automated scan (e.g., Snyk Container, Trivy, Grype) in your CI/CD pipeline to flag outdated base images.

Defense in depth: Place Apache servers behind a WAF or reverse proxy (e.g., Nginx, HAProxy, AWS CloudFront) that can terminate HTTP/2 and rate-limit RST_STREAM floods before they reach Apache worker processes.

Regular vulnerability scanning: Use Trivy, Grype, or similar SCA tools in your pipelines:

# Trivy: scan running container
trivy image your-apache-image:tag

# Grype: scan file system
grype /

Monitoring & Detection

Operators should baseline normal worker restart rates and alert on deviations. Key signals to watch:

# Prometheus/Grafana: alert on mod_status worker state anomalies
# In Apache config, enable mod_status:
<Location "/server-status">
    SetHandler server-status
    Require ip 127.0.0.1
</Location>

# Key metrics to monitor:
# - apache_workers{state="closing"} rising rapidly → RST flood in progress
# - OS-level: repeated apache2 worker process respawns in systemd journal
sudo journalctl -u apache2 --since "1 hour ago" | grep -i "child\|crash\|signal"

# SIEM detection rule (pseudo-code):
# ALERT when: count(apache_error_log contains "caught SIGTERM") > 5 in 60s
#          OR count(apache_error_log contains "double free") > 0

Network-level indicators: A flood of HTTP/2 RST_STREAM frames from a single IP (or small range) within a short window is a strong indicator of CVE-2026-23918 exploitation. If your load balancer or WAF supports HTTP/2 frame inspection, alert on:

  • More than 50 RST_STREAM frames from a single source IP in under 10 seconds
  • RST_STREAM frames with error code 0x8 (CANCEL) on streams with no corresponding response

References

Latest from the blog

See all →