22 October 2026 CVE Nicolas D

PHP2Shell: 2 Critical RCE Flaws in PHP (CVSS 9.2)

CVE-2026-109618 / CVE-2026-109619 / Critical · 9.2 / Patched October 2026

The score is 9.2, not the perfect 10.0 some people claimed before the fix. But the impact is real: Wiz used these bugs to run code on default WordPress, Drupal and Joomla, with no login. And we reproduced a working PHP2Shell code-execution chain in our own lab.

PHP2Shell code execution chain reproduced in our lab (PHP 8.4.26, Apache mod_php).

TL;DR

PHP fixed two critical bugs, nicknamed "PHP2Shell". Both involve memory left in a bad state when PHP stops an operation early. An attacker can crash a worker or, in some setups, run code without logging in. Wiz demonstrated this on default WordPress, Drupal and Joomla, and we reproduced a working code-execution chain in our lab. Patch now.

KEY TAKEAWAYS

  • Two CVEs cover several related bugs. Fixed in PHP 8.2.35, 8.3.36, 8.4.27 and 8.5.12.

  • 109618 can leave half-built request data after an out-of-memory error. 109619 leaves stale global state for later requests.

  • Wiz demonstrated unauthenticated code execution on default WordPress, Drupal and Joomla. We reproduced a worker crash (CVE-2026-109619) and, separately, chained CVE-2026-109618's memory leak and use-after-free into working code execution in our lab.

  • Code execution is harder than a crash and depends on the target. Updating PHP is the only full fix.

1. From speculation to confirmed risk

Before the technical details were public, plenty of videos and supposed PoCs were circulating. Many did not actually show a working exploit, and the claims of a perfect 10 score were still speculation. The disclosed CVEs are serious enough on their own: an unauthenticated, pre-auth code-execution path in PHP core, rated CVSS 9.2. Exploiting many different servers is harder, as attacks may need adapting to each target. But widespread exploitation is not impossible.

2. Two root causes, several bugs

There are two CVE numbers, but more than two bugs. PHP grouped several related problems under each number.

Both groups involve PHP leaving memory in a bad state when an operation is interrupted, especially by an out-of-memory error. The fix is named after it: "Avoid state corruption via bailout."

The difference is what goes wrong:

CVE-2026-109618, OOM corruption. PHP stops while some request data is half-built. Cleanup can then use that invalid data, sometimes in the same request.

CVE-2026-109619, stale global state. PHP keeps a pointer to request memory after freeing it. A later request on the same worker can follow it.

The second group is a use-after-free. The first can also lead to memory corruption without that exact sequence. Wiz says other variants probably exist under these two numbers.

CVE-2026-109618: OOM corruption

Each PHP request has a memory budget (memory_limit, 128 MB by default). If a request goes over it, PHP stops it at once and jumps to cleanup. The problem is that this can happen while PHP is still reading the request body. The data is left half-built, and the same request's cleanup then uses it. Several failures fit this CVE.

The half-built upload data

If PHP runs out of memory while building the list of uploaded files, that list is left half-built. Cleanup then uses parts that were never set. On mod_php, this can happen before your code runs. PHP-FPM handles failed startup differently, so the same pre-application path is not confirmed there. We did not reproduce a standalone crash on this path in our lab; our confirmed hands-on result is the full code-execution chain in section 5, which relies on the related upload-progress use-after-free.

A memory leak

The second bug helps make real attacks practical. An out-of-memory stop can leave session state pointing at freed memory. A later multipart request can return a chunk of worker memory in a PHPSESSID response cookie, even if the app never starts a session. This gives the attacker memory addresses needed for exploitation. Wiz chained this leak with an upload-progress unserializer use-after-free to run code on WordPress, Drupal and Joomla.

CVE-2026-109619: Leftovers for the next request

PHP keeps some data inside each worker between requests. A fatal error can leave this data pointing at freed memory and skip the reset, so the next request uses it. Wiz demonstrated two cases here; PHP's advisory also describes a session lifecycle case.

The saved error trace

When PHP saves a trace after a fatal error (on by default in 8.5), it can leave a pointer to freed memory. An early diagnostic on the next request can touch it. We reproduced this crash in our lab as well.

