Skip to content
Fri 23 Oct 2026 · 19:31 UTC
XT.PT
SecurityPHP core
Feature

PHP2Shell: the out-of-memory flaw PHP spotted in 2024 and fixed in 2026

How PHP's out-of-memory handling and stale per-worker state became critical RCE bugs, why mod_php is worst, what the leaked disclosure looked like, and what to do now.

23 Oct 2026, 18:45 UTC 13 min · 2,417 words Prelo · Verified by Roger Morais
Hacker

On October 21, 2026 the PHP project shipped PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35, a release that fixes two vulnerabilities in the engine itself rather than in any application built on it. The security advisory on php.net is blunt about who is exposed: "all PHP installations serving web requests, including PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed." Both bugs are rated CVSS v4.0 9.2, both can lead to unauthenticated remote code execution, and the project says an update is the only complete fix.

The two CVEs have been nicknamed PHP2Shell. The name is new, but the defect underneath is not. The PHP Foundation says one of its own contractors saw the first signs of it in 2024, and that the fix then stalled for months. What changed in August 2026 was that three research teams turned a theoretical weakness into working exploits, and the two that have described their methods first-hand, Wiz and OpenAI, did it with AI models.

Two bugs, one habit

Both CVEs come from the same habit in PHP's C code: abandoning work halfway through when something goes badly wrong, and trusting that whatever is left behind is still safe to touch.

The first, CVE-2026-109618 ("RCE via OOM corruption"), is about running out of memory. The advisory explains the mechanism in one sentence: "PHP uses longjmp() for out-of-memory (OOM) errors to exit the current context and abort handling of the request." The jump skips any code that would have tidied up. If it happens while some request data is reachable but only partly built, PHP's shutdown routine later reads that half-built data as if it were valid. The advisory notes that the allocation that fails "does not need to be related to the invalid memory."

Getting a request to run out of memory on purpose turns out to be easy. The advisory lists three ways multipart parsing can exceed the limits PHP appears to enforce: a multipart body "without a usable declared content length" that slips past the initial post_max_size check, a part carrying "an unrestricted number of headers" that cost far more memory inside PHP than on the wire, and empty file fields that do not count toward max_file_uploads but still build the usual metadata arrays.

The second, CVE-2026-109619 ("RCE via stale global state"), is about what a worker process remembers between requests. PHP keeps some global state, such as error, session and extension state, that points into memory owned by the current request. That pointer has to be cleared before the request's memory is released. A fatal error, including an out-of-memory error, can abandon an operation before its cleanup runs. The request's memory is freed, the global still points at it, and the next request handled by the same worker can use the stale pointer before normal initialization overwrites it.

The advisory frames exploitation as a producer and a consumer: one request leaves request-owned state behind, and a later request on the same worker acts on it. It names two confirmed consumers. One is a warning raised by invalid or deprecated INI settings applied per request, before the request is initialized. The other is session upload progress, which the advisory says "is enabled by default, and no application call to session_start() is required."

Why mod_php is worst, and why FPM is not safe

The advisories rank the server APIs differently, and the difference comes down to what each one does when request startup fails.

With Apache mod_php, the OOM advisory says shutdown runs unconditionally, "even when request startup has failed." Automatic body parsing can exhaust memory during startup and leave invalid state for shutdown to process. That happens "entirely before any application-specific code begins execution." Remote code execution was "validated over HTTP against mod_php with default PHP input and memory limits and normal prefork configurations with multiple workers."

PHP-FPM behaves better at that particular moment: it "exits the child when request startup fails," which removes the poisoned worker from the pool. But it is not off the hook. For the OOM bug, the advisory says source analysis supports FPM "as a susceptible, though no complete FPM exploit has been demonstrated." For the stale-state bug, the session lifecycle path "was validated against mod_php and FPM," using an application operation after a successful startup, then a fatal error, then a later upload-progress request on the same worker. FPM is also exposed to the INI-diagnostic consumer when a frontend such as nginx sends per-request PHP_VALUE or PHP_ADMIN_VALUE parameters that trigger a warning.

The stale-state advisory also explains why hiding errors does not help: "Global state is accessed before PHP decides whether to report, display or log the warning. Changing error_reporting, using @, or disabling display_errors or log_errors does not protect against this vulnerability."

What the fix changes

