PHP2Shell: 9.2 Critical. Did Your Host Patch?

Four PHP releases landed on 21 October 2026: 8.5.12, 8.4.27, 8.3.36, 8.2.35. They fix two memory-safety bugs that had sat in the engine for a decade. One of them can be triggered before a single line of your application runs.

Cloudflare’s rules went live at 03:02 UTC that morning, nine hours before the official announcement, because the vulnerability had leaked. Cloudflare says it saw attack traffic.

Wiz Research found the bugs and built a single exploit for both. They named it php2shell. Soon after, there was a website and an emergency briefing.

PHP’s advisory calls them critical remote code execution vulnerabilities. In practice, an unauthenticated request can corrupt memory and run code as the web server user.

The bug that runs before your code

CVE-2026-109618 lives in how PHP handles memory exhaustion. PHP uses longjmp() to abandon a request when it hits the memory limit. On some paths, request memory is reachable before it has been fully set up. If an allocation fails at that moment, the cleanup routine reads data that was never initialised.

On Apache with mod_php, this happens before any application code runs. Your theme is not loaded. Your plugins are not loaded. Your very careful functions.php has not been read.

PHP demonstrated complete remote code execution over HTTP against mod_php, with default input and memory limits.

CVE-2026-109619 crosses request boundaries. A fatal error can abandon an operation before it resets state that points into the request heap. The heap is freed. The next request on the same worker follows the stale pointer. PHP confirmed that against mod_php, and against FPM through session upload progress — a feature that is on by default, and does not need your application to call session_start().

A hello-world page on an affected build is exposed. Updating the CMS changes application files. The engine underneath keeps its bugs.

The server interface sets the risk. mod_php carries a demonstrated, application-independent exploit. FPM exits the child process when request startup fails, which closes one path and leaves others, and PHP’s source analysis supports susceptibility without a complete FPM exploit for that specific bug. FrankenPHP is likely susceptible on the same analysis. LiteSpeed is likely reachable through application-specific paths that were not validated. PHP’s position is that no web-serving environment is immune.

Two days to exploit, two months to patch

19 August: discovery. 20 August: code execution against Drupal. 21 August: the WordPress stack. 24 August: reported to the PHP security team. 21 September: acknowledged, fix under way. 21 October: patched.

Two days to break it. Two months to fix it. That is what coordinated disclosure looks like when the bug is genuinely hard.

The embargo did not hold. Cloudflare shipped rules ahead of the announcement because the details were already moving. Nine hours early is long enough to matter if you have the specifics, and short enough that most defenders were asleep.

Three answers on the same day

The NVD entry carries a CISA assessment recorded at 14:34 UTC on 21 October that lists exploitation as “none.” CERT-EU, publishing the same day, said proofs of concept were circulating and that this raised the likelihood attackers were already exploiting. Cloudflare said it observed attack traffic.

A proof of concept is not a campaign. A campaign is not a confirmed breach. The CISA entry is a snapshot of what was known at 14:34, and 14:34 was a long time ago.

The question worth waiting on is not whether someone is exploiting it. It is whether your runtime was patched before they did.

Two scores, one bug

PHP rates both vulnerabilities 9.2. NVD rates the first one 9.8. The gap is not a disagreement about damage. It is a disagreement about how hard the attack is to pull off: one form says high complexity, the other says low. The bug has no opinion.

Both descriptions agree on the parts that matter. No login. Reachable over the network. Full loss of confidentiality, integrity, and availability on the worker.

Everything that buys time

PHP’s temporary guidance is unusually candid. Every measure comes with its own confession.

