Back to articles

PHP2Shell: How Running Out of Memory Became Remote Code Execution

Two critical PHP vulnerabilities turned an out-of-memory error into unauthenticated code execution. What broke, how it was found, how PHP fixed it, and what to do now.

On this page

Patch first, read later

If you run PHP that serves web requests, update to PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35 and restart every worker. According to the PHP team, there is no complete configuration workaround.1 This article was written on 21 October 2026, the day the fixes were released.

On 21 October 2026, PHP shipped fixes for two critical vulnerabilities, CVE-2026-109618 and CVE-2026-109619, which the researchers named PHP2Shell. Both are rated 9.2 on CVSS v4. Under the right conditions, an unauthenticated HTTP request can execute code on the server or read memory belonging to other requests, before your application code runs at all.23

The bug is not in WordPress, Drupal or any other application or framework. It is in how the PHP engine behaves when it runs out of memory, and the idea behind it is more than twenty years old.

This article covers what broke, how it was found, how PHP fixed it, and what to do about it. It deliberately stays high level; the official advisories and the researchers' write-ups linked at the end have the technical detail.

The short version

  • The bugs are in the PHP engine, in what happens when a request runs out of memory or hits another fatal error at the wrong moment.
  • In those cases PHP could abandon work halfway through, and leftover state could survive into the next request handled by the same worker process.
  • The researchers turned that into memory disclosure and unauthenticated remote code execution. Their demonstrations covered widely used PHP applications and frameworks, including WordPress, Drupal, Joomla, Magento, MediaWiki, Nextcloud, Laravel and Symfony.45
  • The fix changes how PHP handles running out of memory, and resets leftover state between requests.

An old idea, rediscovered

The research write-up from Calif, one of the teams that found the bugs, starts in 2004. That year, Stefan Esser, then PHP's best-known security researcher, found the first remote vulnerability built on memory_limit. He considered it severe enough that he never published his exploit.4

The same pattern kept resurfacing:

  • 2020: PHP bug #78876 reported that long variables in multipart/form-data requests could cause out-of-memory errors and leave temporary files behind. It was treated as a minor bug.6
  • 2024: an audit of PHP by OSTIF and Quarkslab found a related out-of-memory issue. Exploitation wasn't proven, so it wasn't treated as a security vulnerability.4
  • 2026: three groups found exploitable paths independently, within weeks of each other.

How it was found

Calif started auditing PHP in late July as part of OpenAI's Patch the Planet initiative, with the PHP Foundation involved from the start. Their researcher, Ryan Austin, didn't ask a model to find all the bugs. He knew PHP's vulnerability history, so he asked it specifically for modern relatives of Esser's 2004 bug. Within days, on 1 August, that produced the first out-of-memory interruption leading to code execution. They then had the model generalise the pattern and search for similar code, which turned up more variants.4

Calif notes that no special tooling or unreleased models were used for the initial discovery. As they put it, "know your target" still matters.4

Wiz Research found the first of its bugs on 19 August using its Wiz Atlas platform, Gemini 4 Argon and GPT-5.6 Sol. It confirmed unauthenticated RCE against Drupal the next day and against WordPress the day after. Wiz also found a related variant in OPcache.5 OpenAI's Codex Security team is credited as a reporter too.7

Date (2026) Event
1 August Calif finds multiple RCE paths
11 August Calif reports the first bug to PHP
15 August PHP confirms Calif's proof of concept
19 August Wiz finds its first variant
24 August Wiz reports to PHP
20 October Details leak; PHP announces a release for the next day
21 October, 14:00 UTC Fixed releases and write-ups published

The dates come from Calif's and Wiz's own timelines.45

The leak

The planned disclosure date was Thursday, 22 October. On 20 October, a Hacker News post announced two critical PHP RCEs with that date and both CVE identifiers, with no technical detail. watchTowr confirmed the leak was real.8 That evening PHP announced a release for 21 October at 14:00 UTC, a day earlier than planned.1 Cloudflare says it had deployed WAF rules about nine hours before disclosure and had already seen attack traffic aimed at its customers before the release.9

What went wrong, briefly

PHP enforces memory_limit per request. Before the fix, hitting the limit stopped the current operation immediately, wherever it was. Most of the time that's harmless: the request fails with "Allowed memory size exhausted" and the worker moves on.

The problem is the rare moment when PHP is in the middle of updating its own internal data. Stopping there can leave that data half-updated, or leave a worker-wide pointer aimed at memory that has already been freed. Some of PHP's request handling, including multipart form parsing and session upload progress, runs before your application code and before PHP has fully reset itself for the new request. That made it possible to reach the leftover state without any help from the application.3

Request A leaves state in the worker and is abandoned; request B on the same worker later uses that stale state.
Leftover state crossing from one request to the next on the same worker

