A critical security vulnerability family dubbed PHP2Shell has hit core PHP, tracked across two primary CVEs: CVE-2026-109618 and CVE-2026-109619. With a CVSS score of 9.2 (Critical), these flaws allow unauthenticated remote attackers to trigger memory corruption and achieve Remote Code Execution (RCE) or sensitive process memory disclosure across millions of web targets.
Whether you are running legacy monoliths, containerized microservices, or standard LAMP/LEMP stacks (WordPress, Drupal, Laravel, Symfony), here is an engineering breakdown of how PHP2Shell works, what versions are vulnerable, and how to patch or mitigate it today.
1. What is PHP2Shell?
PHP2Shell represents a pair of memory-safety vulnerabilities residing in PHP's request lifecycle engine, particularly around how the Zend Memory Manager (Zend MM) handles out-of-memory (OOM) bailouts and persistent global state across multi-request worker lifecycles.
- CVE-2026-109618 (OOM Bailout Corruption): Occurs when specific large or nested multi-part form requests trigger an early memory ceiling bailout before the engine completes internal cleanup routines.
-
CVE-2026-109619 (Stale Session State / UAF): Abuses the
session.upload_progresstracking callback. When an OOM occurs after initialization but before the user script executes, stale pointers in the process's global session structures survive into subsequent worker requests, resulting in Use-After-Free (UAF) conditions and controllable function pointer hijacking.
Because the vulnerable logic fires during the pre-request initialization (pre-RINIT) stage, no application code or authentication is required to reach the vulnerable code paths.
2. Technical Anatomy of the Attack Chain
The exploit leverages a 3-stage chain targeting web servers reusing worker processes (such as mod_php prefork or specific PHP-FPM pool setups)
-
Memory Ceiling Probe & Chunk Base Leak: A crafted multipart request pushes the parser past PHP's configured
memory_limit, triggering an unhandled allocation failure. Becausesession.upload_progresshas already registered tracking structures, the abrupt bailout leaves internal state un-cleared. On the next request, the dangling pointer is reflected in the response (such as inSet-Cookieor error channels), leaking the Zend chunk address. -
Library Base Resolution: The leak primitive is repeated or shifted to discover the memory mapping offset of
libphp.soor the host binary. -
Execution Hijacking: A forged HashTable references the stale chunk; when PHP’s garbage collector or destructor (
zval_ptr_dtor) tears down the corrupted structure, control flow jumps tozif_systemor arbitrary executable memory.
3. Affected Versions
The vulnerabilities impact standard builds across major PHP releases:
| Major Branch | Vulnerable Releases | Official Fixed Release |
|---|---|---|
| PHP 8.5 | <= 8.5.11 | 8.5.12 |
| PHP 8.4 | <= 8.4.26 | 8.4.27 |
| PHP 8.3 | <= 8.3.35 | 8.3.36 |
| PHP 8.2 | <= 8.2.34 | 8.2.35 |
| PHP 7.x & 8.0 / 8.1 | All versions (EOL) | Vendor backports only |
Note on Distro Backports: Debian, Ubuntu, RHEL, and Alpine backport patches to their standard package repos. A banner reading
PHP 8.2.27might already be patched if your distro released a-0ubuntu...or-1~deb...security build.
4. How to Audit Your Environment
Check Local / Running PHP Version
Run the following on your application nodes:
bash
# Check the exact compiled patch level
php -v
# On Debian/Ubuntu systems:
dpkg -l | grep -E "php[0-9.]*-common|libapache2-mod-php"
# On RHEL/Alma/Rocky systems:
rpm -qa | grep -E "php-common|php-fpm"
# Inside running Docker containers:
docker exec -it <container-name> php -v
Audit Repositories & CI/CD Pipelines
If you manage many repositories or microservices, audit declared container base images and composer.json requirements using GitHub CLI or scanner actions:
Bash
# Scan a repository for unpatched PHP definitions
npx php2shell-repo-audit --repo your-org/your-app
5. Remediation Steps
Step 1: Upgrade PHP (The Permanent Fix)
Pull the latest official security release for your active branch.
For Docker / Kubernetes Workflows:
Floating tags (e.g., php:8.3-fpm or php:8.4-alpine) must be pulled fresh and rebuilt without caching:
Bash
docker compose build --no-cache --pull
docker compose up -d
For Ubuntu / Debian (using ondrej/php PPA or official repos):
Bash
sudo apt update
sudo apt install --only-upgrade php8.3-fpm php8.3-cli libapache2-mod-php8.3
sudo systemctl restart php8.3-fpm
sudo systemctl restart apache2 # or nginx
Step 2: Emergency Hot-Fix Mitigations (If You Cannot Patch Immediately)
If you must wait for an maintenance window, deploy the following configuration adjustments to your php.ini or FPM pool configuration to neutralize the known exploit chain:
1. Disable Upload Progress Tracking
The primary public exploit chain requires session.upload_progress initialization to stage the UAF:
Ini, TOML
; /etc/php/8.x/fpm/php.ini or conf.d/99-security.ini
session.upload_progress.enabled = 0
2. Restrict Multipart & Memory Pressure
Lower the headroom an attacker has to trigger pre-RINIT allocation exhaustion:
Ini, TOML
; Constrain the maximum post size (adjust based on your app's legitimate needs)
post_max_size = 16M
; Limit nesting depth to prevent parser call-stack explosion
max_input_nesting_level = 32
; Suppress error and backtrace leaks in production
display_errors = 0
display_startup_errors = 0
3. Enforce Aggressive Worker Recycling
The exploitation of stale state relies on multiple sequential requests hitting the same worker process. Force PHP-FPM or Apache to kill workers frequently:
In PHP-FPM (/etc/php/8.x/fpm/pool.d/www.conf):
Ini, TOML
pm.max_requests = 100
In Apache Prefork (mpm_prefork.conf):
Apache
MaxConnectionsPerChild 100
Warning: Do NOT set memory_limit = -1 as a workaround. While it stops PHP's internal memory manager from hitting soft bailouts, it transfers allocation exhaustion to Linux OOMKiller, risking systemic denial of service across the entire host.
Top comments (0)