
SonicWall Capture Labs threat research team became aware of PHP2Shell, a pair of critical memory-safety vulnerabilities in PHP core tracked as CVE-2026-109618 and CVE-2026-109619, assessed their impact, and developed mitigation measures. The flaws, known together as PHP2Shell, are in the PHP interpreter itself rather than any single application, so every site running an affected build on the vulnerable configuration is exposed regardless of its own code. They let an unauthenticated attacker run arbitrary code on the server by sending ordinary HTTP requests. CVE-2026-109618 is an out-of-memory bailout that can interrupt request parsing while data is only partly initialized, leaving corrupted state that request shutdown then treats as valid. CVE-2026-109619 is request-owned state that survives in a persistent worker after the heap backing it is freed, reached through the session upload-progress handler before any application code runs. The weaponized chain combines a use-after-free (CWE-416) with access of an uninitialized pointer (CWE-824), then uses an out-of-bounds heap read to defeat ASLR. Both issues were published on October 21, 2026 under the title "Avoid state corruption via bailout" and carry a CVSS 4.0 base score of 9.2 (Critical). The fix shipped in PHP 8.5.12, 8.4.27, 8.3.36, and 8.2.35, so administrators should upgrade without delay. Exposure is greatest on Apache with mod_php in prefork mode, the configuration in which request shutdown runs after a failed request and makes the corrupted state reachable; the two CVEs are related defect classes fixed together, not a mandatory two-step chain.
Affected and fixed versions:
| Branch | Affected | First fixed |
|---|---|---|
| PHP 8.5 | 8.5.0 to 8.5.11 | 8.5.12 |
| PHP 8.4 | up to 8.4.26 | 8.4.27 |
| PHP 8.3 | up to 8.3.35 | 8.3.36 |
| PHP 8.2 | up to 8.2.34 | 8.2.35 |
| PHP 8.1 and earlier | end of life | no fix |
PHP is the server-side interpreter behind a large share of the web, running under Apache mod_php, PHP-FPM, and the command line. PHP2Shell is not a weakness in WordPress, Drupal, Joomla, or any other application; those products appear in public demonstrations only because they are the most common PHP applications on the internet. The defect is in the interpreter's memory manager and request lifecycle, so the application code never runs during the vulnerable window, which is what makes the issue reachable before authentication.

The shared root cause is a bailout that fires in the middle of a multi-step state change. PHP handles unrecoverable conditions, the important one here being an allocation that hits memory_limit, with zend_bailout(), a longjmp back to a safe point set up at the request boundary. When an allocation fails partway through building a structure, that longjmp unwinds past the code that would have finished initializing it, leaving an object that looks valid to later code but holds a dangling or half-set field. Two consumers then use that invalid state: request shutdown, which on Apache mod_php runs even after a failed request startup, and the next request handled by the same persistent worker. CVE-2026-109618 lives in request-variable registration, where deeply nested POST keys allocate an array at every level with no check that the allocation succeeded before continuing.

CVE-2026-109619 is what makes the corruption reachable without privilege. PHP's session upload-progress feature, enabled by default, lets a client track a large file upload by posting a field named PHP_SESSION_UPLOAD_PROGRESS. The handler for that field decodes and re-encodes the session during request startup, before RINIT and before any user code can run, operating on data the attacker shapes. That is precisely the window in which a co-resident oversized field has driven the allocator into the bailout described above, so the serializer runs over a freed and reclaimed block.

The fix in PHP 8.5.12 is broad rather than a single hunk. In the session extension a new per-request flag, PS(in_rfc1867_update), forces the upload-progress (de)serializer into an "incomplete classes only" mode so attacker-influenced session data can no longer instantiate real objects or reach __wakeup() during startup. In the variable parser, ZEND_HAS_OOM() checks were threaded through add_post_var() and php_register_variable_ex(), and empty-array creation was switched to array_init_singleton(), so the engine stops building instead of continuing on a half-initialized object. The allocator itself gained a delayed out-of-memory path so a failed allocation returns cleanly instead of bailing in the middle of an operation.