The OOM patch removes longjmp() from out-of-memory handling. According to the advisory, it is "replaced with a delayed, graceful soft OOM triggered at the next VM safe point, and an immediate hard OOM using _exit() if memory consumption reaches a second, higher threshold." In other words, PHP no longer jumps out of the middle of an operation when it hits memory_limit. It finishes to a point where its state is consistent, then stops. If memory keeps climbing past a second ceiling, the worker exits outright instead of unwinding into code that cannot cope.

The stale-state patch resets the wider set of request-owned global state at request boundaries, which the advisory calls "the complete state reset patch." Wiz, one of the research teams, says the fix "eliminates all variants in the patched release," including variants beyond the four bugs it reported.

A problem first seen in 2024

The most candid account of how this happened comes from the PHP Foundation. In 2024 PHP had its first external security audit in years, funded by the Sovereign Tech Agency, organized through OSTIF and carried out by Quarkslab. While fixing one of Quarkslab's findings, "a PHP core developer contracted by the PHP Foundation found the first signs of the OOM problem behind CVE-2026-109618." An exploit was theorized, but "no concrete vectors were found."

The security team then developed several competing fixes, each with a cost: compatibility breaks, new denial-of-service risk, or changes to many third-party extensions. Operating on consensus, the team could not choose, and its decision process "caused work on a fix to stall and restart repeatedly over months." The Foundation does not soften it:

We won't pretend otherwise: the delay was a structural failure, caused by both the security team's decision process and its lack of resources.

The PHP Foundation, October 21, 2026

It says the team has since changed its process "so that decisions can be made by majority and compromises are encouraged."

Three teams, and the AI in the loop

The weakness stopped being theoretical in August. Under the Patch the Planet program, security firm Calif began auditing PHP on July 30, and on August 11, per the Foundation, "researcher Ryan A at Calif reported an unconditional remote code execution with the same root cause as the OOM problem we had found in 2024." OpenAI independently reported the same root cause and sent further attack vectors on September 18. Wiz Research reported on August 24. From August 11, the Foundation says, fixing it "became the top priority for all of the PHP Foundation's team working on security."

Wiz's timeline has its first discovery on August 19, unauthenticated RCE confirmed against Drupal on August 20 and against WordPress on August 21. It says it "used Wiz Atlas, Gemini 4 Argon and GPT-5.6 Sol throughout this research" and that Gemini 4 Argon "made an invaluable contribution to finding the vulnerability, exploit development, attack detection, and validation of the patch." That is the model Google released to vetted cyber defenders before its public API launch, as XT.PT reported on October 6. Wiz demonstrated pre-authentication RCE against the stock WordPress, Drupal and Joomla container images, and separately showed that an attacker could read PHP worker memory, which "may contain passwords, authentication tokens, API keys and certificates belonging to other requests or the server itself."

Wiz's explanation of why the bug sat unexploited for so long is worth reading closely, because it is a claim about AI rather than about PHP: "The underlying flaws had been present for decades, but demonstrating their exploitability required an exceptionally complex chain of steps, at the edge of what human researchers could practically achieve." The exploits had to chain several flaws and adapt to each target's memory layout. Wiz says frontier models let it "develop and adapt working exploits across multiple applications in hours." OpenAI, as quoted by the Associated Press, called the bugs "one of the most impactful security issues in recent memory" and said "we expect similar findings to increase sharply in the coming months."

One detail for anyone cross-referencing write-ups: Wiz's own table files its upload-progress bugs under CVE-2026-109618 and the fatal-error backtrace bug under CVE-2026-109619. PHP's advisories describe session upload progress and fatal error backtraces under CVE-2026-109619, and CVE-2026-109618 as the out-of-memory corruption. This story follows PHP's advisories.

The flood of AI-assisted research cuts both ways for a small team. The Foundation says the PHP security team "has published 15 CVEs since July 1st and worked through more than 100 reports that turned out to be invalid, duplicates, or not reproducible." It also used AI on its side: a scan with Anthropic's Claude Mythos through Alpha-Omega earlier in the year, and OpenAI's Daybreak Red for validating reports and exploits. "AI helps the team validate reports and group reports sharing the same root cause," it writes, "but a person makes every call and works on every patch."

A leak, a video and a broken embargo