Upload progress

PHP's upload-progress feature (on by default) can also read stale session state left by an earlier error. PHP confirms this path under 109619. It needs no session_start()call, but the validated PHP-FPM attack also needs a suitable application operation.

OPcache mix-up

A failed memory allocation can leave PHP's shared code cache out of sync, so another worker mixes up one internal type for another. Because this cache is shared, the problem can reach more than one worker.

05. How exploitable is it?

We reproduced full code execution

By chaining PHP2Shell's memory leak with its upload-progress use-after-free, we achieved remote code execution in our own lab, not just a crash. This is direct, hands-on proof that the risk is real and not only theoretical. We are not releasing our exploit. Working attack code is already public, and one more ready-to-run tool helps attackers more than defenders. We spent our time the other way: making the exploit run reliably in our lab so we could understand it fully, then using what we learned to detect this vulnerability faster.

The hard part is not the vulnerability. It is the target. The two things that usually stop this class of bug are already handled. Address randomisation (ASLR) is defeated by the memory leak, which hands the attacker live addresses inside a PHPSESSIDresponse cookie. And once the flaw is reached, the primitive is reliable.

What is left is per-target precision. A working exploit is pinned to one build: the exact symbol offsets (for example zifsystem and zvalptr_dtor) and the heap-grooming "magic values" that place attacker data where the freed pointer will read it. Change the PHP version, the distribution, the compiler, the extensions or the CPU architecture and those numbers move, so the proof-of-concept has to be re-tuned. Some configurations add friction too, mainly around reliably forcing the out-of-memory condition, but none of it is a wall.

So the realistic threat is targeted attacks, not one universal exploit. And the per-target data an attacker needs is often handed to them: a PHP version in a verbose error page, an X-Powered-By: PHP/8.x.y or Server header, a recognisable build. Leak the version and matching the offsets becomes mechanical: exactly the kind of repetitive work automation makes cheap.

FindingDetail
Demonstrated Unauthenticated code execution. We chained the memory leak and the use-after-free of CVE-2026-109618 to run commands pre-authentication in our lab, with no credentials and no gadget chain.
Demonstrated Worker crashes. A reproducible denial of service; repeated crashes can take a service offline.
Harder One exploit per server. Build-specific offsets and heap grooming values differ from target to target, so each one needs tuning: a speed bump that disappears as soon as the version is disclosed.

06. What to do now

Update PHP. The fixed releases are 8.2.35, 8.3.36, 8.4.27 and 8.5.12 (or newer). Vendor backports may also carry the fix. Restart PHP workers after updating. This is the only full fix.

If you cannot update today, these lower your risk. None of them replaces the update:

  • Set and enforce memory_limit=-1 in server configuration, then restart every worker and confirm that no pool, directory or app setting puts a finite limit back. This removes one trigger, not the bug.

  • Keep postmaxsize conservative and enforce the real request size at your proxy. Reduce maxinputnesting_level and cap input variables, parts, per-part headers and uploads. If your app does not use uploads, block multipart requests (including ones with no Content-Length) at the proxy.

  • Set session.uploadprogress.enabled=0 (worth keeping off even after patching if you do not use it). On PHP 8.5 also set fatalerrorbacktraces=0. On PHP-FPM, pm.maxrequests=1 helps if performance allows. Restart all workers.

  • Avoid configuration that prints a warning before a request starts: prefer settings applied at startup over per-request overrides, and remove obsolete directives.

  • Check your app for places where attacker input can cause a fatal error or a timeout (uploads, imports, serialization, error and shutdown handlers).

  • If you use a CDN or WAF, check its advisory. Several vendors (Cloudflare, Fastly, Akamai, AWS and others) published interim mitigations.

What to watch for

  • Odd multipart requests followed by memory-limit errors.

  • Many HTTP 500 errors.

  • Very large or malformed PHPSESSID response cookies.

  • PHP workers crashing and restarting for no clear reason.

These bugs act below your application, so your app logs may show little. Rely on your web server and PHP error logs.

Expect more of these

