CrowdStrike Protects Customers from Threats Delivered via PHP2Shell

Two critical memory-corruption RCE vulnerabilities tracked as CVE-2026-109618 and CVE-2026-109619 have been disclosed in core PHP.

October 21, 2026

• • From The Front Lines

Two critical memory-corruption remote code execution (RCE) vulnerabilities, collectively dubbed PHP2Shell, have been disclosed in core PHP, the underlying engine powering a vast portion of the web. The CrowdStrike Falcon® platform provides out-of-the-box visibility and detection coverage for post-exploitation activities of both vulnerabilities.

Tracked as CVE-2026-109618 and CVE-2026-109619, both vulnerabilities carry a CVSS 4.0 score of 9.2 (Critical) and are classified as CWE-824: Access of Uninitialized Pointer. Both allow an unauthenticated remote attacker to execute arbitrary system commands on an affected web server over HTTP.

The two issues share a single root cause. When PHP encounters a fatal condition (exhausting its configured memory_limit, exceeding the maximum execution time, or losing the client connection), it does not return an error up the call stack. It raises a fatal error and uses longjmp() to abandon the current execution context outright, aborting the request. Any C function caught midway through building a data structure is simply discarded: no destructors run, no bookkeeping is rolled back, and nothing records that the structure is incomplete. PHP then proceeds into an otherwise ordinary cleanup sequence that assumes every structure is in a consistent state.

The two CVEs describe where that assumption breaks:

  • CVE-2026-109618 (RCE via out-of-memory) covers contexts that leave request memory uninitialized. A data structure is marked as containing an entry before the memory backing that entry is allocated; when the allocation is the one that crosses the memory limit, the entry is left holding stale heap data. The failing request's own cleanup routine then reads and branches on those bytes.
  • CVE-2026-109619 (RCE via fatal errors) covers contexts that leave global state invalid. A module-level global still points into the per-request heap that shutdown has already freed, and still reports its operation as active. A subsequent request handled by the same worker process reads or branches on that freed memory.

In both cases, the attacker controls the trigger and, critically, the data that PHP ultimately misinterprets. CrowdStrike has verified end-to-end that a single anonymous POST request can cause an Apache mod_php worker to launch a shell process and execute an attacker-specified command, with no authentication, no session, and no vulnerable application code involved.

What elevates these vulnerabilities is their placement in the execution stack. PHP parses incoming request data before extensions are initialized and before a single line of application code is compiled, so exploitation is independent of what the server hosts. A meticulously maintained application is exposed to precisely the same degree as a neglected one. The attack is also remarkably quiet: The worker process does not crash, the server returns a standard HTTP 200 response, and application logs record nothing unusual.

In our testing, we developed an exploit code that operated consistently across x86-64 and ARM architectures, remained reliable across worker restarts, and on ARM-based hosts, which are increasingly common in cloud fleets, defeated Address Space Layout Randomization (ASLR) using only HTTP requests.

Technical Details

The Shared Root Cause: longjmp() Without Unwinding

Both vulnerabilities live in Zend, the engine at the heart of PHP. Zend is the C implementation that compiles PHP source into bytecode, executes it in a virtual machine, and manages everything the language needs at runtime: variables, arrays, objects, reference counting, and memory allocation. Originally written by Zeev Suraski and Andi Gutmans (the name is a contraction of their first names) and introduced with PHP 4, it is not an optional component or a third-party add-on; it ships inside every PHP build, which is why its source lives in the Zend/ directory of php-src. In practice, this means a defect in Zend is a defect in the PHP engine itself, affecting every application, framework, and extension running on the interpreter, regardless of how that code is written.

Two pieces of core Zend machinery are central to the issues described here: the HashTable, which backs every PHP array including the $_POST superglobal, and the Zend Memory Manager (Zend MM), PHP's own per-request allocator that sits between PHP and the operating system. PHP's fatal-error path terminates in zend_bailout(), which performs a LONGJMP out of the current C call stack:

// Zend/zend.c
ZEND_API ZEND_COLD ZEND_NORETURN void _zend_bailout(const char *filename, uint32_t lineno)
{
   ...
   CG(unclean_shutdown) = 1;
   ...
   LONGJMP(*EG(bailout), FAILURE);
}

