Summary:
PHP2Shell is a pair of PHP memory-safety vulnerabilities that can turn a deliberately triggered fatal error into unauthenticated remote code execution. Calif’s write-up explains how apparently mundane out-of-memory failures leave corrupted or dangling internal state—and how a later cleanup operation or request turns that state into an exploit. The flaws are in PHP itself, not necessarily in application code.calif
How the attack works
The researchers connect three pieces:
- Trigger a precisely timed memory-limit failure. Specially structured multipart uploads can consume far more PHP heap memory than their HTTP-body size suggests, allowing an attacker to exhaust PHP’s memory limit despite ordinary request-size restrictions.
- Interrupt an unsafe state transition. PHP sometimes updates bookkeeping or publishes pointers before completing an operation that can fail. A fatal error skips the remaining initialization or cleanup.
- Make PHP trust the leftover state. Request shutdown—or a subsequent request handled by the same worker—then processes partially initialized structures or pointers into freed memory. That enables memory disclosure and, with further exploitation, code execution.calif
The underlying pattern is: publish state → do fallible work → finish initialization or restore state. If the middle step aborts, later code can mistake incomplete state for valid state.calif
Why Apache and PHP-FPM differ
The article identifies three exploitable paths, with an important deployment distinction:
| Path | What goes wrong | Scope demonstrated |
|---|---|---|
| Partially initialized hash-table entry | PHP counts an entry as live before its initialization finishes; shutdown then interprets stale memory as a valid entry. | Apache mod_php, PHP 7.3 and later, against arbitrary PHP scripts. |
| Stale session-upload-progress state | An early fatal error skips session cleanup, leaving pointers into a freed request heap for the next request. | Apache mod_php, with the earliest affected path dating to PHP 5.4.0, against arbitrary PHP scripts. |
| Abandoned unserialization state | A fatal error during decoding leaves shared bookkeeping dangling; a subsequent upload-progress request reuses it before normal initialization resets it. | Both mod_php and PHP-FPM, but dependent on reachable application behavior. |
All three paths are described and demonstrated in the write-up.calif
PHP-FPM avoids the first two paths because it exits the worker when request startup fails. It remains vulnerable through the third path because a fatal error after successful startup can leave the worker alive. Crucially, that path does not require the application to unserialize attacker-supplied objects: ordinary session decoding or legitimate serialized data can provide the operation that the attacker interrupts.calif
What the researchers demonstrated
Calif reports pre-authentication code execution against WordPress, Drupal, Joomla, MediaWiki, Roundcube, Magento, phpMyAdmin, Nextcloud, Laravel, Symfony, and WHMCS. That does not mean every deployment has identical exploit conditions: PHP version, server integration, and application behavior determine which path is available.calif
The research lesson
This was human-directed, AI-assisted vulnerability research—not an autonomous model finding everything from a generic “audit PHP” prompt. Ryan Austin used knowledge of PHP’s historical memory-limit vulnerabilities to guide an LLM through allocation, fatal-error handling, and shutdown code. After the first finding, they generalized the unsafe state-transition pattern and used it to locate additional variants.calif
The historical thread matters: a related out-of-memory issue was reported in 2020 and revisited during a 2024 audit, but exploitation had not been established. Calif’s contribution was connecting such failure conditions to reliable exploitation paths.calif
Remediation
The article’s recommendation is to patch immediately. Fixed releases are PHP 8.2.35, 8.3.36, 8.4.27, and 8.5.12.tenable
Its suggested configuration workarounds are explicitly temporary and partial. In particular, setting memory_limit=-1 removes PHP’s memory cap and increases memory-exhaustion risk; it is not an equivalent substitute for upgrading.