Back Sploitus Exploit for Improperly Controlled Sequential Memory Allocation in Php
- **GHSA**: [GHSA-w8hw-w9fx-j79w]( — *"RCE via OOM corruption"*
- **Affected**: ` This tool may be used for legal purposes only. Users take full responsibility for any
> actions performed using this tool. Only run it against systems you own or are
> authorized to test. The author accepts no liability for damage caused by this tool.
A chunked multipart request has no usable declared `Content-Length`, and in vulnerable
builds `fill_buffer()` has no `post_max_size` cap — so attacker-controlled part-header
metadata is retained until `memory_limit` (128M stock) is exhausted. The failing
allocation makes `zend_mm_safe_error()` raise `E_ERROR` and then `zend_bailout()`
**longjmp** out of the multipart parser, unwinding a **half-initialized request**.
Request shutdown then proceeds from invalid state; the prefork worker **survives** and
keeps serving. A crafted heap layout converts that retained state into full remote code
execution (vendor-confirmed against mod_php with defaults).
Vendor-named OOM strategies: unusable declared `Content-Length`, **unlimited multipart
part headers**, empty file parts not counted by `max_file_uploads`, nested field names.
1. the OOM fatal during multipart parsing at **stock config** — the bailout trigger;
2. the pinned worker **survives** the bailout and answers the request on the
same connection — the half-initialized state path;
3. the **delta** vs fixed builds: `>= 8.2.35` stops reading at `post_max_size` — same
bytes, no OOM, no fatal — which identifies vulnerable targets.
Full RCE conversion requires heap grooming of the request-shutdown path and is
**not included** — no invented chain is claimed.
python3 cve-2026-109618.py -H TARGET -p 80 --path /index.php --mode headers --mb 160
python3 cve-2026-109618.py -H TARGET -p 80 --path /index.php --mode bigcd --mb 160
No external dependencies. Output against a vulnerable 8.2.34 target:
[2] flood sent=160MiB status=200 err=None (0.5s)
[+] OOM FATAL reproduced at stock config:
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 4096 bytes)
[+] zend_mm_safe_error() -> E_ERROR -> zend_bailout() longjmp out of the parser
[+] worker SURVIVED the bailout and served the request
[+] PRIMITIVE CONFIRMED - OOM bailout corruption trigger reproduced.
`--mode bigcd` uses one huge `Content-Disposition` value — OOM on a 65 MB allocation
instead of 4 KB. The flood request carries `Authorization`/`Cookie` markers; if they
appear in the request on the same worker, the script flags stale state.
## Verification (reproduced on 8.2.34, delta on 8.2.35)
| Same request bytes | 8.2.34 stock | 8.2.35 stock |
| 160 MB part headers (chunked) | OOM fatal `allocate 4096`; worker survives | no OOM — partial upload (error 3), 2 MiB retained |
| 160 MB bigcd | OOM fatal `allocate 65015808`; worker survives | no OOM — error 3, 4 MiB retained |
| 160 MB file data (control) | no OOM; error 1, 2 MiB | no OOM; error 1, 2 MiB |
| 16 MB declared Content-Length | rejected pre-parse (`post_max_size`) | rejected pre-parse |
The fix removes reachability at stock config: `fill_buffer()` caps reads at
`post_max_size` (`main/rfc1867.c:249`), `php_mime_should_save_hdr()` allow-lists and
dedups part headers, and the allocator's delayed soft OOM (`EG(delayed_oom)` + VM
interrupt) replaces the `longjmp` bailout.
- [GHSA-w8hw-w9fx-j79w]( — php-src security advisory
- php.net: [8.2.35]( ·
[8.3.36]( ·
[8.4.27]( ·
[8.5.12](
- Fix commits: `01a40e89fbfc499936e3b05888d8f00bfde886df` (8.2) ·
`3c832b523ee13b1a010177d2ff84aea18be0015e` (8.3) ·
`506e87fa7dcdd306975e5b8f8265ee03527aacbf` (8.4)
- Regression test added by the fix: `Zend/tests/oom_request_parsing.phpt`
The full story
This article is one source in a clustered incident — the cluster page carries the summary, timeline and every other outlet covering it.