Nothing between the fatal error and the enclosing zend_catch block executes. Any caller that mutated the state and relied on reaching its own cleanup code leaves that state behind. This is safe only if every such caller is written with abrupt termination in mind, and several are not.

CVE-2026-109618: Uninitialized Request Memory, Consumed During the Same Request

The canonical instance is _zend_hash_str_add_or_update_i() in Zend/zend_hash.c, the helper behind every string-keyed insert into a PHP array. On the insert path, it increments both element counters, takes a pointer to the new bucket, and only then allocates a copy of the key:

// Zend/zend_hash.c
add_to_hash:
   idx = ht->nNumUsed++;
   ht->nNumOfElements++;
   p = ht->arData + idx;
   p->key = key = zend_string_init(str, len, GC_FLAGS(ht) & IS_ARRAY_PERSISTENT);

If zend_string_init() exceeds memory_limit, Zend MM raises a fatal error that ends in zend_bailout(). Execution never returns to complete the bucket. Now, the table reports one more entry than it actually contains, and that entry's value, hash, and key pointer are whatever bytes the allocator last left in that memory; the bucket array is allocated with an uninitialized emalloc(), with only the hash region explicitly reset.

During teardown, php_request_shutdown() destroys the superglobals, and zend_array_destroy() iterates nNumUsed buckets. The stale entry is passed to i_zval_ptr_dtor(), which treats the leftover bytes as a reference-counted value: It dereferences the pointer they contain, decrements the reference count at that address, and (if it reaches zero) dispatches through a function-pointer table indexed by a type tag that also came from the leftover bytes.

Triggering the condition requires nothing more than a URL-encoded POST body, because PHP populates $_POST inside php_hash_environment(), which php_request_startup() calls before zend_activate_modules() and before any script is compiled. A field with a nested name such as ZZZZ…[x]=1 causes php_register_variable_ex() to insert the outer name as a new array key, so the attacker directly controls the length of the key that must be allocated. Sizing that name to cross memory_limit places the bailout precisely on the key allocation.

Control over the leftover bytes comes from the allocator's determinism. Zend MM serves small allocations from per-size free lists in strict last-in-first-out order and never zeroes reclaimed blocks. An earlier field in the same request, one whose value PHP copies, decodes, and immediately releases, can therefore be sized so that its freed contents occupy exactly the block the corrupted array subsequently claims. The stale entry becomes a fully attacker-defined value, which we used to synthesize a counterfeit array whose destructor pointer resolves to libc's system(), with the command string resident in the same block.

The only server-side artifact of a successful attempt is a generic fatal error in the web server log:

PHP Fatal error:  Allowed memory size of 16777216 bytes exhausted (tried to allocate 3670048 bytes) in Unknown on line 0

The in Unknown on line 0 is the tell: It confirms that no PHP file was ever executing.

CVE-2026-109619: Invalid Global State, Consumed by a Subsequent Request

The second CVE follows the same mechanism but the damage lands in process-scoped state, which shifts the exploitation window to a later request. Here, the interrupted operation is one that caches state in a global variable and depends on reaching its own cleanup call to clear it. PHP caches the active serializer and unserializer contexts in module globals and reuses the cached pointer whenever the nesting level is non-zero. Only the matching destroy call decrements that level and clears the pointer, and only an extension's request-initialization routine resets it. Session handling behaves comparably, holding the session identifier, handler state, session variables, and upload-progress state in globals cleared by dedicated shutdown routines.

When a fatal error interrupts one of these operations, the matching teardown is skipped. The global retains its pointer, and the nesting counter still asserts that the operation is active. Request shutdown then frees the entire per-request heap, but the global survives because it belongs to the process rather than the request. PHP is left holding a module-level pointer into memory it has already released.

