Hi, while I was checking the PHP security releases yesterday morning I noticed something I don’t see often: emergency patches for two critical CVEs, shipped a day before the coordinated disclosure window even opened. The details for CVE-2026-109618 and CVE-2026-109619 leaked early on October 20, the PHP team pushed fixed builds at 14:00 UTC on October 21, and a PoC named php2shell landed on GitHub within hours. It bundles an isolated Docker lab plus nine independent RCE chains. I spent the evening picking it apart, so here is what the bugs actually are and how the exploit works.
GitHub PoC: wakemeiflose/php2shell
NVD CVE-2026-109618: nvd.nist.gov
NVD CVE-2026-109619: nvd.nist.gov
watchTowr analysis: watchtowr.com
Sansec research: sansec.io
PHP manual: session.upload_progress
The Basics
Both bugs are heap use-after-free flaws in PHP core, and both score 9.2 on CVSS v4.0 (9.8 under v3.1). Neither one needs authentication, and both can end in remote code execution with the privileges of the web worker. The stock configuration is exposed out of the box: session.upload_progress.enabled has defaulted to On since PHP 5.4, so no application code needs to opt in for the attack surface to exist.
| Detail | CVE-2026-109618 | CVE-2026-109619 |
|---|---|---|
| Alias | RCE via OOM corruption | RCE via stale global state |
| Component | Request memory and OOM (longjmp) handling | SAPI-level state retained between requests |
| Trigger | Crafted multipart POST forces an out-of-memory condition | Multipart POSTs, session upload progress as early consumer |
| Bug class | Use-after-free (CWE-416) | Use-after-free (CWE-416) |
| CVSS v4.0 | 9.2 Critical | 9.2 Critical |
| CVSS v3.1 | 9.8 Critical | 9.8 Critical |
| Auth required | No | No |
Every PHP release from 5.4.0 up to 8.5.11 is affected. The fixed builds were released on October 21, 2026:
| Branch | Fixed release |
|---|---|
| 8.5 | 8.5.12 |
| 8.4 | 8.4.27 |
| 8.3 | 8.3.36 |
| 8.2 | 8.2.35 |
PHP 8.1 and older are past end of life and had no routine upstream updates on October 21. If you are stuck on an old branch, you need a vendor backport or a rebuild from source.
Technical Deep Dive
CVE-2026-109618 is about how PHP handles failed allocations. When a memory allocation fails, PHP uses longjmp() to abandon the current request context immediately. The catch is that some contexts bail out before their request memory is fully initialized, and the cleanup path later reads or branches on that uninitialized memory. An attacker can force OOM repeatedly with crafted multipart requests that stay inside the configured limits, turning an allocation failure into heap corruption and finally code execution. Rapid7 notes the demonstrated path runs against Apache mod_php before application code even executes, while PHP-FPM terminates the worker on startup failure, which blocks that particular sequence.
CVE-2026-109619 is a use-after-free that spans requests. PHP keeps global state that temporarily points into request memory. In some scenarios that state is not reset correctly, or it gets repopulated after being reset, so it still references memory that has already been freed. A later request then reads or branches on that stale state. Researchers confirmed RCE against mod_php through the fatal error backtrace feature, and a second session lifecycle path against both mod_php and PHP-FPM. Session upload progress is enabled by default and acts as an early consumer of the stale state, which is why the attack surface is so wide.
The PHP2Shell Exploit
The php2shell PoC does not bet on a single technique. It ships nine independent chains that cover different target configurations, so the odds of landing a shell are much better across diverse setups. Chains 2 and 9 can get code execution without an LFI at all.
| # | Chain | Needs LFI | Reliability |
|---|---|---|---|
| 1 | Session poisoning + LFI | Yes | High |
| 2 | Deserialization gadgets (Laravel, Symfony, Monolog, Guzzle) | No | Medium |
| 3 | /proc/self/fd temp file race | Yes | Low |
| 4 | phpinfo() temp file leak | Yes | Medium |
| 5 | Log poisoning via User-Agent | Yes | Medium |
| 6 | PEARCMD.php config-create webshell | Yes | Medium |
| 7 | /proc/self/environ injection | Yes | Medium |
| 8 | PHP wrappers (php://input, data://) | Yes | High |
| 9 | UAF disturbance race on the session HashTable | Optional | Low |
Chain 1 is the classic php2shell flow. The attacker fully controls the PHP_SESSION_UPLOAD_PROGRESS field value, PHP writes it into a session file at a predictable path, and because session.use_strict_mode defaults to 0 the attacker can pick any session ID they like. Find an LFI, include the session file, and the PHP payload executes. It is the old session poisoning trick, but now the same primitive rides on a fully patched, critical core bug.
Session Fix → Recon → Framework Detect → Chain 2 (Deser)
→ LFI Scan
→ Chain 1 (Session+LFI) → Chain 9 (UAF Race)
→ Chain 5 (Log Poison) → Chain 7 (environ) → Chain 6 (PEARCMD)
→ Chain 8 (Wrappers) → Chain 4 (phpinfo) → Chain 3 (/proc/fd) → DoneProof of Concept
The repository includes a Docker lab running PHP 8.4.26 with a deliberately weak session configuration, an upload endpoint, an intentional LFI, and a phpinfo page. It makes each chain easy to watch in action.
git clone https://github.com/wakemeiflose/php2shell.git
cd php2shell
docker compose up --build -d
bash scripts/verify.sh
pip install -r exploit/requirements.txt
python3 exploit/php2shell.py -t http://localhost:8080 -c "id"Auto mode walks through every chain. You can force a specific one, race against upload cleanup, or drop into an interactive shell.
# Force a specific chain
python3 exploit/php2shell.py -t http://localhost:8080 --chain 1 -c "id"
# Race mode for targets with session.upload_progress.cleanup = On
python3 exploit/php2shell.py -t http://target --race --threads 20 --attempts 500 -c "id"
# Interactive shell
python3 exploit/php2shell.py -t http://localhost:8080 --shellphp2shell> id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
php2shell> uname -a
Linux abc123 6.8.0 #1 SMP x86_64 GNU/LinuxThere is also uaf_probe.py, a crash probe for the CVE-2026-109619 parser bug. It sends malformed multipart payloads (nested boundaries, 8KB+ headers, null bytes, truncated parts, interleaved containers) and identifies vulnerable targets through crash behavior, without attempting code execution.
Detection and Defence
| Layer | Detection signal |
|---|---|
| Web / app logs | POST bodies containing PHP_SESSION_UPLOAD_PROGRESS, followed by a GET to a different endpoint with the same session cookie |
| Session store | Session files containing |
| Process audit | execve syscalls from php-fpm children |
| Network | Rapid concurrent multipart POSTs sharing one PHPSESSID (UAF race pattern) |
| Availability | Bursts of HTTP 502/503 after multipart POSTs (FPM worker crash from UAF) |
auditctl -a always,exit -F arch=b64 -S execve -F ppid=$(pgrep -d, php-fpm) -k php2shell
grep -rl 'SecRule ARGS:PHP_SESSION_UPLOAD_PROGRESS "@contains
"id:2026109618,phase:2,deny,status:403,msg:'php2shell session poison'"
SecRule REQUEST_HEADERS:User-Agent "@contains
"id:2026109619,phase:1,deny,status:403,msg:'php2shell log poison'"Mitigation
Patch on an emergency basis, outside the normal cycle. There is no full workaround short of the fixed releases, and Sansec predicted mass exploitation within minutes of the patch going public.
If you cannot patch right away, config changes only buy partial cover. Disabling upload progress blocks part of the CVE-2026-109618 surface, but the multipart parser bug on the 109619 side remains, so treat every workaround as temporary:
session.upload_progress.enabled = Off
session.use_strict_mode = 1At the frontend, bound the decoded request size, input and multipart counts, headers per part, and nesting depth. If your application does not use multipart uploads, block those requests before they reach PHP.
My take: if you run PHP anywhere, update today, not next sprint. The patches are small, the exploit is public, and researchers confirmed reproduction within hours of disclosure. On the red team side, the repo is worth studying: the chain selection, the race harness, and the crash probe are clean examples of how one memory bug becomes nine different roads to a shell. Keep the detection rules handy, and check your session files for anything that looks like PHP.