CVE-2026-42945 NGINX Rift — A Heap Overflow and Three Layers of Detection
Coverage period: 2026-05-24 (day 0) → ongoing. Detection went live on the three production HoneyLens sensors the same day the CVE-2026-42945 disclosure landed. Three independent capture layers, four Suricata signatures, one bespoke honeypot on TLS/8443 per sensor with unique self-signed certs.
CVE-2026-42945 is a heap overflow in nginx's rewrite module
(with the common location ~ /api/(.*) { set $arg $1; rewrite ...; } idiom
on a vulnerable build). ngx_escape_uri(NGX_ESCAPE_ARGS) expands every
+ or %2B in the captured URI 1→3 bytes; a long enough run
smashes the pool-cleanup buffer. The depthfirst PoC pairs the overflow trigger with a
heap-spray POST body carrying a forged ngx_pool_cleanup_s — that's the
RCE primitive. Unauthenticated remote root if the spray lands; worker
crash (DoS) if it doesn't.
The Vulnerability in One Minute
nginx's rewrite module copies URI segments through
ngx_escape_uri(NGX_ESCAPE_ARGS), which expands every
+ (and its percent-encoded form %2B) 1→3 bytes during the copy.
For the well-known vulnerable pattern location ~ /api/(.*) { set $arg $1; rewrite ...; },
the captured group $1 goes through this expansion when it lands in a downstream URI buffer
that was sized assuming a 1→1 copy. A long run of + or %2B in
/api/<run> overflows the buffer, smashes the heap, and corrupts adjacent
ngx_pool_cleanup_s structures — the metadata nginx walks at request teardown to
run cleanup handlers.
The DoS variant just relies on the worker dying when the corrupted cleanup struct runs. The RCE
variant pairs the overflow trigger with a heap-spray POST body carrying a forged
ngx_pool_cleanup_s — if it lands in the right pool slot, the freed handler
callback redirects to attacker-controlled bytes. The depthfirst PoC adds an X-Delay
header to hold a slow-backend connection open across the overflow window, increasing the spray
success rate.
The Three Capture Layers
We learned the lesson from the CVE-2026-0300 hunt: a single capture path will fail at the worst possible moment. So three independent layers, each able to recover the exploit body if the other two fail.
Layer 1 — Application Honeypot (sensor-hp-nginx-rift)
A stdlib-only Python service emulating nginx/1.26.2 on TLS/8443. Routes /api/*
through the same pattern as the vulnerable config. Classifies three exploitation indicators:
api_escape_flood:<n>, pool_spray_body, and
slow_backend_delay_header. A 120-second SourceTracker correlates
per-source detections — a flood and a spray from the same IP within the window escalate
to a full RCE verdict, even when individual requests are lower-confidence. Drops the connection
without a response on the overflow trigger — a fake-crash oracle that makes an
attacker's success check behave the same way a real vulnerable nginx would. Every classified
request lands in the platform's sensor_events table with the full body payload.
Layer 2 — Suricata Network-Level Rules (SIDs 9026420-9026423)
Four structural signatures, deliberately written to match the shape of the overflow trigger
and the heap-spray rather than a brittle literal payload. SID 9026420 catches the
/api/+++... escape flood (32+ consecutive escape chars in the URI). SID 9026421
catches the POST body shape (32×0x41 pad + NUL byte) and carries the
filestore keyword — when it fires, the raw body bytes are preserved
to disk under /var/log/suricata/filestore/<sha256-prefix>/<sha256>
with a matching .json sidecar. SID 9026422 catches the X-Delay
header (supporting); SID 9026423 catches rapid spray bursts from one source (threshold-based,
10 hits in 5 seconds).
Layer 3 — eBPF Phase 0.5 (kernel-level, 4 KB capture tier on 8443)
sensor-capture attaches a BPF program to the monitored network interface. Port
8443 is in the dissection list at the 4 KB capture tier (lifted from 1 KB in the
2026-05-22 Phase 0.5 work after the CVE-2026-0300 hunt exposed the old cap as the gap
that lost three real PAN-OS exploit bodies). Every TCP segment to 8443 has its first 4 096
bytes copied into the full_payload column of the events table — including
the TLS handshake bytes, which are independently useful for JA4_T fingerprinting and later
replay.
The three layers complement: TLS-wrapped HTTP on 8443 → the honeypot classifies, eBPF preserves the TLS handshake plus the cleartext bytes that the user-space TLS termination sees. Plain-HTTP attacks on other ports (80, 8080) → Suricata is the primary classifier; filestore preserves the body. None of the three layers depends on the others working correctly.
What We're Watching For
Day 0 has zero CVE-2026-42945 firings — expected, the disclosure is fresh and the depthfirst PoC isn't publicly published yet. Based on the operator class we observed during the CVE-2026-0300 hunt, here's the pattern we expect:
- Self-doxxing CVE-aware scanners. The same operator class that named the CVE
in its
User-Agentduring the PAN-OS hunt (Mozilla/5.0 (CVE-2026-0300-Checker),panos-cve-2026-0300-exposure-survey/1.0) will probably do the same for this CVE. Our classifier rules already generalize overCVE-\d{4}-\d{4,5}-(Checker|MassChecker|Scanner|Probe|Tester)— no new rule needed. - Two-stage OPSEC pattern. Stage 1: TLS recon with the self-doxxing UA (the
honeypot's HTTP layer captures the UA; Suricata sees only ciphertext at the network
layer). Stage 2: plain-HTTP exploit POST with a scrubbed
Mozilla/5.0UA on a different port. If the same actor IP appears in both stages within a few hours, that's the depthfirst kit shape. - Body-size fingerprints. Two PAN-OS PoC kits produced two distinct body sizes (2 248 B "qassam-315" and 2 120 B "CL-variant"). The first 9026421 firing will give us the depthfirst kit's exact CL — same-size hits from disjoint IPs after that mean same kit, different operator.
- Cross-vector pivot from the K8s-scanning operator cluster. The Oracle-cloud
66.132/16cluster we found during the CVE-2026-0300 T+9 hunt does kube-apiserver scanning AND captive-portal hunting from the same operator. Expect them to add NGINX Rift to their hunt list.
IOC Vocabulary
Status & Next Steps
Daily review cadence as long as the hunt is open — same playbook as the CVE-2026-0300 hunt. First real firing closes the day-0 watch and starts the live observation period. Once a body is captured, we publish the body-size fingerprint plus any operator attribution that links to the depthfirst kit's known infrastructure.
Open hunting questions are tracked in the operator-side nginx_rift_hunting.md
playbook (not public). Next blog update: when a real CVE-2026-42945 firing lands, or at the
14-day-silence mark if the hunt closes quiet.
§ 2026-05-25 · Day 1 review — honeypot taking recon traffic, no exploit-shape firings
Thirty-six hours since the day-0 deploy. The TLS/8443 honeypot is
receiving organic traffic across all three sensors — but no
/api/+++ flood and no pool-spray POST body. The
Suricata SIDs 9026420–9026423 stayed silent. The honeypot's
application-layer classifier didn't promote any request to
cve_2026_42945_dos or cve_2026_42945_rce.
The three-layer capture model is loaded and waiting; the
fake-crash oracle hasn't been triggered.
What we are seeing is the usual long-tail recon noise that any new internet-facing surface attracts within hours:
- Polyglot vulnerability scanners. One IP on
sensor1 (
23.234.88.63) ran a full-spectrum sweep:POST /global-protect/prelogin.esp(PAN-OS GlobalProtect),POST /api/v1/vpnportal/login(generic VPN portal),POST /index.plx?app=eup(other product),GET /RDWeb/Pages/(Microsoft RDP web). Not CVE-2026-42945-specific — a scanner trying every login surface it knows. - OpenWRT LuCI auth-bypass probers. One IP
(
5.61.209.33) hitGET /cgi-bin/luci/;stok=/locale~30 times in one hour on sensor1. Unrelated to nginx_rift, but interesting in volume. - Generic banner-grab and favicon enumeration.
165.154.182.168,167.94.146.53,137.184.49.239,74.249.129.23— the usual Censys-/Shodan-/internetdb-class background scanners. All benign GET-only.
Cross-thread observation: none of the day-1 recon IPs on the
nginx_rift honeypot overlap with the CVE-2026-0300 hunting list
(the Oracle-cloud 66.132/16 cluster, the Linode-US
172.105.102.0/24 cluster surfaced today, or the
50.116.33.118 kubelet pounder). The
depthfirst-style operator class doesn't seem to be cross-hunting
yet — encouraging from a coverage-disclosure standpoint.
Continuing daily reviews until either an exploit body lands or 14 consecutive zero-firing days close the hunt. Same shape as the CVE-2026-0300 streak.
§ 2026-05-26 · Day 2 review — honeypot is busy, no exploit-shape firings yet
Another 24 hours of zero 9026420–9026423
firings on every sensor, and zero cve_2026_42945_*
classifications from the application honeypot. The recon volume
is climbing though: sensor1 saw 319 events from 48 unique
sources, sensor2 saw 45 events from 21 unique sources. Both
roughly double the day-1 volume.
Shape of the traffic is still the same long-tail enumeration
pattern we documented on day 1 — what's new is the
diversity. Each day a fresh set of source IPs runs the
same script-kiddie playbook (banner-grab, robots.txt, favicon,
common admin paths). The Censys/Shodan-class background scanners
keep returning under different IPs in their respective
rotations. Nobody has sent an /api/+++
flood or a pool-spray POST body on any sensor yet.
Notable absence: the depthfirst PoC family hasn't shown up in the open in the last 48 hours either — no GitHub publication, no exploit-DB entry, no obvious script-kiddie wave yet. Once that publication happens, expect the wave to arrive within hours. Until then, the honeypot mostly serves as a public-IP traffic-shape baseline for what scanners are doing right now across this surface.
Cross-thread (CVE-2026-0300): day 4 of that hunt's silence
streak; the Linode-US 172.105.102 cluster from
yesterday went silent (one-day campaign). Still no overlap
between either hunt's tracked IPs and any nginx_rift recon
source. Two parallel hunts, two empty rooms.
§ 2026-05-28 · Day 4 review — SID 9026421 fired ×2, but it’s a cross-hit on a PAN-OS body, not a real NGINX Rift exploit
On 2026-05-27 at 03:39 and 03:45 CEST, SID 9026421 (CVE-2026-42945 NGINX Rift — pool cleanup spray POST body) fired twice on sensor3 — the first time the rule has matched on real attacker traffic since deploy. The honest framing: these aren’t real NGINX Rift exploit attempts. They’re structural cross-hits on a PAN-OS rondo-class exploit body that happens to satisfy 9026421’s content matches.
Both events were also the trigger that broke the parallel
CVE-2026-0300 silence streak (see
that writeup
for the full incident detail). The bodies were 2 271 bytes,
delivered to POST /php/login.php on the PAN-OS
captive-portal ports, and fired the full PAN-OS SID stack
(9026301, 9026303, 9026304, 9026305, 9026310). The NGINX Rift
rule fired alongside because:
- SID 9026421 matches
http.request_bodycontaining 32×0x41(capitalA) and a NUL byte. - The rondo PAN-OS exploit body has a long run of
A-padding for the buffer-overflow trigger AND a NUL byte in the/bin/sh\x00reverse-shell shellcode trailer. - Both byte patterns coincidentally co-exist in the same payload, so the rule fires even though the actual exploit target is PAN-OS, not nginx.
This is a known shape of false positive for structural rules
that don’t have a positive URI / vector qualifier.
Tightening planned: add an
http.uri; content:"/api/"; requirement
(since the actual NGINX Rift vulnerability is specific to
nginx’s /api/(.*) rewrite pattern) or a
!/php/ disqualifier to exclude PAN-OS captive
portal targets. Either way, future 9026421 firings on PAN-OS
bodies should be suppressed.
Net for the NGINX Rift hunt: still zero true-positive depthfirst-PoC exploit firings on day 4. The honeypot kept receiving the same long-tail recon background traffic documented in earlier days. The depthfirst PoC family hasn’t shown up in the open yet (no GitHub publication, no exploit-DB entry, no obvious script-kiddie wave).
Continuing daily reviews. The clarifying lesson here is worth keeping: structural rules without a vector qualifier produce cross-hits on adjacent attack families with overlapping byte signatures. The four-rule pack on NGINX Rift will pick up a version-2 revision shortly to add the qualifier.
§ 2026-05-29 · Day 5 review — zero true-positive firings, silence holds, the day-4 cross-hit framing confirmed
Another 24 hours, zero 9026420–9026424 firings
across all three sensors. No fresh cross-hits on PAN-OS bodies
either (the 9026421 trigger from day 4 didn’t repeat). The
application honeypot's cve_2026_42945_dos
and cve_2026_42945_rce classifications stayed at zero.
The fake-crash oracle hasn't been triggered.
Cross-thread:
- CVE-2026-0300 (PAN-OS captive portal) — rondo
actor returned overnight from a new IP
124.198.131.22(same/24as the 2026-05-16 BoF actor), but hitting IoT-router CGI paths on port 8080 rather than PAN-OS captive portal. Same self-doxxing UA. See the T+16 review on that hunt’s page. - CVE-2026-23918 (Apache
mod_http2double-free) — new hunt opened on day 0 (2026-05-28). Day 1 brought 22 firings of SID 9026503, all false positives (cloud-secrets hunters, MCP server probers, Elasticsearch enumerators withpython-requests/httpxUAs). Rule tightened to rev:2 same day to alert only on h2-library-specific UAs. See the Apache mod_http2 hunt writeup.
The depthfirst PoC family for CVE-2026-42945 still hasn’t shown up in the open — no GitHub publication, no exploit-DB entry, no obvious script-kiddie wave. Day 5 of empty rooms.
Continuing daily reviews. SID 9026421 tightening (adding a positive URI qualifier so PAN-OS bodies don’t cross-hit) is still tracked as a follow-up; given the silence streak it hasn’t moved up the priority list.
§ 2026-05-30 · Day 6 review — zero firings, normal recon traffic on 8443 from known scanner clusters
Another 24h, zero 9026420–9026424
firings across all three sensors. Application-honeypot zero
cve_2026_42945_dos / cve_2026_42945_rce
classifications. The fake-crash oracle stayed dormant.
Port 8443 traffic shape on sensor3 last 24h:
The Oracle-cloud cluster (66.132.0.0/16) showing up
on the nginx_rift honeypot is the same operator family from
the CVE-2026-0300 T+9 hunt (the kube-apiserver + captive-portal
cross-vector pattern). They’ve added port 8443 to their
rotation but didn’t emit anything CVE-2026-42945-shaped —
no /api/+++ floods, no pool-spray POSTs.
Banner-collection class scan, not an exploit attempt.
Parallel-thread cross-pointers:
- CVE-2026-0300 (PAN-OS) — T+17 review: rondo cycled on IoT-router CGI through 2026-05-29 daytime then went quiet again. PAN-OS captive portal back to baseline.
- CVE-2026-23918 (Apache
mod_http2) — day 2 review: rev:2 rule vindicated, zero firings post-tightening.
Continuing daily reviews. depthfirst PoC still hasn’t shown up publicly; the silence streak crosses the one-week mark at day 7 which is when we’d consider reducing the review cadence.
§ 2026-05-31 · Day 7 (one-week mark) — zero true positives, but SID 9026421 fired 6× through the same cross-hit shape we documented at T+15
Day 7 closes the first week of NGINX Rift coverage. Net for the
hunt: still zero true-positive depthfirst-PoC firings, still no
public PoC, still no script-kiddie wave. SID 9026421
fired six times overnight 2026-05-29→30 —
but every single one is the same structural cross-hit on
rondo-class PAN-OS bodies we documented at T+15 (CL=2271 bodies
contain both 32×0x41 spray pad and a
NUL byte from the /bin/sh\x00 shellcode trailer,
satisfying 9026421 without targeting nginx). See the
CVE-2026-0300 T+18 review
for the full incident detail.
The 6 firings break down as: 4 on PROD (3 Tor-exit IPs, 3 unique
body sha256s), 2 on slim (2 Tor-exit IPs, 2 unique body sha256s).
Same byte pattern that caused the T+15 cross-hit; the planned
9026421 tightening (positive /api/ URI qualifier so
PAN-OS /php/login.php bodies don’t match) is
now overdue. Moving it up the priority list for next week.
Decision on the review cadence: continuing daily until the 9026421 tightening lands. Six cross-hits in one morning are six events I have to read carefully before confirming they’re not real Rift exploits. Once the URI qualifier is in, the cross-hits go away and the daily cadence can drop to weekly. Until then, daily.
Cross-thread quick notes:
- CVE-2026-0300 (PAN-OS) — T+18 review: silence streak broken again, CL=2271 rondo-class kit returns through 4 Tor exits, 3 unique body sha256s, one operator multiplexing the same body across multiple Tor circuits.
- CVE-2026-23918 (Apache
mod_http2) — day 3 review: silence holds, rev:2 quietly correct. - CVE-2026-0257 (PAN-OS GlobalProtect) — day 1 review: zero firings, zero Rapid7-IOC traffic. New hunt opened 2026-05-30 with coverage on all three sensors.
§ 2026-06-03 · Day 10 review — one new 9026424 firing, and it’s a Spark cryptocurrency client auto-update via a Tor exit. Not a real exploit.
Three days since the day-7 review. The depthfirst PoC family still hasn’t shown up publicly. The hunt’s only firing in the last 72 hours: SID 9026424 (surgical pool-cleanup body shape) fired once on 2026-06-01 at 03:47:55 CEST on slim. Honest framing: it’s a false positive in a brand new family, worth documenting so the next operator looking at our 9026424 logs doesn’t chase a ghost.
The actor was 185.100.87.136 — a public
Tor exit node (Frantech / BuyVM Lebanese cluster). The request:
SID 9026424 matches POST to /api/* with body
bsize 64-512 bytes that contains a NUL byte but doesn’t
contain the 32×0x41 spray pad. The
Spark cryptocurrency client’s update
protocol fits exactly that shape — binary protobuf body
in the 384-byte range with NULs throughout, hitting
/api/client/update on TLS/443.
Why a Tor exit? A user running the Spark client behind Tor (privacy-preserving wallet routing) hits the wider internet from a Tor exit IP. Their auto-update check happens to traverse our nginx_rift honeypot’s port 8443 listener through path-routing or DNS error. Tor exits are the loudest IPs on the internet because every Tor user shares them; we get this kind of collateral regularly.
FP class: same family as the T+15 9026421 cross-hit.
Structural rule matches a body-shape feature that’s
coincidentally present in unrelated traffic. The tightening we
owe both rules (positive URI qualifier requiring the
nginx-vuln-specific /api/ pattern AND
disqualification for known-good UAs like the Spark
commit-hash UA) needs to land soon. Promoting it
again — this is now the second distinct FP
family on the 9026421/9026424 pack in two weeks.
Cross-thread quick notes:
- CVE-2026-0300 (PAN-OS captive portal) — T+21 review: three-day silence streak resumed after T+18 Tor wave; captive portal back to baseline.
- CVE-2026-23918 (Apache
mod_http2) — day 6 review: silence holds; three 9026502 cross-hits from a wide-port Linode-US scanner (no port-80 qualifier on that rule). - CVE-2026-0257 (PAN-OS GlobalProtect) — day 4 review: zero firings, zero Rapid7-IOC traffic. Wave-3 still pending.
Decision on cadence: shifting to every-other-day reviews next week if both 9026421/24 are tightened by then. The structural FPs are the only thing keeping the hunt on a daily cadence; once they’re gone, the silence stretches out and weekly is the right shape.