Two factors make this reachable and controllable:

  • Worker reuse: In persistent-worker deployments such as Apache mod_php, the same process handles the next request. The stale global is found intact, the operation is believed to be in progress, and the freed pointer is read, written through, or branched on. Whatever occupies that memory by then is attacker-influenced, because the intervening request is also the attacker's: They choose its size and contents, and Zend MM's allocation behavior is deterministic enough to place them reliably. PHP-FPM terminates a worker whose request startup fails, which removes the reuse for interruptions in the startup phase; an interruption inside an already-running script leaves the FPM worker alive and equally exposed.
  • Startup ordering: php_request_startup() parses request variables before running extension request-initialization callbacks. Multipart upload-progress handling therefore executes session and serialization code before the extensions that own those globals have had any opportunity to reset them, allowing a stale pointer to be consumed in the earliest phase of a request.

Beyond memory exhaustion, this condition can be reached through execution timeouts and aborted client connections, each of which routes through the same zend_bailout() path. Exploitation is consequently a two-stage operation: One request poisons the worker, a second triggers the use of the freed memory.

Vulnerability Impact

The severity of both issues stems from their position early in request handling. Because the corruption occurs while PHP is still assembling internal request variables, before extension initialization and before any application code runs, every reachable PHP endpoint on an affected server constitutes an active attack surface, including static forms, login routes, and health checks. There is no authentication to bypass, no session to steal, and no vulnerable endpoint to discover. The exposure is independent of the CMS, framework, or plug-ins in use.

The attack also leaves virtually no digital footprint. Worker processes remain stable, responses return HTTP 200, and application logs record no anomalies, because no application code ever ran. The only server-side trace is a single memory-limit fatal in the web server's error log, which is routinely dismissed as noise, leaving defenders without actionable web telemetry to identify compromised endpoints. 

In multi-process web servers such as Apache prefork, every worker is derived from a single parent process, so the memory layout an attacker depends on remains consistent across worker restarts. That stability allows an attacker to refine payloads at no cost; a failed attempt is absorbed by a worker respawn that monitoring treats as routine.

The two CVEs differ in scope and timing in ways that matter operationally. CVE-2026-109618 is self-contained: A single request corrupts its own data, and the consequence executes during that same request's cleanup, which makes it fast, atomic, and difficult to reconstruct after the fact. CVE-2026-109619 spans requests, requiring a poison step and a trigger step against the same worker, but it reaches a broader set of internal state and can be triggered through timeouts and client disconnects as well as memory exhaustion. Persistent-worker deployments are the material exposure for this second class.

Successful exploitation of either issue yields command execution in the web server user context, the standard foothold for deploying web shells, harvesting database and API credentials from configuration files, and pivoting into internal networks. Patching is the only complete remediation. Where immediate patching is not feasible, endpoint behavioral monitoring is the primary line of defense, because the exploitation is invisible at the HTTP layer while its outcome is not.

Am I Impacted?

Both vulnerabilities are present in every PHP release from the 7.x and 8.x series. The specific HashTable ordering behind CVE-2026-109618 was introduced by a performance optimization in October 2017 and first shipped in PHP 7.3.0 (December 2018). The serialization and session paths behind CVE-2026-109619 date back further, to PHP 7.1 and PHP 5.4, respectively. In other words, the exploitable window has existed in released PHP for more than seven years. No supported branch is unaffected.

Version alone does not determine exposure. The out-of-memory trigger must be reachable while PHP is parsing request data, which requires that a request body be allowed to grow large enough to exhaust the memory limit before any script runs. In practice, this means the configuration satisfies roughly:

 memory_limit < 4 × post_max_size

PHP's shipped defaults (post_max_size = 8M, memory_limit = 128M) do not meet this condition, so a stock installation is not reachable through the URL-encoded path we demonstrated. However, many production deployments are tuned to accept large uploads, and common pairings such as post_max_size = 64M with memory_limit = 128M, or post_max_size = 100M with memory_limit = 256M, satisfy it. Note that post_max_size can be set per directory in .htaccess or a virtual host, so on shared hosting, a single site can place itself into the vulnerable configuration without any server-wide change. CrowdStrike reproduced the issue with memory_limit lowered to 16M and post_max_size raised to 64M purely to keep the test payload small; the same bug is reached with larger payloads against the larger limits above.

To see which version of PHP and configuration is running on your server, either run php --version. In order to see the values set on your server for options listed above, run the following command:
php -i | grep -E "^(memory_limit|post_max_size|upload_max_filesize|Loaded Configuration File)”.

