CVE-2026-23918 — Apache mod_http2
STREAM_CLOSED Rapid Reset
A coordinated fleet of scanning nodes probing for Apache mod_http2 servers vulnerable to a novel variant of the HTTP/2 Rapid Reset attack, using RST_STREAM(STREAM_CLOSED) instead of the CANCEL code used by CVE-2023-44487.
01 Executive summary
Four IP addresses, each on a different abuse-tolerant hosting provider, spent five days probing our honeypot fleet with browser traffic that looked completely legitimate down to the TLS fingerprint. What gave them away was a single byte in the HTTP/2 RST_STREAM error code, one that slips past the rate-limiting Apache shipped after the 2023 Rapid Reset attacks. No payloads were dropped, this is reconnaissance, and as of this writing it is still running.
Between 2026-06-19 and 2026-06-23, the cowrAI honeypot fleet recorded four distinct source IPs
conducting HTTP/2 vulnerability scanning consistent with CVE-2026-23918, an Apache
mod_http2 flaw affecting stream state management. Each scanner sends bursts of
8–9 concurrent HTTP/2 connections, immediately issuing RST_STREAM frames with error code
STREAM_CLOSED (0x5), a code distinct from the CANCEL (0x8) used
by the 2023-era Rapid Reset attacks. No payloads were delivered; the HEADERS frames that do
get sent are ordinary secret/config-exposure probes (.env, .git/HEAD,
docker-compose*.yml — see §07) riding over HTTP/2, torn down with
STREAM_CLOSED once the scanner is done with each stream.
RST_STREAM(CANCEL) (CVE-2023-44487) was
observed from three additional IPs (Tencent Cloud, DigitalOcean). These are distinct
from CVE-2026-23918 and documented in the IOC table for completeness.
02 Threat actor profile
Infrastructure characteristics
| IP | ASN / Org | Country | Role | CVE Family |
|---|---|---|---|---|
| 185.65.135.182 | AS39351 31173 Services AB | SE | Blaster | CVE-2026-23918 |
| 103.81.231.93 | AS203020 HostRoyale Technologies | US | Blaster | CVE-2026-23918 |
| 146.70.201.108 | AS9009 M247 Europe SRL | JP | Blaster | CVE-2026-23918 |
| 154.47.29.11 | AS212238 Datacamp Limited | HR | Blaster | CVE-2026-23918 |
| 152.42.217.112 | AS14061 DigitalOcean | SG | Scanner | CVE-2023-44487 |
| 129.226.83.92 | AS132203 Tencent Cloud | SG | Scanner | CVE-2023-44487 |
| 43.128.73.4 | AS132203 Tencent Cloud | SG | Scanner | CVE-2023-44487 |
All CVE-2026-23918 nodes use abuse-tolerant hosting (31173 Services AB, HostRoyale, M247, Datacamp/Datapacket), a pattern consistent with rented scan infrastructure that prioritises network diversity over persistent IP identity. The CVE-2023-44487 nodes use mainstream cloud (DigitalOcean, Tencent) suggesting compromised instances or a separate operator with different opsec posture.
03 Infrastructure diagram
04 Technical analysis
CVE-2026-23918 vs CVE-2023-44487 — error code distinction
The 2023 HTTP/2 Rapid Reset attack (CVE-2023-44487) established a pattern: open a stream
(HEADERS frame), then immediately cancel it (RST_STREAM with error code CANCEL = 0x8)
before the server processes the request. Apache patched this path with rate-limiting on
CANCEL RSTs in mod_http2.
CVE-2026-23918 uses STREAM_CLOSED (0x5) instead. Per RFC 9113, STREAM_CLOSED
indicates "the stream is no longer open." Sending this as a client RST, before the server
has responded, places mod_http2 into an ambiguous state: the stream object was allocated
on receipt of HEADERS, but the client is now telling the server the stream is already closed
from the client's perspective. This bypasses the CANCEL rate-limit codepath and targets a
distinct branch of the stream state machine, likely triggering resource exhaustion or a
use-after-free in stream cleanup.
| Property | CVE-2023-44487 (Rapid Reset) | CVE-2026-23918 (STREAM_CLOSED) |
|---|---|---|
| RST error code | 0x8 CANCEL | 0x5 STREAM_CLOSED |
| Patched rate-limit bypass | No — rate-limited | Yes — different code path |
| Attack vector | Resource exhaustion (CPU) | State confusion + potential UAF |
| Concurrent sessions | Many | 8–9 per wave (lower profile) |
| First seen in fleet | — | 2026-06-19 |
Browser UA rotation (anti-fingerprint)
CVE-2026-23918 sessions present realistic browser fingerprints with matching
User-Agent and sec-ch-ua headers. Across all four scanner IPs we
observed five distinct UA profiles, not three — including a Windows/Edge
profile that is the single most common UA on two of the four IPs:
| Profile | User-Agent | sec-ch-ua | Seen on |
|---|---|---|---|
| Mac Chrome 120 | Mozilla/5.0 (Macintosh…) Chrome/120.0.0.0 | "Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120" | 154.47.29.11 (20×) |
| Mac Chrome 124 | Mozilla/5.0 (Macintosh…) Chrome/124.0.0.0 | (not captured in this profile's sessions) | 185.65.135.182 (22×), 103.81.231.93 (67×), 146.70.201.108 (15×) |
| Mac Chrome 131 | Mozilla/5.0 (Macintosh…) Chrome/131.0.0.0 | "Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24" | all 4 IPs (89× combined) |
| Windows 10 Edge 101 | Mozilla/5.0 (Windows NT 10.0; Win64; x64)… Chrome/101.0.4951.64 Edg/101.0.1210.47 | (not captured in this profile's sessions) | 185.65.135.182 (22×), 146.70.201.108 (44× — its most common UA) |
| Mac Safari 18 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Version/18.0 Safari/605.1.15 | (absent — Safari does not send sec-ch-ua) | 146.70.201.108 (1×), 154.47.29.11 (3×) |
The macOS 10_15_7 (Catalina) platform string is common but is not a fixed anchor across all profiles — the Windows 10/Edge 101 UA accounts for the majority of requests from 146.70.201.108 and a third of requests from 185.65.135.182, so platform string alone is not a reliable discriminator for this campaign.
Scan wave pattern
Each scanner operates in waves: 8–9 connections open concurrently, all originating from a single source port per session. Within each wave, 1–2 "hero" connections send dozens of HEADERS+RST pairs while the remaining connections hold open (1 HEADERS, 1 RST each), likely testing server-side concurrency limits alongside the RST error path.
Wave timing (154.47.29.11 example — 118 seconds total, times UTC):
22:05:26.964 Wave start (single probe session)
22:05:42.560 8 concurrent sessions open simultaneously (incl. "hero" session 827c9e39955d)
22:05:48.975 1 follow-up session
22:05:58.187 8 concurrent sessions (second full burst)
22:06:04.938 1 follow-up session
22:06:20.537 8 concurrent sessions (third full burst)
22:06:26.946 1 follow-up session
22:06:36.156 8 concurrent sessions (fourth full burst)
22:06:49.938 1 follow-up session
22:07:09.877 8 concurrent sessions (fifth full burst)
22:07:10.265 1 closing session
22:07:24.589 All sessions closed (46 sessions total)
05 MITRE ATT&CK mapping
| ID | Technique | Evidence |
|---|---|---|
| T1595.001 | Active Scanning: Scanning IP Blocks | Multiple scanner IPs hitting different honeypot nodes across the fleet |
| T1595.002 | Active Scanning: Vulnerability Scanning | RST_STREAM(STREAM_CLOSED) pattern targets specific mod_http2 code path |
| T1036 | Masquerading | Rotating 5 legitimate browser UA profiles (Mac Chrome 120/124/131, Windows Edge 101, Safari 18) with matching sec-ch-ua where applicable |
| T1583.003 | Acquire Infrastructure: Virtual Private Server | All nodes on abuse-tolerant VPS providers (Datacamp, M247, HostRoyale, 31173) |
| T1499.003 | Endpoint DoS: Application Exhaustion Flood | High-volume HEADERS+RST bursts targeting mod_http2 stream resource allocation |
06 Campaign timeline
07 Captured HTTP/2 frames & headers
Raw headers captured by apachelure from session 827c9e39955d (154.47.29.11).
Timestamps below are UTC. apachelure captures full HEADERS frames including the
:method, :path, and :authority pseudo-headers — see
"Requested paths" below for what these scanners were actually probing for.
--- HTTP/2 Frame Sequence (representative session 827c9e39955d) ---
[22:05:42.567] apache.h2_preface ← client magic received
[22:05:42.567] apache.h2_frame ← SETTINGS frame
[22:05:42.568] header_captured sec-ch-ua: "Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"
[22:05:42.575] apache.h2_frame ← HEADERS, stream 3: GET :authority= :path=/docker-compose.yml
user-agent: Mozilla/5.0 (Macintosh…) Chrome/120.0.0.0 Safari/537.36
[22:05:42.576] apache.h2_frame ← HEADERS, stream 5: GET :path=/docker-compose.yaml
[22:05:43.567] apache.h2_rst_stream stream 1, STREAM_CLOSED (0x5) — no HEADERS ever sent on this stream
[22:05:43.568] apache.h2_frame ← HEADERS, stream 7: GET :path=/docker-compose.prod.yml
⋮ (13× HEADERS total — mostly GET requests for .env / .git / docker-compose paths —
interleaved with 10× RST_STREAM, all STREAM_CLOSED, targeting a mix of streams that
did and did not carry a HEADERS frame)
[22:05:57.572] apache.h2_session_end ← session closed
--- All RST_STREAM errors observed across campaign ---
apache:h2_rst_error:STREAM_CLOSED 391 (100% of CVE-2026-23918 RSTs)
Requested paths
Across the 4 CVE-2026-23918 IPs, apachelure captured 283 HTTP/2 requests spanning 38 distinct
paths. The request pattern is a standard secrets/config-exposure sweep — .env
variants, .git/HEAD, docker-compose*.yml — plus at least one
known RCE probe (/_ignition/execute-solution, the Laravel Ignition
deserialization RCE path). This is evidence the actors are not fuzzing the HTTP/2 stream
state machine blind — they are running an ordinary web recon scanner over HTTP/2 and
tearing down each stream with RST_STREAM(STREAM_CLOSED) once done with it.
| Path | Requests |
|---|---|
| / | 17 |
| /graphql | 15 |
| /.env.backup | 14 |
| /backend/.env | 14 |
| /.env.dev | 14 |
| /api/.env | 12 |
| /.env.production | 12 |
| /.git/HEAD | 11 |
| /.env.local | 11 |
| /docker-compose.yml | 10 |
08 Indicators of compromise
CVE-2026-23918 scanner IPs
| IP | ASN | Country | Sessions | RSTs | First Seen |
|---|---|---|---|---|---|
| 185.65.135.182 | AS39351 31173 Services AB | SE | 54 | 87 | 2026-06-19 |
| 103.81.231.93 | AS203020 HostRoyale Technologies | US | 52 | 89 | 2026-06-20 |
| 146.70.201.108 | AS9009 M247 Europe SRL | JP | 84 | 140 | 2026-06-21 |
| 154.47.29.11 | AS212238 Datacamp Limited | HR | 46 | 75 | 2026-06-23 |
Related CVE-2023-44487 IPs (CANCEL RST — separate campaign)
Behavioral signatures
| Indicator | Value |
|---|---|
| RST_STREAM error code | 0x5 (STREAM_CLOSED) — all RSTs |
| Concurrent sessions per wave | 8–9 |
| Session duration | ~15 seconds per wave |
| UA profiles observed | 5 distinct (Chrome 120/124/131 macOS, Edge 101 Windows, Safari 18 macOS) — no single fixed platform anchor |
| Target port | 443 (TLS only) |
| Protocol | HTTP/2 (h2) — no h2c observed |
| ASNs of interest | AS39351, AS203020, AS9009, AS212238 |
09 Detection signatures
Suricata — CVE-2026-23918 STREAM_CLOSED Rapid Reset
alert tcp any any -> $HTTP_SERVERS 443 (
msg:"CVE-2026-23918 Apache mod_http2 STREAM_CLOSED Rapid Reset Scan";
flow:established,to_server;
content:"|50 52 49 20 2a 20 48 54 54 50 2f 32 2e 30 0d 0a 0d 0a 53 4d 0d 0a 0d 0a|";
comment:"HTTP/2 client magic (PRI * HTTP/2.0)";
threshold:type both, track by_src, count 5, seconds 10;
detection_filter:track by_src, count 3, seconds 5;
metadata:cve CVE-2026-23918, confidence high;
classtype:attempted-dos; sid:9002691801; rev:1;
)
# Tighter: STREAM_CLOSED RST (binary RST_STREAM frame with error 0x00000005)
alert tcp any any -> $HTTP_SERVERS 443 (
msg:"CVE-2026-23918 RST_STREAM STREAM_CLOSED error in HTTP/2";
flow:established,to_server;
content:"|00 00 04 03 00|"; comment:"RST_STREAM frame header (len=4, type=0x3, flags=0x0)";
content:"|00 00 00 05|"; within:4; comment:"error code STREAM_CLOSED = 0x5";
threshold:type both, track by_src, count 8, seconds 30;
metadata:cve CVE-2026-23918, confidence high;
classtype:attempted-dos; sid:9002691802; rev:1;
)
Apache httpd — rate-limit RST_STREAM(STREAM_CLOSED)
# In httpd.conf or mod_http2 config:
H2MaxSessionStreams 20
H2MaxWorkerIdleSeconds 30
H2StreamMaxMemSize 65536
# Monitor: grep "h2_rst_error:STREAM_CLOSED" in cowrie events
# or Apache error.log for "RST_STREAM" with status 0x5
Nginx / HAProxy upstream — mitigation
# Nginx: limit concurrent HTTP/2 streams
http2_max_concurrent_streams 20;
limit_req_zone $binary_remote_addr zone=h2_limit:10m rate=30r/m;
# HAProxy: bind with h2 rate limits
bind :443 ssl crt /etc/ssl/cert.pem alpn h2,http/1.1 \
maxconn 100
10 Collection methodology
Data collected by the cowrAI distributed SSH/Telnet honeypot fleet, deployed across multiple cloud providers and geographic regions. HTTP/2 traffic was captured by apachelure, a custom lure daemon presenting a realistic Apache HTTPS endpoint on port 443, deployed fleet-wide as an anchor-tier lure.
apachelure logs each captured HTTP header individually, plus structured events for HTTP/2 frame-level telemetry (connection preface, frame-level detail, RST_STREAM events, and session end). All events are ingested in near-real-time into the fleet's operational data store and cross-referenced against a separate analytics pipeline for fleet-wide aggregation.
:method, :path,
:authority) were not captured during this campaign. That was incorrect —
apachelure's HEADERS frame logging already includes the full pseudo-header set, and that
data (see §07 "Requested paths") is what identified the scanners' actual targets
(.env/.git/docker-compose secret-exposure paths).