PHP2Shell series, 4 parts
Read in order, or jump to the part you need.
  1. What PHP2Shell is
  2. How the two bugs work
  3. How it was found, and the leak
  4. How to patch and protect your servers

Bonus: watch it in 3D

This is part 2 of the PHP2Shell series, for readers who want the mechanism. If you want the simpler version first, read part 1. Everything below is drawn from the two PHP advisories, the PHP security team member who wrote about the fix, and the security vendors' write-ups, all listed at the end. Where something is one source's analysis rather than the advisory, it says so.

Background: three ideas you need

1. A request has a lifecycle

Every web request to PHP goes through startup (read the input, build $_GET, $_POST and friends, apply per-request settings), then your script runs, then shutdown (clean up). The key fact for these bugs: both bugs fire before your application code runs, so application-level logins and checks cannot block them. Datadog makes the same point.

2. PHP limits memory per request

The memory_limit setting (128M by default) caps how much memory one request may use. When a request goes over, the old behavior was to bail out immediately.

3. "Bailing out" used longjmp()

In C, longjmp() is a jump that leaves the current function and everything above it, straight back to a saved point. It skips the remaining lines of whatever was running. That is fine if nothing is in a half-finished state. It is dangerous if something was.

CVE-2026-109618: "RCE via OOM corruption"

The advisory states the root cause: PHP uses longjmp() to exit a request on out-of-memory. In some contexts, request memory is reachable before it is fully initialized. An early exit there leaves that memory invalid, and request shutdown may read it or branch on it. Importantly, the failed allocation does not need to relate to the corrupted memory. Any allocation that fails at the wrong moment can do it.

1. PHP builds a table from the requestIt bumps the table's counter first,then allocates the new entry2. The allocation fails (memory_limit reached)PHP uses longjmp() to jump out at once.The counter says 'one more entry'. The entry was never built.3. Request shutdown cleans up the tableIt trusts the counter and walks the unbuilt entry.Uninitialized memory is treated as real data,which can lead to code execution
The hash table detail is Datadog's description. The advisory describes the general pattern: memory is reachable before it is fully initialized.

Datadog gives a concrete example: if allocation fails while PHP builds the $_POST array, PHP has already updated the table's counters for an entry it never finished creating. Cleanup then processes that unfinished entry as valid. Datadog also notes the hash table is one instance of a broader pattern, so defenses should not hinge on that one code path.

How an attacker reaches the out-of-memory moment

The attacker needs to make PHP run out of memory during startup, even though a size cap (post_max_size, 8 MB by default) is supposed to prevent huge requests. The advisory lists these routes:

RouteWhy it works
Multipart body with no usable declared lengthIt can pass the first post_max_size check.
Parts with unlimited headersHeaders use far more memory inside PHP than they take on the wire.
Empty file fieldsThey do not count toward max_file_uploads but still create file metadata arrays. Nested field names multiply the work.
App-specific inputsAnything that causes a large allocation can reach the same condition.

Why Apache mod_php is the confirmed case

The advisory compares the ways PHP is run (called SAPIs):

How PHP runsStatus in the advisory
Apache mod_phpRuns shutdown even when startup fails. Body parsing can hit the memory limit, and shutdown then processes invalid state before any application code runs. Confirmed exploitable over HTTP with default input and memory limits and a normal prefork setup.
PHP-FPMExits the child process when startup fails, which limits the startup case. An out-of-memory after startup may still hit the flaw. Source analysis suggests it is susceptible, but no complete exploit was demonstrated for this bug.
FrankenPHPLikely susceptible per source analysis, similar to mod_php. Not validated.
LSAPIModeled on FPM. Application-specific exploits are likely but unvalidated.

CVE-2026-109619: "RCE via stale global state"

PHP keeps some state at the worker level (error, session, object and extension state) that is supposed to be reset for each request. This bug is about state that outlives the request that created it.

One PHP worker, two requests in a rowRequest A (the producer)Hits a fatal error or runs out of memory.It leaves request state behind withpointers into memory that is freedThe worker takes the next requestRequest B (the consumer)Some code runs before the request is reset,such as a warning while applying settings,and it reads the leftover pointersThat is a use-after-free
Producer and consumer must run in different requests on the same worker. A bigger pool lowers the success rate but does not remove the condition.

The advisory gives the model: a producer leaves request-owned state published, and a later consumer uses it before initialization resets it. The two must happen in different requests handled by the same worker. Ways state gets left behind include fatal errors of any kind (parse or compile errors, uncaught errors, a failed require, execution timeouts and out-of-memory), and diagnostics raised during cleanup.

The cases the advisory confirms

On mod_php, per-directory settings (.htaccess, php_value) are applied before request startup, so they can act as early consumers. On PHP-FPM, exposure is conditional, for example when a frontend passes PHP_VALUE parameters that trigger diagnostics.

How hard is it to exploit?

The scoring vector for both bugs includes AC:H (high attack complexity) and AT:P (attack requirements present). Reasons gathered from the sources:

But "hard" is not "safe". Wiz demonstrated pre-authentication code execution against WordPress, Drupal and Joomla deployments, as reported by The Hacker News, and Calif has chained both bugs into a login-free attack per Sansec. Attackers do not need to be universal. They need to be reliable on the most common builds. Treat the complexity rating as a delay, not a defense.

How the fix works

Before the fixnormal work, up to memory_limitlimit hit: instant jump outAfter the fix (8.2.35, 8.3.36, 8.4.27, 8.5.12)normal worksoft limit zonehard stop100% of memory_limit200% (below 1G)Soft: an error is recorded and PHP unwinds cleanly. Hard: the process exits.
After the patch, going over the limit records a pending error and PHP stops at the next safe point. A second, higher limit ends the process outright.

The advisory says the fix removes longjmp() from out-of-memory handling and adds two thresholds. Alexandre Daubois, PHP security team explains the details:

Two caveats. A patched PHP still kills a worker that runs out of memory, but without corruption. And Datadog notes the fix does not change the underlying "counter updated before the allocation" pattern, so similar bugs may appear later, including in older branches.

One more observation from the same author: other runtimes handle this differently. Go's standard runtime treats out-of-memory as unrecoverable, and Rust binaries abort by default. C++ code remains exposed to the same shape of bug, because any new can throw into code that did not expect it.

What this means in practice

If you remember one thing: there is no configuration that fully replaces the update. The PHP advisory for 109618 calls the update the only complete fix, and the advisory for 109619 states there is no complete generic configuration mitigation. Part 4 lists the partial measures, separated by which CVE they cover, because several widely shared tips cover only one of the two.

Need help with PHP2Shell?
This is the kind of fix we help with. GreyScript AI helps teams find where PHP runs behind their websites and AI products, patch it in the right order, and secure what sits around it: where model keys and other secrets live, how the AI integration is built, and which credentials to rotate. We start with an inventory, then give you a short, ordered patch-and-rotate plan, backed by our AI governance and security audit. Book a 30-minute scoping call and tell us what you run.

Sources: PHP advisory for CVE-2026-109618, PHP advisory for CVE-2026-109619, Alexandre Daubois, PHP security team, Datadog Security Labs, Daniele Berardinelli's analysis, Sansec, The Hacker News. Read 23 October 2026