Measure What it does What it leaves open
Set memory_limit=-1 Removes the controllable limit the confirmed exploit relies on An underlying allocation failure can still reach the vulnerable path. Nothing else may restore a finite limit.
Lower post_max_size Reduces the multipart memory strategies that trigger exhaustion Not a fix. Enforce decoded request size at the proxy as well.
session.upload_progress.enabled=0 Blocks the confirmed session paths Only after every worker restarts. Other lifecycle paths remain.
fatal_error_backtraces=0 on 8.5 Removes the confirmed fatal-backtrace producer Other request-owned state remains reachable.
pm.max_requests=1 Stops a worker from serving a second request Costs throughput. Does not patch anything.
A web application firewall Blocks known payloads and exploit shapes No control over memory corruption inside the process.

Turn memory_limit off and you remove the limit the exploit abuses, along with the limit that keeps one bad request from eating the server. Disable session upload progress and restart every worker, or the change does nothing. Use a container limit, but not RLIMIT_AS or RLIMIT_DATA, because those cause the allocation failure you are trying to avoid.

Then PHP prints the sentence that turns the table into a holding action: there are no complete mitigations. Updating is the only one.

You were protected. You were not patched.

Keep the firewall on anyway. Cloudflare added response-side detection because request-only rules could not reliably tell an exploit from legitimate traffic, and its advice is still to patch. The WAF buys the hours; the package ends the problem. If you want that layer, Malcure WAF360 is one option — alongside the patch, never instead of it.

Five questions

Most sites do not own their runtime. If you rent a server or buy a managed plan, someone else does, and a version number on a dashboard will not settle it. The panel can disagree with the process. A distribution backport can carry a version number that looks older while containing the fix.

Ask these.

  1. Which PHP package is serving requests right now — the version the running process reports, not the number in the control panel?
  2. Is that the fixed release, or a vendor backport carrying a different version number?
  3. Which server interface is in front: mod_php, FPM, php-cgi, LiteSpeed, or FrankenPHP?
  4. Were the workers restarted after the package changed?
  5. If the branch is 8.1 or older, is there a vendor backport, or is the honest answer “upgrade”?

What counts as an answer: a package version, a changelog entry, and a restart time. “We are fully patched” is a mood, not a record.

If you run the server yourself

php -v reports the command-line build, which can differ from the FPM pool or the Apache module. Confirm the package came from your distribution or vendor. Restart the workers. A patch that is installed but not loaded is a rumour.

Branch Affected Fixed release
PHP 8.5 before 8.5.12 8.5.12
PHP 8.4 before 8.4.27 8.4.27
PHP 8.3 before 8.3.36 8.3.36
PHP 8.2 before 8.2.35 8.2.35

If you are on 8.1 or older

You are outside support. PHP’s end-of-life table lists 8.1 as unsupported since 31 December 2025, 8.0 since November 2023, 7.4 since November 2022, and 7.3 since December 2021.

For this incident, PHP published one-off source backports for 7.4, 8.0, and 8.1. It published nothing for 7.3 and older. A source backport is a patch for someone with a compiler, not for someone with a website. Your realistic options are a vendor package, an upgrade, or taking the site off the public internet until one of those happens.

If the server may have been hit before it was patched, the work changes from prevention to cleanup. Start with the 12 practical tests for a compromised site and the nine signs a site is already compromised, then scan the public surface for what an exploit would leave behind. A clean scan does not confirm the runtime was patched. It only tells you what the scanner could see.

The name and the version

The exploit got a name, a website, and a webinar. The fix got a version number and a restart.

The name is what people shared. The version is what keeps the site up.

If your host cannot answer

Fully managed WordPress hosting puts the runtime, the packages, and the restarts in one place, with someone accountable for them.

If you are staying where you are, SiteCare maintenance is where that update work belongs.

Written by
Principal Security Researcher, Malcure Web Security

Shiv has worked in security and infrastructure since 2002, with hands-on experience across enterprise network security, incident response, problem coordination, triage management, Windows and Linux systems provisioning, scripting automation, Nginx, ModSecurity, reverse proxies, web application firewalls, WordPress malware removal, malicious redirect cleanup, SEO spam remediation, WP-CLI workflows, vulnerability response, and website hardening. His research informs Malcure’s malware detection, cleanup, and hardening methodology.