The following conditions must be met for successful exploitation of PHP2Shell:
Exploiting PHP2Shell needs no credentials and no special tooling, only HTTP access to any URL the PHP application serves. The attacker sends a sequence of multipart/form-data POST requests, each carrying a PHP_SESSION_UPLOAD_PROGRESS field and an oversized field that drives the worker to the out-of-memory bailout. The triggering request is byte-identical whether the outcome is a shell or merely a faulted worker, because whether the corrupted state yields code execution depends on target-side heap layout, not on anything in the packet.

With the free condition created, the attacker reclaims the freed block with a forged zend_string whose length field claims 4096 bytes. When the session serializer re-encodes that string it reads 4096 bytes out of bounds, and those live heap bytes are emitted to the client inside a Set-Cookie: PHPSESSID= header. The mode of the 2 MB-aligned pointers in that leak is the Zend memory manager chunk base (stage one); a second leak through a 56-byte fake HashTable exposes a function pointer that resolves to the libphp load base (stage two). Both defeat Address Space Layout Randomization. In the final stage the attacker forges a HashTable at a known offset in the leaked chunk whose destructor pointer is set to a chosen libphp function, then overwrites the dangling PS(http_session_vars) reference so that session teardown calls that function with an attacker-controlled command string.

On current PHP 8.5 the destructor gadget is zif_passthru rather than zif_system. The system() implementation on this branch writes the command's return value through a pointer that is invalid in the forged-destructor context and faults the worker, while passthru() takes the code path that never performs that write, so the command runs to completion. The result is command execution as the web-server user, which in a typical container image is www-data or root, reached with no authentication over plain HTTP.
Video Demonstration
| Component | Value | Purpose |
|---|---|---|
| Transport | multipart/form-data POST to any PHP URL | Reaches request parsing and the upload-progress handler |
| Trigger Field | PHP_SESSION_UPLOAD_PROGRESS | Runs the session serializer pre-RINIT (CVE-2026-109619) |
| OOM Field | one field of about memory_limit plus 12 MB | Forces zend_bailout() mid-parse (CVE-2026-109618) |
| Reclaim Primitive | forged zend_string, length 0x1000 | Re-reads 4096 bytes out of bounds into Set-Cookie |
| Disclosure Channel | Set-Cookie: PHPSESSID= | Leaks heap, yielding chunk base then libphp base |
| Control Primitive | forged HashTable, pDestructor set to a libphp gadget | Session teardown calls the gadget |
| Call Gadget | zif_passthru (not zif_system on 8.5) | Runs the command without the return-value write that faults |
| Impact | command execution as the web-server user | Unauthenticated remote code execution over HTTP |
To ensure SonicWall customers are prepared for any exploitation that may occur due to these vulnerabilities, the following signatures have been released:
| Signature ID | Signature Name |
|---|---|
| IPS: 22573 | PHP Zend Memory Manager Remote Code Execution (CVE-2026-109618) 4 |
| IPS: 22565 | HTTP Request with PHP_SESSION_UPLOAD_PROGRESS Object (CVE-2026-109619) |
| IPS: 22566 | PHP Session Stale Global State Use-After-Free (CVE-2026-109619) |
| IPS: 4636 | PHP Warning Message (Suspicious Session ID) |
The risks posed by PHP2Shell can be mitigated or eliminated with the following measures:
PHP2Shell was disclosed on October 21, 2026 through the PHP project's security advisories GHSA-w8hw-w9fx-j79w (CVE-2026-109618) and GHSA-6wmf-4rv5-p3rp (CVE-2026-109619), published together under the title "Avoid state corruption via bailout" as part of a coordinated disclosure. The fixes shipped the same day in PHP 8.5.12, 8.4.27, 8.3.36, and 8.2.35.
Third-party vulnerability database mirrors:
Share This Article

An Article By
An Article By
Security News
Security News