**PHP2Shell** is a pair of critical vulnerabilities in the PHP runtime itself that allow an unauthenticated attacker to run arbitrary code on a web server. It was disclosed and patched in October 2026, and exploitation began within minutes of the patch going public. Because the flaw lives in the language interpreter rather than in any application, even a perfectly secure PHP application is exposed if the server is running an unpatched version.
This writeup explains what PHP2Shell is, how the bugs work, how they are exploited, and what defenders need to do to fix the problem.
What PHP2Shell Is
PHP2Shell is the collective nickname for two vulnerabilities:
- **CVE-2026-109618** – Remote code execution via out-of-memory corruption
- **CVE-2026-109619** – Remote code execution via stale global state
Both are rated **9.2 on the CVSS v4.0 scale (Critical)** and both are **pre-authentication** flaws, meaning an attacker does not need a login, a session cookie, or any interaction from a user. The vulnerabilities affect PHP versions going back to **PHP 5.4.0** and impact every server API that serves web requests, including Apache with `mod_php`, PHP-FPM, `php-cgi`, FrankenPHP, and LiteSpeed.
The name echoes Log4Shell: it is a runtime-level flaw, not an application-level one, and it scales across the entire PHP ecosystem.
The Root Cause: Session Handling and Bailouts
Both bugs share a common theme. When PHP encounters a fatal condition, such as exceeding `memory_limit` or a fatal error during request processing, it uses an internal mechanism called a **bailout** to abort the request. The bailout works, but it does not always clean up the memory and state that were left behind. That stale, partially freed state becomes exploitable when it is accessed again, either later in the same request or in a subsequent request handled by the same worker.
The specific code involved is PHP's **session upload progress handler**, `php_session_rfc1867_callback` in `ext/session/session.c`. This handler caches raw `zval *` pointers into a HashTable so it can track upload progress across request chunks. When the HashTable is rebuilt or destroyed during a progress update, those cached pointers are left dangling, pointing into memory that has already been freed. That is the use-after-free condition at the heart of the exploit.
How the Two CVEs Differ
### CVE-2026-109618: Out-of-Memory Corruption
This flaw is triggered by forcing PHP to exhaust its memory limit during the processing of a multipart request. When the out-of-memory condition hits, PHP bails out, but some internal contexts are left with uninitialized or partially initialized state. Subsequent cleanup then reads that invalid state and treats it as valid memory.
Attackers can reach this condition without sending a request larger than the default `post_max_size` (8 MiB) by using **chunked HTTP/1.1 or length-less HTTP/2 multipart bodies**, or by sending hundreds of empty file parts that inflate the internal `$_FILES` structure while keeping the request body small.
### CVE-2026-109619: Stale Global State
This flaw involves state that survives across requests handled by the same PHP worker. One common path is a **serializer mismatch**: a worker that handles a request with a different session serializer retains that serializer for the next request, causing a decode failure that triggers object destruction on partially deserialized data. Combined with a gadget chain, this can lead to code execution.
Another path is the **`session.upload_progress` handler** itself, which caches pointers into a HashTable. When the session is reinitialized, those pointers are never refreshed, leaving dangling references into freed memory. Public research has also documented a **`var_destroy` reentry use-after-free** that is confirmed by AddressSanitizer and does not require `session.upload_progress` to be enabled.
Exploitation Flow
The exploitation process generally follows these stages:
1. **Trigger** – A crafted multipart POST request activates upload progress tracking. The request includes the upload progress field (`PHP_SESSION_UPLOAD_PROGRESS` by default) before a file part.
2. **Free** – A progress update cycle rebuilds the session data HashTable. The rebuild destroys the previous data tree, freeing the memory that the cached pointers still reference.
3. **Reclaim** – Attacker-controlled data is placed into the freed memory region. The dangling pointer now references structures shaped by the attacker.
4. **Execute** – PHP invokes a function call through the corrupted structures, redirecting execution to a function such as `system()` and running arbitrary commands as the web server user.
Public research has documented multiple independent exploit chains. One verified remote chain achieves a shell on `php:8.4.26-apache` with `uid=33(www-data)` on a mod_php prefork worker. Other chains include handler mismatch attacks, session file injection through a local file inclusion sink, and a `var_destroy` use-after-free that still works on some patched versions.
The attack does not require an upload form, an authenticated session, or any specific application functionality. An endpoint that simply echoes "OK" can still be affected, because the vulnerable processing happens before a single line of application code runs.
Why PHP2Shell Is So Dangerous
### Runtime-Level Exposure
The bug sits in the PHP runtime, not in application code. A well-written WordPress, Laravel, or custom PHP application is equally vulnerable if the underlying PHP version is unpatched. There is nothing the application developer could have done differently to prevent it.
### Pre-Auth and No User Interaction
Both CVEs are reachable without authentication and without any user action. Any internet-facing PHP service can be attacked directly.
### The Feature Is On by Default
The vulnerable feature, `session.upload_progress.enabled`, is enabled by default in PHP. This means the attack surface is present on virtually every PHP deployment unless it has been explicitly disabled.
### Weaponization Is Immediate
Details leaked ahead of coordinated disclosure, and active exploitation was observed **19 minutes** after the patch was published. Cloudflare and other edge providers deployed WAF rules before the fixes even shipped, and honeypots captured exploitation attempts shortly after the patch went live.
### Secrets Are the Real Prize
The code execution runs as the PHP worker user, which typically has read access to configuration files, `.env` files, database credentials, API keys, SMTP credentials, and cloud access keys. No privilege escalation is needed to steal the secrets that matter most.
How to Detect Exposure
### Check the PHP Version
The most direct method is to verify the installed PHP version. On Debian-based systems:
```bash
apt list '?installed ?name(^php[0-9.]+$)'
```
Any PHP version **below** 8.2.35, 8.3.36, 8.4.27, or 8.5.12 is vulnerable.
### Passive Fingerprinting
Tools such as the `php2shell` PoC repositories provide a passive check mode that reads `X-Powered-By` headers and response banners to fingerprint PHP versions without sending exploit traffic.
### Perimeter Scanning
Many organizations do not have a complete inventory of PHP installations, because PHP is often bundled, hidden, or forgotten. Nuclei templates and similar scanners can identify unpatched PHP across the estate without sending exploit-like payloads.
### Log Review
Look for **chunked multipart requests**, **length-less HTTP/2 bodies**, and **anomalous `$_FILES` structures** in PHP-FPM, Apache, and application logs. These patterns may indicate exploitation attempts.
Mitigation and Remediation
### The Complete Fix: Patch PHP
Upgrade to **8.2.35, 8.3.36, 8.4.27, or 8.5.12**, then restart every PHP worker (Apache, PHP-FPM, or the entire system). The patches were released on **21 October 2026**.
```bash
sudo apt update && sudo apt upgrade
sudo systemctl restart apache2 # or php8.x-fpm
```
### Temporary Mitigations (Bridge Only)
If immediate patching is not possible, the following measures reduce exposure but do not eliminate the vulnerability:
- **Disable `session.upload_progress`** in `php.ini` or, more robustly, via FPM pool configuration with `php_admin_flag`, so it cannot be overridden by `.user.ini`:
```ini
session.upload_progress.enabled = Off
```
- **Block multipart requests** at the reverse proxy or WAF layer.
- **Deploy WAF rules** for your edge provider. Cloudflare deployed managed rules on all plans shortly after disclosure.
Disabling `session.upload_progress` blocks the upload-progress attack chains, but it **does not** block CVE-2026-109618 or the serializer-mismatch path. Patching remains the only reliable remediation.
### End-of-Life Branches
PHP 8.1 and older no longer receive official updates. Some distributions and LTS vendors provide backported fixes. The PHP team also published minimal source backports for PHP 7.4, 8.0, and 8.1 to help those building from source or without an LTS vendor.
### Incident Response
If exploitation is suspected:
- Isolate the host from the network immediately.
- Rotate all secrets accessible to the PHP worker: database credentials, API keys, SMTP credentials, and cloud access keys.
- Preserve forensic evidence before rebuilding.
- Hunt for persistence mechanisms such as webshells and scheduled tasks.
Practice PHP2Shell Hands-On
Reading about PHP2Shell is a start, but hands-on practice is what makes it stick. Break The Root's lab environment lets you safely trigger the out-of-memory corruption, observe the use-after-free behavior, and walk through the full exploitation chain, then apply the fix and verify the remediation yourself.
Explore the Break The Root Arena to find labs covering PHP2Shell, memory-safety vulnerabilities, and other critical exploits. Learn the bug, understand the exploit, fix the vulnerability, and build the skills that make you job-ready.