The coordinated disclosure, the Foundation says, "worked well until 2 days ahead of the targeted release date." Around 15:00 UTC on Tuesday, October 20, the two CVE IDs began circulating online with a wrong release time and a wrong CVSS score of 10.0. No technical details leaked, but the project published its pre-announcement at 19:30 UTC and moved the release from October 22 at 14:00 UTC to October 21 at 14:00 UTC. It did not release immediately because distributors were still packaging the fix.

Two more things went wrong. Around 20:00 UTC, the Foundation says, Calif "mistakenly published a video" of its proof of concept, which revealed "a few additional, though limited, details" and kept circulating after it was deleted. Then, at 07:41 UTC on October 21, "more than six hours before the coordinated release, Chainguard unilaterally broke the embargo and published built images containing the fix," which let anyone study the patch in binary form while everyone else waited.

Is it being exploited?

The answer depends on whom you ask and when, so here is what each source said, with dates.

  • CISA, October 21: it "has not observed exploitation of these vulnerabilities as of the time of publication." It told organizations that cannot update, or that run PHP 8.1 or earlier, to "take PHP-based systems offline until they can be patched or updated."
  • Rapid7: multiple public proof-of-concept exploits exist, but each "demonstrates RCE only against a specific combination of operating system, PHP version, and web server, and none appears to be broadly reliable." Rapid7 "has not observed exploitation in the wild, though there are multiple reports of exploitation attempts."
  • Cloudflare CTO Dane Knecht, before the patches were released, said the company was already seeing early attacks against the two bugs, per BleepingComputer. Cloudflare had deployed WAF rules for the known exploit methods and called them temporary.

The caution went beyond advisories. The Associated Press reported that governments and other organizations in Europe and the US took websites offline temporarily on October 21, that the Finnish government's site was only partly available, and that the FBI's cyber division recommended taking sites offline if they could not be updated immediately. Rapid7's own framing is the useful one: the current difficulty of reliable exploitation "should be treated as a window to patch rather than a reason to delay."

What to do

Update, then restart every PHP worker. The fixed upstream versions are 8.5.12, 8.4.27, 8.3.36 and 8.2.35. Distributions and LTS vendors received the patches in advance, and a vendor package that backports the fix can carry its own version number, so check your vendor's errata rather than the version alone. A patched binary on disk does nothing for workers that started before the update.

php -v                      # CLI version
php-fpm -v                  # FPM version, if you run FPM
systemctl restart php-fpm   # or reload Apache for mod_php

Older branches. PHP 8.1 and older get no upstream releases, but "given the severity," the project published minimal source backports for 7.4, 8.0 and 8.1, linked from the advisory. CISA's advice for anything that cannot be updated is to take it offline.

If you truly cannot update yet, the advisories list partial measures, each with stated limits:

  • For the OOM bug, set memory_limit=-1 administratively (for example php_admin_value in the FPM pool), keep post_max_size low (8M without uploads, 32M with them), and bound request size, input counts, multipart parts and headers at the web server. Do not use RLIMIT_AS or RLIMIT_DATA, which the advisory says "will cause mmap() to fail once the memory limit is reached, triggering the vulnerable longjmp() path."
  • For the stale-state bug, set session.upload_progress.enabled=0 in php.ini, the pool or server configuration and restart every worker, because ini_set() and FPM's .user.ini act too late. On PHP 8.5, also set fatal_error_backtraces=0. Remove deprecated or invalid per-request settings, including any PHP_VALUE your nginx config passes, and fix attacker-reachable fatal errors and timeouts. The advisory also mentions pm.max_requests=1 "if performance is not a factor."

The advisory suggests keeping session upload progress off even after patching if you do not use it, "as more attackers try to find flaws in this hardly-used and hardly-tested feature." Everything else on that list is a stopgap. Both advisories carry the same warning in the same words: updating is "the only complete mitigation."

Primary sources: PHP security advisory, php.net, GHSA-w8hw-w9fx-j79w (CVE-2026-109618), GHSA-6wmf-4rv5-p3rp (CVE-2026-109619), oss-security announcement, The PHP Foundation, Wiz Research, CISA, Rapid7, BleepingComputer, Associated Press via ADN, read 2026-10-23.

Read next · Hardware / Smart home Apple HomeView walls off its camera in A18 Secure Exclaves, and won't be sold in the EU →