
Analyzes CVE-2026-42945, an NGINX rewrite heap overrun, providing a differential detector, deterministic DoS PoC, and a credited RCE port with verification.
Security research on CVE-2026-42945 (CVSS 4.0 9.2 CRITICAL): a heap
buffer overrun in NGINX's script engine where a rewrite with arguments in
the replacement (e.g. rewrite ^(.*) /new?c=1;) leaves the engine's
is_args flag set and never cleared — so any capture ($1) evaluated
afterwards in set/if is copied with URI escaping into a buffer
that was sized for the unescaped length. Reachable pre-auth by any
client whose request URI carries %XX escapes or +.
Result (phase 1): a safe differential detector (vulnerable 1.30.0 vs fixed 1.30.1, zero crashes) plus a (SIGSEGV, core dumped — the response is fully served first). Both reproduced against builds compiled from source at commit .
71d62c6Result (phase 2): a credited port of the reporter's RCE —
system() execution as the worker user (nobody), reproduced 3/3 clean
runs against the reporter's pinned build replicated under lab/rce/.
The exploitation technique is Leo Lin's (DepthFirst); see
What is original here vs prior work and the writeup's Prior work.
| Claim | Evidence |
|---|---|
| Root cause derived from the fix commit (not from blogs) | finding-sheet |
Fix is a one-liner — e->is_args = 0; in ngx_http_script_regex_end_code() | commit 524977e (quoted in writeup) |
| Safe detector: vulnerable vs fixed oracle, zero crashes | poc/diagnose.py — truncation fingerprint (/foo%25b vs /foo%bar) |
| Trigger matrix: quoted/plus URI and escapable chars in the capture — both required | poc/diagnose.py probe battery incl. the /a%41b control |
| Deterministic worker DoS (SIGSEGV, core dumped, response served first) | poc/crash.py — X-Worker-PID change proves worker death |
| Fixed 1.30.1 survives the identical attack | poc/crash.py --host 127.0.0.1:29544 — 3/3 rounds survived |
| RCE (phase 2, reporter's technique, credited port) | exploit/rce.py — 3/3 runs, proof file + uid=65534(nobody), workers exited on signal 11 after execution |
# differential pair: nginx 1.30.0 (:29543) + 1.30.1 (:29544) built from source
cd lab && docker compose up -d --build && cd ..
python3 poc/diagnose.py # safe oracle — never crashes either worker
python3 poc/crash.py --host 127.0.0.1:29543 # 1.30.0: worker dies on signal 11, core dumped
python3 poc/crash.py --host 127.0.0.1:29544 # 1.30.1: survives every round
docker logs cve-42945-vuln 2>&1 | grep 'exited on signal' | tail -3
Phase 2 — RCE against the reporter's pinned build (nginx dev rev
98fc3bb7 = release-1.29.8-14, ASLR disabled by the pinned entrypoint):
docker compose -f lab/rce/docker-compose.yml up -d --build # :29545, minutes to build
python3 exploit/rce.py \
--cmd 'echo pwned-by-cve-2026-42945 > /tmp/proof' --verify --expect pwned
docker compose -f lab/rce/docker-compose.yml exec nginx cat /tmp/proof
| Doc | What it is |
|---|---|
| docs/finding-sheet.md | One-page datasheet: versions, CVSS, config pattern, trigger conditions, detection signatures |
| docs/report-ptes.md | PTES-structured report (7 phases), scoped to what was actually done |
| docs/research-writeup.md | The investigation narrative — fix-commit archaeology, the truncation surprise, portable lessons, future work |
The phase-1 research chain is original work: the crash-free differential
oracle and its truncation fingerprint (the served body is the
re-escaped capture truncated to the raw length — the visible signature of
the length/value two-pass mismatch), the trigger matrix proving both
preconditions necessary, the source-level explanation of why the length
pass cannot see is_args, and the deterministic post-response worker
kill. The bug itself, the vulnerable config pattern, and the fix-commit
analysis are prior work by the reporter and upstream — Leo Lin
(DepthFirst) disclosed the issue and Roman Arutyunyan fixed it; see the
writeup's Prior work section and
DepthFirst Disclosures — Nginx-Rift,
tree CVE-2026-42945.
Phase 2 (RCE) is a port of the reporter's published exploit, not an
independent chain: the cross-request heap feng shui that lands the overrun
on an adjacent request pool's cleanup pointer, the fake
ngx_pool_cleanup_t spray via held POST bodies, the cleanup-dispatch
redirect to libc system(), the overflow geometry (349 A + 969 + +
6 target bytes), the no-ASLR base/offset constants, and the pinned build
in lab/rce/ (their env/) are all Leo Lin's design, reproduced with
credit in exploit/rce.py. Ours in phase 2: the port itself — this
repo's CLI/logging/verification conventions, live heap-scan candidate
discovery and base autodetection (their static constants are
kernel-dependent; see the exploit docstring), the stdlib reverse-shell
listener, and the verification evidence.
This repo shares its methodology with CVE-2026-42533 — NGINX Pre-Auth Heap Overflow: Analysis & RCE (sibling repo): the same two-pass length/value bug class in the nginx script engine, taken there through a full pre-auth RCE chain. That chain delimited the plausible ceiling for this bug; phase 2 of this repo reaches the same ceiling here — as a credited port of the reporter's technique, not an independent chain.
docs/ finding sheet · PTES report · research writeup
poc/ diagnose.py (safe differential oracle) · crash.py (deterministic DoS)
exploit/ rce.py — credited port of the reporter's system() chain
lab/ Dockerfile (nginx from source, -g -O0) + differential compose pair
lab/nginx/ vulnerable.conf — the PoC config from the fix commit
lab/rce/ reporter's pinned RCE environment (their env/, our port mapping)
524977e7c534
"Rewrite: fixed escaping and possible buffer overrun" (Roman Arutyunyan) — released in nginx 1.30.1CVE-2026-42945 (reporter's prior work; Leo Lin)Educational research artifact. Run only against systems you own or are explicitly authorized to test. The vulnerability is patched — upgrade nginx to ≥ 1.30.1.