PHP tracks the two CVEs as two classes of the same problem:

  • CVE-2026-109618, "RCE via OOM corruption": running out of memory interrupts PHP while internal request data is only partially initialised.2
  • CVE-2026-109619, "RCE via stale global state": state owned by one request survives after its memory is freed, and a later request on the same worker uses it.3

How exposed you were depends on how PHP is run. PHP rates Apache with mod_php as highest risk, with a demonstrated attack before any application code runs. PHP-FPM, FrankenPHP, php-cgi and LiteSpeed are also listed as affected.1 For the full technical analysis, read PHP's two advisories and the write-ups from Calif and Wiz.

The fix

The patch is a single large commit, "Avoid state corruption via bailout", applied to all four supported branches.10 Most of its roughly 11,700 changed lines are new tests that deliberately run PHP out of memory inside hundreds of functions. The change in behaviour comes down to three things:

  • Running out of memory no longer stops PHP mid-operation. Hitting memory_limit is now recorded and handled at the next safe point, where PHP can unwind cleanly and report the usual fatal error. Code that can't safely continue can now detect the failed allocation and back out on its own.
  • There is a new hard limit. If a request keeps allocating past the soft limit and reaches the hard ceiling, PHP terminates the worker process immediately with exit status 137. A new INI setting, memory_hard_limit_percent, controls how far above memory_limit that ceiling sits.10
  • Leftover state is reset. Error, session, serializer and other request state is now cleared at the end of a request, or initialised before early request processing can touch it.3

One operational consequence: when the hard limit is hit, the process exits without running shutdown functions or destructors. If you rely on register_shutdown_function() to log or clean up after memory errors, that won't happen in this case.7

What to do

Update PHP everywhere it serves web requests. Updating your application, framework or Composer packages does nothing here, because the bug is in the runtime underneath. Check every place PHP actually runs:

  • The PHP-FPM version, not just the CLI. php -v and php-fpm -v can differ on the same machine.
  • Docker base images, including images pinned to a specific minor version. Rebuild and redeploy them.
  • Managed platforms and hosting providers. Check whether they have rolled out the fixed versions.
  • CI and build images. They are less exposed, but there is no reason to keep running a vulnerable PHP.
Shell
php -v
php-fpm -v

You want 8.5.12, 8.4.27, 8.3.36 or 8.2.35 or newer. PHP 8.1 and older no longer receive fixes from the PHP project; some Linux distributions and LTS vendors backport them, so check with your vendor.1

Restart everything that runs PHP. Installing a package doesn't replace code already loaded in memory. Restart PHP-FPM, Apache, and any long-running PHP application servers, such as FrankenPHP workers.

If you can't update today, PHP's advisories list temporary measures. None of them is a complete fix:23

  • memory_limit=-1 removes the predictable limit the confirmed attack relies on, at the cost of letting one request use all available memory. Pair it with an operating-system memory limit, and don't use RLIMIT_AS or RLIMIT_DATA for this.
  • session.upload_progress.enabled=0 disables a feature most applications don't use. PHP suggests keeping it off even after patching.3
  • On PHP 8.5, fatal_error_backtraces=0 removes one of the confirmed paths.
  • Keep post_max_size conservative, and block multipart/form-data at the edge for apps that don't accept uploads.

Restart workers after changing any of these, and remove memory_limit=-1 once you've updated.

Look for warning signs. Wiz and PHP mention unusual multipart requests followed by memory-limit errors or bursts of HTTP 500s, unexpectedly large or malformed PHPSESSID cookies in responses, and unexplained worker crashes or restarts. None of these proves an attack, and their absence doesn't prove you're safe.57

Why this one matters

Nothing about the underlying weakness was new. The same idea was described in 2004, reported as a minor bug in 2020, and found again in an audit in 2024. What changed in 2026 was the cost of turning it into a working exploit. Both Calif and Wiz describe AI models doing a large share of the work of finding variants and adapting exploits to different targets, and Wiz says a single researcher did in a week what would previously have taken a team months.45

That cuts both ways. The same tools helped the researchers and the PHP team find and fix the whole class of bug rather than one instance of it. But it also means "too complicated to exploit in practice" is a weaker argument than it used to be, for PHP and for everything else we run.

Credits

The vulnerabilities were found and reported by Ryan Austin of Calif; Sagi Tzadik, Shir Tamari, Nir Ohfeld and Tomer Schwartz of Wiz Research; and Amy Burnett of OpenAI Codex Security.7 The fix was written by Arnaud Le Blanc with Ilija Tovilo, Jakub Zelenka, Nora Dossche and Nicolas Grekas of the PHP project.10 Thanks to all of them for coordinated disclosure, and for detailed public write-ups that made this article possible.

Back to articles
Keep reading
How This Site Is Built