
Affected: Apache HTTP Server 2.4.66 with mod_http2 + Event MPM
Fixed in: Apache 2.4.67 (mod_h2 v2.0.37)
CVSS 3.1: 8.8 HIGH — Unauthenticated Remote Code Execution possible
CWE: CWE-415 (Double Free)
mod_http2 in Apache 2.4.66 has a double-free bug inside h2_mplx.c:m_stream_cleanup(). The issue happens when a client sends a HEADERS frame immediately followed by a RST_STREAM on the same stream. If the timing is right, the stream ends up pushed twice into the m->spurge purge array. When the mplx gets destroyed, the APR pool is freed twice, which corrupts the heap and causes a SIGABRT or SIGSEGV.
Apache patched this in mod_h2 v2.0.37 by introducing add_for_purge(), a simple deduplication check that prevents the same stream from being added twice.
// Vulnerable (< v2.0.37)
APR_ARRAY_PUSH(m->spurge, h2_stream *) = stream; // can happen twice
// Fixed (v2.0.37+)
static int add_for_purge(h2_mplx *m, h2_stream *stream) {
for (int i = 0; i < m->spurge->nelts; ++i)
if (APR_ARRAY_IDX(m->spurge, i, h2_stream*) == stream)
return FALSE;
APR_ARRAY_PUSH(m->spurge, h2_stream *) = stream;
return TRUE;
}
By default the script runs a 3-phase pipeline on the target:
| Phase | What it does |
|---|---|
| 1 — Recon | Detects Apache version, HTTP/2 support via ALPN, MPM type |
| 2 — Exploit | Sends threaded HEADERS+RST_STREAM bursts, tries inline then staged mode |
| 3 — RCE Assessment | Measures crash consistency and scores the risk level |
The RCE score is computed from three signals that are detectable remotely. First, whether Apache 2.4.66 is running. Second, whether Event or Worker MPM is in use (mod_http2 refuses to start with Prefork so if HTTP/2 works, it's a threaded MPM). Third, how deterministic the crash is — a crash on the first round means the heap corruption is controlled and reproducible, which is what you need for RCE.
pip install hpack requests
# Full PoC on a single target (default behavior)
python poc.py -t 192.168.1.100
# Increase pressure with more rounds, bigger bursts and more threads
python poc.py -t 192.168.1.100 -n 20 -b 500 -w 5
# Passive check only, nothing is sent to the target
python poc.py -t example.com --check-only
# Scan a whole list of targets
python poc.py -l sites.txt -o results.json
# Passive check on a list
python poc.py -l sites.txt --check-only
# Generate a Markdown report after the run
python poc.py -t target.com --report report.md
# one target per line, comments are ignored
192.168.1.100
example.com
example.com:8443
https://example.com
http://example.com:8080
The proper fix is upgrading Apache to 2.4.67 or later. If you can't upgrade right away, disabling HTTP/2 removes the attack surface entirely:
# Before
Protocols h2 http/1.1
# After (disables HTTP/2)
Protocols http/1.1
This tool is meant for authorized security testing and research only. Make sure you have explicit written permission before running it against any target. The author takes no responsibility for misuse.
| Flag | Default | Description |
|---|
-t | — | Single target (hostname or IP) |
-l | — | File with targets, one per line |
-p | 443 | Port |
-n | 10 | Number of exploit rounds |
-b | 200 | HEADERS+RST pairs per round |
-w | 3 | Parallel connections per round |
-d | 0.1 | Delay between rounds in seconds |
-m | inline | Attack mode: inline or staged |
--no-tls | — | Use h2c instead of TLS |
--check-only | — | Passive recon only, no exploit |
-o | — | Save results to a JSON file |
--report | — | Generate a Markdown report |
-v | — | Verbose output |
| Level | What it means |
|---|
| CRITICAL | 2.4.66 confirmed, HTTP/2 active, Event MPM detected, crash is deterministic — patch right now |
| HIGH | 2.4.66 confirmed, HTTP/2 active, threaded MPM in use |
| MEDIUM | 2.4.66 detected but HTTP/2 or crash not confirmed yet |
| LOW | Version doesn't match or no HTTP/2 detected |
| NONE | Already patched to 2.4.67+ or not affected |