Back Sploitus Exploit for Improperly Controlled Sequential Memory Allocation in Php
A comprehensive research lab and exploit reproduction toolkit for **CVE-2026-109619** (also known as **PHP2Shell**), a critical vulnerability in PHP's deserialization subsystem affecting persistent worker architectures (such as **PHP-FPM**), enabling pre-auth cross-request use-after-free and arbitrary remote code execution under the privileges of the web process.
## 1. Technical Analysis (Root Cause Analysis)
- **Component:** PHP Deserialization Subsystem (`ext/standard/var_unserializer.re`)
- **Affected Versions:** PHP `8.2.0`–`8.2.34`, `8.3.0`–`8.3.35`, `8.4.0`–`8.4.26`, `8.5.0`–`8.5.11`
- **Vulnerability Class:** CWE-672: Operation on Resource After Expiration or Release / CWE-416: Use After Free
- **CVSS Score:** 9.2 (CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N)
1. **Unchecked Bailout in Deserialization:**
During deserialization, `php_var_unserialize_init()` initializes reference tracking structures and destruction chains in `BG(unserialize).data`, incrementing `BG(unserialize).level = 1`. If an in-script fatal error or memory exhaustion occurs (e.g. inside `__wakeup()` or class constructor), PHP executes `zend_bailout()`. Ordinary request cleanup routines (`php_var_unserialize_destroy()`) are skipped, leaving dirty global pointers and unzeroed reference counters in the worker's memory space.
2. **Persistent Worker Survival (PHP-FPM):**
In persistent multi-request architectures like PHP-FPM (`pm = static` or `dynamic`), child worker processes survive in-script fatal errors across HTTP requests without terminating. When a subsequent HTTP request arrives at the same worker process, it inherits the dirty `BG(unserialize)` parser state from the preceding aborted request.
3. **Cross-Request Reference Poisoning & Memory Corruption:**
When the surviving worker enters `unserialize()` again, it detects `BG(unserialize).level > 0` and mistakes the new payload for a nested call within an already-active deserialization session.
- By supplying cross-request references (`R:1`), the deserializer resolves pointers into previously freed memory.
- By reclaiming the dangling 4096-byte `first_dtor` structure in Zend MM through controlled POST allocations, the attacker constructs a fake `HashTable` whose `pDestructor` points directly to `zif_system`.
- Upon destruction, arbitrary shell commands are executed with `www-data` privileges.
│ ├── nginx-fpm/ # Vulnerable Nginx 1.27 + PHP-FPM 8.4.26 environment
│ │ ├── nginx.conf # FastCGI reverse proxy configuration
│ │ ├── php.ini # PHP runtime configuration (memory_limit = 8M)
│ │ ├── fpm-pool.conf # PHP-FPM static pool configuration (1 worker)
│ │ ├── lab.sh # Container management script (up, down, status, logs)
│ │ ├── test_behavior.py # Diagnostic script verifying worker survival
│ │ └── webroot/ # Target web application endpoints
│ │ ├── cve_109619_poc.php # Two-stage exploitation endpoint
│ │ ├── cve_109619_demo.php # Diagnostic demonstration
│ │ └── index.php # Worker status and PID baseline
│ ├── docker-compose.yml # Multi-container lab orchestration
│ ├── run_lab.sh # Lab startup script
│ └── stop_lab.sh # Lab teardown script
│ ├── poc.py # Full-featured network PoC (arbitrary RCE, interactive shell, reverse shell)
│ ├── scan.py # Non-destructive passive vulnerability assessment scanner
│ ├── poc_raw.py # Low-level Zend MM memory corruption exploit (zif_system)
│ ├── revshell.py # Standalone reverse TCP shell handler
│ ├── profiles.json # Affected version catalog and fix matrix
│ └── ui.py # Terminal output and status helpers
│ ├── suricata.rules # Suricata / Snort network IDS detection signatures
│ ├── modsecurity.conf # ModSecurity / OWASP CRS perimeter WAF rules
│ └── semgrep.yml # Semgrep SAST rule for unsafe unserialize calls
│ ├── showcase.gif # Terminal demonstration GIF
│ └── showcase.webm # High-definition screencast
│ ├── analysis.md # In-depth technical root cause analysis
│ └── licensing.md # License details
├── test_all.sh # Automated verification suite for all lab services
├── php2shell.py # Root CLI entrypoint
├── Makefile # Automation recipes
└── README.md # Research report and documentation
Docker is required to run the local lab environment.
# Start lab containers (Nginx 1.27 + PHP-FPM 8.4.26)
Once running, the following endpoints are exposed:
- **Webroot Oracle:** ` (SAPI & PID verification)
- **Target Vulnerable Endpoint:** `
- **Diagnostic Endpoint:** `
To reset the lab with clean logs and fresh worker memory:
### 4.1. Scenario 1: Non-Destructive Vulnerability Assessment (`scan.py`)
Performs fingerprinting of PHP version, FastCGI SAPI, worker survival during in-script memory exhaustion, and cross-request state reuse without executing arbitrary commands.
python3 poc/scan.py -u
# or: ./php2shell.py -u --check
version: php 8.4.26 (vulnerable, php 8.4.26 whoami
uid=33(www-data) gid=33(www-data) groups=33(www-data)
### 4.3. Scenario 3: Real TCP Reverse Shell (`--revshell`)
Launches a local listener on port `4444`, triggers the exploit against the PHP-FPM worker, and executes a reverse connection from inside the Docker container back to the host:
python3 poc/poc.py -u --revshell
endpoint:
trigger: payload sent, waiting for connection...
shell: interactive session open (type commands, ctrl+d to exit)
uid=33(www-data) gid=33(www-data) groups=33(www-data)
### 4.4. Scenario 4: Automated Verification Suite (`test_all.sh`)
Executes an automated end-to-end regression test suite verifying container health, passive scanner accuracy, RCE payload execution, reverse shell callbacks, and unit tests:
PHP maintainers remediated the vulnerability by introducing `php_basic_early_rinit()` called after `zend_activate()` and before `sapi_activate()`:
1. Zeroes all serialization and deserialization state (`BG(unserialize)`) unconditionally before input processing begins.
2. Resets URL rewriters and locks before request execution.
3. Explicitly cleans executor backtraces and superglobals (`EG(last_fatal_error_backtrace)`).
- **Update PHP Runtime:** Upgrade PHP to patched versions:
- **Disable Dangerous Deserialization:** Audit codebases and avoid passing untrusted user input to `unserialize()`. Enforce `allowed_classes => false` or transition to structured formats (`json_decode()`).
- **WAF Inspection:** Reject incoming requests attempting serialized object injection (`O:[0-9]+:`) at perimeter reverse proxies.
## 6. Detection Signatures & Security Rules
The repository provides production-ready defensive rules in the [`rules/`](rules/) directory:
- **Network IDS (Suricata / Snort):** [`rules/suricata.rules`](rules/suricata.rules) detects anomalous inbound serialized object payloads and cross-request reference injections.
- **Web Application Firewall (ModSecurity / CRS):** [`rules/modsecurity.conf`](rules/modsecurity.conf) inspects HTTP request bodies and drops unauthorized PHP serialized objects at the perimeter.
- **Static Application Security Testing (Semgrep):** [`rules/semgrep.yml`](rules/semgrep.yml) audits source code for unvalidated `unserialize()` calls.
- **Vulnerability Discovery & Research:**
- Calif & Wiz Research for the public research and architectural analysis of the PHP2Shell vulnerability family.
- devploit for early vulnerability tracking and baseline research.
- **Extended Research Suite & Enhancements:**
- **Interactive Remote Shell (`-i`) & Command Execution (`-c`):** Full cross-request state poisoning and interactive terminal session.
- **Standalone TCP Reverse Shell (`--revshell`):** Automated listener and container-to-host callback integration.
- **Reproducible Laboratory:** Complete Nginx 1.27 + PHP-FPM 8.4.26 Docker Compose test environment.
- **Defensive Engineering:** Added production-ready Suricata IDS rules, ModSecurity WAF rules, and Semgrep SAST signatures.
- **Automated Verification:** End-to-end verification suite (`test_all.sh`) and regression unit tests.
The full story
This article is one source in a clustered incident — the cluster page carries the summary, timeline and every other outlet covering it.
