Developer Tools

PHP2Shell: patch the PHP engine tonight, not your app

Two critical PHP engine bugs give attackers code execution before your app even runs. Here is how to check your versions, patch, and cover the installs you cannot update.

A dark server rack in a dim data centre, its face cracked by glowing orange lines in the outline of an elephant
A dark server rack in a dim data centre, its face cracked by glowing orange lines in the outline of an elephant

What shipped today, and why it is different

PHP released 8.5.12, 8.4.27, 8.3.36 and 8.2.35 on 21 October. The php.net security advisory says the releases "fix two critical vulnerabilities that can allow unauthenticated remote code execution." The pair has been nicknamed PHP2Shell, and the pre-announcement on 20 October rated both CVEs 9.2 on CVSS v4.0.

What sets this one apart from the usual run of PHP security news is where the bugs live. They are not in WordPress, a Composer package or a framework. They are in the engine that runs all of those. Patching your plugins, upgrading Laravel or auditing your own code does nothing here. The only fix is a new PHP binary, or, if you cannot get one, taking the box off the network.

The two bugs in plain terms

The advisory describes two separate flaws:

  • CVE-2026-109618, "RCE via OOM corruption." If PHP runs out of memory while it is still setting up request data, the out-of-memory handling interrupts it partway through. The half-built state that remains can be turned into code execution.
  • CVE-2026-109619, "RCE via stale global state." Request state outlives the memory behind it. Once that memory has been freed, a later request handled by the same worker can still reach it.

Per php.net, the exposed population is "all PHP installations serving web requests, including PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed." The project rates mod_php the highest risk because a demonstrated exploit fires "before any application code runs." No routing, no authentication check and no WAF rule inside your app gets a chance to look at the request.

Sansec's write-up adds two details that matter for triage. The vulnerable code goes back to PHP 5.4.0, released in 2012, so every PHP version you are likely to find in production is affected. And "every known trigger is a multipart/form-data request", which is the ordinary encoding for HTML form posts and file uploads.

Sansec also lists applications against which Calif has confirmed unauthenticated RCE. The list includes Magento 2.4.9, WordPress 7.1.2, Drupal 11.4.3, Joomla 6.1.4, MediaWiki 1.46.2, Nextcloud 35.0.1, Laravel 13.11 and Symfony 8.1, among others. Its conclusion for Magento shops applies to everyone: "You cannot fix PHP2Shell from within your store code."

Is it being exploited? The sources disagree

At publication, CISA's alert said it had "not observed exploitation." It still told organisations to inventory every PHP system and patch now, and said systems that cannot be updated, or that run PHP 8.1 or earlier, should go offline until they are patched.

Cloudflare tells a different story. Its post says details leaked early and that it saw attack traffic aimed at its customers. It also "observed how attackers were mutating the attack payload to try to bypass our protections." Cloudflare began rolling out WAF rules to free and paid plans at 03:02 UTC on 21 October, about nine hours before disclosure. On Sansec's timeline, Cloudflare saw the first attacks at 07:43 UTC, and the fixed releases arrived at 14:00 UTC.

Both accounts can be true at once. A government agency saying it has not yet observed exploitation is a narrower claim than "nobody is exploiting this." For planning purposes, assume attack traffic reached the internet before the patches did, because a major edge network says it did.

Nobody agrees on how big the exposure is, either. Tenable cites W3Techs, which puts PHP on 69.3% of sites whose server-side language is known, plus outside estimates of 1.3 million to 1.9 million exposed hosts. BleepingComputer puts PHP's share at roughly 75–80% of websites and cites a VirusTotal estimate of more than 5.1 million exposed PHP hosts. Whichever number you pick, PHP runs somewhere between about 70% and 80% of the web, and that is the population with an engine-level RCE right now.

Patch tonight: the practical order of operations

1. Find the version that actually serves requests. The CLI is not always the same build as the one FPM or Apache loads. Distribution packages, multiple installed versions and containers can all leave php -v reporting one version while the web pool runs another. Check both. FPM binaries are often versioned (for example php-fpm8.3), and on Apache with mod_php you want to see which module is loaded:

php -v
php-fpm8.3 -v 2>/dev/null || php-fpm -v
apachectl -M 2>/dev/null | grep -i php

Container images, managed hosting panels and anything running FrankenPHP or LiteSpeed need the same check. Read the version from the runtime, not from a Dockerfile you assume is current.

2. Upgrade to a fixed release on your branch. That means 8.5.12, 8.4.27, 8.3.36 or 8.2.35. Restart the FPM pool or Apache afterwards. CVE-2026-109619 is about state that persists across requests on one worker, so old workers that are still running are not patched just because the package on disk is.

3. If you are on 8.2, the clock is running anyway. 8.2.35 fixes today's bugs, but the branch reaches end of life on 31 December 2026 (see PHP's supported versions page). Schedule the jump to 8.3 or later now, while you are already in the change window.

4. If you are on 8.1, 8.0 or 7.4, you have a hard deadline. There will be no official releases for these branches. The PHP team has published minimal source backports for 7.4, 8.0 and 8.1. That leaves three realistic options: build PHP with the backport applied, get a patched build from a vendor offering long-term support, or follow CISA and take the system offline until it is upgraded. "We will get to it next sprint" is not on that list.

5. Apply the interim settings, and do not mistake them for a fix. For systems that cannot be patched today, php.net lists settings that reduce exposure. Examples include memory_limit=-1 set through php_admin_value, a low post_max_size, session.upload_progress.enabled=0, and on 8.5, fatal_error_backtraces=0. The advisory says plainly that these narrow the attack surface without closing it. Tenable quotes release manager Joe Ferguson more bluntly: "ini settings won't save you." Use them to buy hours, not weeks.

6. Put a filter in front while you work. Every known trigger is a multipart/form-data request, and Cloudflare has shipped WAF rules on free and paid plans. Edge filtering is worth having in place, but Cloudflare's own post describes attackers mutating payloads to get around its rules. Treat a WAF as a delay, not a replacement for the patch.

The AI angle, without the hype

The research credits go to Calif.io, Wiz, OpenAI and Quarkslab, according to Cloudflare. The timelines show separate teams converging on engine code that dates back to 2012. Sansec says Calif found several RCE paths on 1 August and reported the first bug on 11 August. Wiz Research says it found its first bug on 19 August and reported it on 24 August.

Wiz is explicit about how it worked: "We used Wiz Atlas, Gemini 4 Argon and GPT-5.6 Sol throughout this research." It also says "a single researcher completed in a week what previously would have taken a world-class team months of work."

Discount that claim as much as you like; the practical point stands. Bugs in old engine code get found faster than they used to, and more than one team is likely to find each one. Then the embargo leaked. The planned disclosure date was 22 October, details got out on 20 October, and by Sansec's timeline attack traffic arrived hours before the fixed releases. The comfortable assumption that you have days, or a week, between an advisory and exploitation did not hold this time, and there is little reason to expect it to hold next time.

What to change after tonight

Once the fire is out, fix the process that made tonight hard:

  • Keep a live inventory of PHP runtimes, including the version each FPM pool, Apache module and container actually runs, not only the version the CLI reports. CISA's first instruction was to inventory every PHP system. If that took you hours today, automate it.
  • Make restarting a runtime a routine operation. If upgrading PHP and recycling workers across your fleet needs a ticket and a maintenance window, it will be too slow for the next engine-level bug.
  • Retire end-of-life branches on a schedule, not during an incident. Anyone on 7.4, 8.0 or 8.1 tonight is choosing between building from a backport and going offline. Plan to be off 8.2 before 31 December.
  • Keep an edge filtering option ready so you can turn it on within minutes, and remember that it buys time and nothing more.

The engine is shared by every PHP app you run, so its patch cadence is yours too. Today's releases are the fix. Install them tonight.

Related reading

Comments (0)

No comments yet. Start the conversation.

Never shown, never emailed — stored only as a hash.