Day 10 — new FP class: SID 9026424 fired once on Spark cryptocurrency client auto-update via Tor exit May 2026 · coverage live across three sensors

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.

NGINX CVE-2026-42945 Heap Overflow RCE Preemptive Coverage depthfirst PoC Operations Writeup
Sensors live
3 / 3
sensor1 + sensor2 + sensor3
Capture layers
3
eBPF + honeypot + Suricata filestore
Suricata SIDs
4
9026420-9026423
Listener
8443
TLS, unique cert per sensor
Real exploits caught
0
day 10 — silence holds; +1 Spark-Tor FP firing
Cert validity
825d
unique CN per sensor

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:

IOC Vocabulary

# Suricata SIDs (HoneyLens local range) sid 9026420 — HONEYLENS CVE-2026-42945 NGINX Rift — escapable-char flood in /api/ URI sid 9026421 — HONEYLENS CVE-2026-42945 NGINX Rift — pool cleanup spray POST body [filestore] sid 9026422 — HONEYLENS CVE-2026-42945 NGINX Rift — depthfirst PoC X-Delay header sid 9026423 — HONEYLENS CVE-2026-42945 NGINX Rift — rapid spray connection burst (threshold) # Indicators captured by the application honeypot (sensor_events.attack_types) attack_type cve_2026_42945_dos POST/GET /api/ attack_type cve_2026_42945_rce POST body with NUL + 0x41 pad + (optional) X-Delay header # UA family worth flagging (covered by SID 9026314 from the CVE-2026-0300 family) ua_re Mozilla/.*\(CVE-\d{4}-\d{4,5}-(Checker|MassChecker|Scanner|Probe|Tester)\) ua_re ^panos-cve-\d{4}-\d{4,5}- # Operator-cluster IPs to watch (carried over from the PAN-OS hunt, 2026-05-22) ip 66.132.172.203 Oracle-cloud, cross-vector PAN-OS + K8s hunter ip 66.132.186.0/23 Oracle-cloud cluster, kube-apiserver scanning ip 66.132.195.0/24 Oracle-cloud cluster, kube-apiserver + kubelet ip 150.107.38.251 cross-vector PAN-OS captive-portal hunter ip 50.116.33.118 Linode US kubelet pounder (248 hits / 76 s)

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:

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 90264209026423 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:

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 90264209026424 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:

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 90264209026424 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:

6 hits 160.119.71.12 generic banner-grab, single-pass 3 hits 66.132.186.177 Oracle-cloud cluster (cross-vector K8s/PAN-OS hunter) 1 hit 45.148.10.200 background scanner 1 hit 20.46.235.137 background scanner

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:

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:

§ 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:

2026-06-01T03:47:55.508561 185.100.87.136:??? → <sensor3>:443 POST /api/client/update?arch=amd64&commit=08059e95dacafe0bf6e5782f8e2c8ec9cd8c5a17&os=windows HTTP/1.1 User-Agent: SPARK COMMIT: 08059e95dacafe0bf6e5782f8e2c8ec9cd8c5a17 <binary protobuf body, 384 bytes, NUL-bearing>

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:

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.