Research notes, a calibrated exploit and a detection template for the PHP session upload-progress RCEs (CVE-2026-109618 / CVE-2026-109619).
# php2shell — pre-auth RCE in PHP session upload-progress
**English** | [中文说明](README.zh-CN.md)
Research notes, a working exploit, a build-calibration helper and a version fingerprint for
**CVE-2026-109618** and **CVE-2026-109619**.
Everything here was executed against official images on a local lab. Numbers, offsets and
response excerpts come from those runs, not from third-party write-ups.
```
php:8.4.26-apache-bookworm → PROOF: uid=33(www-data) (4 runs, 2 containers, 2 ASLR layouts)
php:8.4.27-apache-bookworm → stage 1 heap leak misses
```
## Why it works, in one paragraph
PHP initialises the session for multipart requests *during request startup*, before the script
runs and before module RINIT. That early path reads the session record, and when a request hits
the memory limit in the middle of it, the interpreter leaves partially built state behind. Two
consequences are used here: the session id (and other fields) can still point at memory from the
previous request, and a fresh worker inherits a heap layout that makes attacker-controlled
buffers land at predictable offsets. Combining a leaked chunk address, a leaked `libphp.so` base
and a forged HashTable inside the session data turns `zval_ptr_dtor` into `zif_system`.
The payload is application-independent: any PHP endpoint that can receive a multipart request is
enough, so any CMS or framework on the same build is equally exposed.
## Affected / fixed
| Branch | Affected | Fixed |
|---|---|---|
| 8.5.x | ≤ 8.5.11 | 8.5.12 |
| 8.4.x | ≤ 8.4.26 | 8.4.27 |
| 8.3.x | ≤ 8.3.35 | 8.3.36 |
| 8.2.x | ≤ 8.2.34 | 8.2.35 |
8.1 and earlier are end-of-life and receive no fix.
## Layout
```
php2shell-exploit-labstyle/
├── README.md this file
├── README.zh-CN.md Chinese version
├── NOTICE upstream attribution and what was changed
├── docs/
│ ├── 01-root-cause.md startup ordering, early session read, what breaks
│ ├── 02-expl
```