October 25, 2026
PHP2Shell: CVE-2026–109618 & CVE-2026–109619 Explained (and How to Patch)
Critical PHP remote code execution vulnerabilities: how they work, which versions are affected, why config workarounds aren’t enough, and…

By Shubham Raj Kumawat
5 min read
Critical PHP remote code execution vulnerabilities: how they work, which versions are affected, why config workarounds aren't enough, and what the bugs teach us about half-built state
On October 21, 2026, the PHP project shipped an emergency release for two critical remote code execution bugs, CVE-2026–109618 and CVE-2026–109619, nicknamed PHP2Shell. Both score 9.2 on CVSS v4.0. Details leaked a day before the planned disclosure, which pulled the release forward, and AWS reported exploitation attempts as early as October 17, days before any patch existed.
The headline is "patch now," and you should. But the more durable story is in the bugs themselves. Both follow a pattern that shows up in far more codebases than PHP, and it is worth understanding even if you never write C.
What was affected
According to Google Cloud's advisory summary, every PHP version from 5.4 through 8.5.11 is vulnerable. Fixes landed in 8.2.35, 8.3.36, 8.4.27 and 8.5.12, and the PHP security team also published minimal source backports for 7.4, 8.0 and 8.1. Every way of serving PHP over HTTP is in scope: Apache's module, PHP-FPM, php-cgi, FrankenPHP and LiteSpeed. Palo Alto's Unit 42 counted more than 22 million potentially exposed instances.
Two details make this worse than a typical application-layer bug:
- The flaws live in the interpreter, below WordPress, Laravel or any framework. Updating your application or plugins changes nothing.
- PHP parses the request before your code runs. Application authentication, input validation and routing never get a chance to reject a malicious request, because the damage happens while PHP is still setting up or tearing down the request.
The shared pattern: abandon an operation, then trust the leftovers
Datadog's researchers, who reproduced the bugs independently, summarize both flaws the same way: a fatal error interrupts PHP partway through an operation, and later code treats the incomplete state as if it were valid.
CVE-2026–109618 is the memory-exhaustion variant. When PHP turns a form submission into the $_POST array, it inserts entries into an internal hash table. In the affected code, the table's bookkeeping counters were updated before the memory for the new key was allocated. The shape of the problem looks like this (simplified pseudocode, not PHP's actual source):
slot = table->used++; // 1. record that the slot exists
table->count++;
table->data[slot].key = alloc(len); // 2. allocate; may failslot = table->used++; // 1. record that the slot exists
table->count++;
table->data[slot].key = alloc(len); // 2. allocate; may failIf step 2 hits the memory limit, PHP raises a fatal error and abandons the insertion. The counters now claim a slot exists that was never initialized. When the request is cleaned up, the destructor walks every counted slot and interprets whatever bytes happen to be there as real values. If an attacker can influence those leftover bytes, they influence what cleanup does next.
CVE-2026–109619 is the lifecycle variant. A PHP worker serves many requests over its lifetime, and some worker-wide fields point at memory owned by a single request. If that memory is freed without clearing the pointer, a later request can follow a stale reference into freed memory, a classic use-after-free. PHP's advisory describes two ways stale state survives: a fatal error interrupts a reset, or a late shutdown diagnostic recreates state after cleanup. The upload path is the notable one, because session upload progress is enabled by default and runs even if your application never starts a session.
Why "just turn off the feature" is not a fix
When details leaked, a one-line workaround circulated: disable session.upload_progress.enabled. It is worth being precise about what that does. It blocks the confirmed session-based path for CVE-2026-109619, but it does nothing for CVE-2026-109618, and PHP's own guidance says no configuration change fully fixes either flaw. The mitigation for the memory bug, setting memory_limit=-1, comes with its own caveats. It must be paired with request-size limits at the frontend and container memory limits, and process-level limits such as RLIMIT_AS should not be substituted, since they can trigger the very allocation failures the bug depends on.
Two days after the emergency release, PHP also published an informational advisory for a related issue, CVE-2026–115560, affecting unusual session configurations. PHP considers it unlikely in practice and plans a fix in a routine release, recommending the same upload-progress setting in the meantime. The lesson is not that configuration hardening is useless. It reduces exposure while you roll out an update, but it should never be the end state.
What the patch changes
The fix is instructive because it changes error handling rather than only the one insertion routine. A soft memory limit now defers the error until PHP reaches a safe point, letting an in-progress operation finish initializing its data. Past a higher hard limit, PHP terminates the worker immediately instead of running normal cleanup over potentially invalid state. Request-owned global state is also reset before anything can reuse it.
Datadog's analysis points out that the counter-before-allocation ordering remains in the fixed code, and that the broader pattern is risky enough that similar bugs may surface. That is a fair reading: the patch closes the known exploit class by controlling how failure unwinds, not by removing every place where metadata is updated early.
Engineering lessons worth taking away
- Commit metadata after the resource it describes. Allocate first, then update counters. If anything fails in between, the structure stays consistent.
- Cleanup code must not assume initialization succeeded. Destructors that trust counts or flags written earlier are only as safe as the last error path that touched them.
- For long-lived workers, reset is a feature, not a courtesy. Anything pointing at per-request memory needs an explicit, tested clear on every exit path, including fatal ones.
- When state is suspect, abort rather than unwind. Terminating a worker is a cheaper failure than running cleanup logic over corrupt data.
- Theoretical findings deserve follow-through. The PHP Foundation said a core developer noticed early signs of the memory flaw during a 2024 external audit, but no concrete route to code execution was found then. Bugs rated "unexploitable" tend to be rated that way until someone builds the exploit.
A practical response checklist
- Inventory first. Include containers and apps that bundle their own interpreter. The CLI PHP on a host often differs from the runtime serving web traffic.
- Update, then verify. Linux distributions frequently backport fixes without changing the upstream version number, so check your vendor's advisory rather than the version string alone.
- Rebuild images and restart workers. A patched package does nothing for processes still running the old runtime.
- If you cannot patch yet, restrict public exposure and filter at a reverse proxy in front of PHP, and treat that as temporary.
- Hunt for signals. Fatal memory-exhaustion errors reported at "Unknown on line 0" mean PHP failed during request initialization, before any script ran. Unusually large or deeply nested multipart submissions, odd
PHPSESSIDvalues, and any shell spawned by a web worker deserve investigation. None is conclusive alone, and a successful attack does not necessarily crash the worker, so correlate them.
Closing thoughts
PHP2Shell is a reminder that the most dangerous bugs often sit in the least glamorous code: error paths, cleanup routines and lifecycle resets that run only when something has already gone wrong. If you operate PHP, patch today. If you build systems in any language, look at your own failure paths and ask a simple question: if this operation stops halfway, what will the next piece of code believe about the state it left behind?
Sources
- PHP security advisory for CVE-2026–109618 (GHSA-w8hw-w9fx-j79w) and CVE-2026–109619 (GHSA-6wmf-4rv5-p3rp)
- Datadog Security Labs: PHP2Shell analysis
- Google Cloud: Responding to core PHP vulnerabilities
- Palo Alto Networks Unit 42 threat brief
- The Hacker News: emergency patches and follow-up on CVE-2026–115560
- watchTowr PHP2Shell FAQ