Skip to content
Exploit for Improperly Controlled Sequential Memory Allocation in Php

Exploit for Improperly Controlled Sequential Memory Allocation in Php

Sploitus • October 26, 2026

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.