These are not two clean bugs with one clean patch. The fix touches several parts of PHP, including out-of-memory handling and global state between requests. Wiz expects other variants. That does not mean the patched releases are still vulnerable. For now, updating is the fix.

For defenders, the lesson is to know which internet-facing systems run an affected PHP version, and to keep knowing, because this class of bug invites targeted attacks and new variants keep surfacing. A version printed in an error page or a response header is all an attacker needs to start matching offsets to your estate.

This is the gap Patrowl is built to close. Continuous external attack-surface monitoring discovers every internet-facing asset, fingerprints the PHP versions they expose (including the verbose errors and headers that hand an attacker a head start), and flags the ones still on a vulnerable release, re-checking as each new advisory lands. Our AI agents probe that surface daily and a human pentester validates every finding, so an affected, exposed server is something you find and fix first, not something an attacker finds for you.

PHP2Shell: frequently asked questions

What is PHP2Shell?

PHP2Shell is the nickname for two critical vulnerabilities in the PHP core, CVE-2026-109618 and CVE-2026-109619, patched in October 2026. They leave memory in an inconsistent state when PHP aborts an operation, which lets an attacker crash a worker or execute code remotely without authentication.

Which PHP versions fix PHP2Shell?

The fixed versions are PHP 8.2.35, 8.3.36, 8.4.27 and 8.5.12, or later. Distribution backports may also include the fix. After updating, restart all PHP workers so the fix takes effect.

Does PHP2Shell allow unauthenticated code execution?

Yes. Wiz Research demonstrated unauthenticated code execution on default WordPress, Drupal and Joomla installations, and Patrowl reproduced a full code execution chain in its lab on PHP 8.4.26 with Apache mod_php.

What is the CVSS score of PHP2Shell?

The CVSS score is 9.2, rated critical. The 10.0 score announced before the patch was released was speculation, but the impact is real: pre-authentication code execution in the PHP core.

What is the difference between CVE-2026-109618 and CVE-2026-109619?

CVE-2026-109618 covers memory corruption triggered when memory_limit is exceeded while a request is being read. CVE-2026-109619 covers stale global state: a pointer to freed memory that the next request on the same worker reuses (use-after-free).

Are WordPress, Drupal and Joomla vulnerable to PHP2Shell?

Yes, if they run on an unpatched PHP version. The flaw is in PHP itself, not in the CMS: updating WordPress, Drupal or Joomla is not enough, PHP is what needs to be updated.

Is there a public PHP2Shell exploit?

Yes, working attack code is already public. Each exploit still has to be tuned to the targeted PHP build (symbol offsets, heap grooming values), which favors targeted attacks over a single universal exploit.

How can I mitigate PHP2Shell without updating PHP?

Disable session.upload_progress, set fatal_error_backtraces=0 on PHP 8.5, limit request size at the proxy and block multipart requests if your application does not accept uploads. These measures reduce the risk, but none of them replaces updating PHP.

How can I detect a PHP2Shell exploitation attempt?

Watch for unusual multipart requests followed by memory limit errors, spikes in HTTP 500 errors, abnormally large or malformed PHPSESSID cookies, and PHP workers restarting for no apparent reason. Check your web server and PHP logs; application logs rarely show the attack.

How do I know if my internet-facing servers are vulnerable?

You need to identify the PHP version of every exposed asset, including versions revealed by X-Powered-By headers or verbose error pages. Patrowl automates this inventory continuously and flags servers still running a vulnerable version, with every finding validated by a pentester.

SOURCES & REFERENCES

  • PHP security advisories: GHSA-w8hw-w9fx-j79w (CVE-2026-109618) and GHSA-6wmf-4rv5-p3rp (CVE-2026-109619).

  • Both fixed in commit 31c0ea7d7ff, "Avoid state corruption via bailout".

  • Demonstrated code execution and bug breakdown: Wiz Research, "PHP2Shell".

  • Hands-on testing: our own lab. Crash reproductions on PHP 8.4.x and 8.5.11 under AddressSanitizer, and a public CVE-2026-109618 code-execution proof-of-concept reproduced on a stock PHP 8.4.26 (Apache mod_php) target.

This article describes the vulnerabilities and their impact. It contains no working exploit or attack code. Our testing was done on isolated systems.