How CrowdStrike Falcon Protects Customers

CrowdStrike employs a layered approach to detecting exploitation and post-exploitation activity, using machine learning and behavior-based detections, including indicators of attack (IOAs). The Falcon platform provides out-of-the-box visibility and detection coverage for post-exploitation activities of the PHP vulnerabilities. Existing IOAs detect and prevent common post-exploitation activity, including web shell deployment, suspicious child processes spawned by PHP or web server processes, and malicious command execution.

CrowdStrike continues to research exploitation techniques associated with this vulnerability.

Figure 1. Exploitation chain of PHP vulnerabilities Figure 1. Exploitation chain of PHP vulnerabilities

Finding Vulnerable PHP Versions with Falcon Exposure Management

CrowdStrike Falcon® Exposure Management provides comprehensive visibility across affected environments. Network Scan (Netscan) and External Attack Surface Management (EASM) capabilities deliver prompt banner-based detection for internet-facing assets. Furthermore, sensor-based visibility enables automated assessment of vendor-supplied downstream patches across supported Linux distributions, Windows, and macOS operating systems.

Figure 2. Identification of vulnerable PHP versions within Falcon Exposure Management Figure 2. Identification of vulnerable PHP versions within Falcon Exposure Management

Threat Identification with Next-Gen SIEM

Falcon Next-Gen SIEM: Emerging Threat Response Dashboard

CrowdStrike Falcon® Next-Gen SIEM detection coverage for this threat (new “PHP Remote Code Execution Vulnerabilities (CVE-2026-109618/109619)” section on the CrowdStrike Falcon - Emerging Threat Response dashboard) has been released to production as part of NG-SIEM Content Packages release.

The Emerging Threat Response dashboard is a focused response tool designed to give CrowdStrike customers immediate visibility into active campaigns or newly discovered vulnerabilities that may impact their environment. By leveraging rich telemetry from Falcon platform modules and third-party data ingested in Falcon Next-Gen SIEM, this dashboard delivers ready-to-use detection and hunting queries that enable security teams to rapidly determine threat exposure and identify potentially compromised assets.

  • See Next-Gen SIEM > Log management > Dashboards

Figure 3. Identification of Emerging Threat Response dashboard within Falcon Next-Gen SIEM Figure 3. Identification of Emerging Threat Response dashboard within Falcon Next-Gen SIEM

Falcon Next-Gen SIEM Correlation Rule Templates

We have added a new Falcon Next-Gen SIEM Correlation Rule Template (CRT) to detect and respond to the CVE-2026-109618 and CVE-2026-109619 CVEs. These rules provide coverage for exploitation attempts related to these vulnerabilities. 

Generic - Web - PHP Memory Exhaustion During Request Startup (CVE-2026-109618 & CVE-2026-109619)

This detection identifies PHP fatal memory-exhaustion errors that occur before a PHP script begins executing. Specifically, it targets a telltale pattern that surfaces in web-server error logs as “Allowed memory size of N bytes exhausted” paired with “in Unknown on line 0.” The "Unknown on line 0” marker distinguishes startup-phase failures from the much noisier population of in-script memory errors, giving defenders a higher-fidelity signal that something is driving the PHP interpreter to exhaust memory before any application code runs. Patterns like this may indicate attempted exploitation of the PHP engine. Customers may view the template here ([US-1]  [US-2]  [US-3]  [EU-1]  [US-GOV-1]  [US-GOV-2]).

Conclusion and Recommendations

CrowdStrike is actively monitoring this evolving situation and will provide updates as new information becomes available. In parallel, we continue to develop and deploy new behavioral logic in the Falcon platform to detect and prevent malicious activity related to disclosed PHP vulnerabilities CVE-2026-109618 and CVE-2026-109619.

We recommend that customers identify any systems running vulnerable PHP versions and apply the latest security updates according to official PHP or vendor guidance.


CrowdStrike Falcon Platform
Ready to protect your business?

Try CrowdStrike free today

Subscribe

Sign up now to receive the latest notifications and updates from CrowdStrike

See CrowdStrike Falcon in action