↓ Skip to main content
  1. Tech Blog: AI, Security, Infrastructure & Open Source/

PHP2Shell — Patch the Runtime, Not Just the Application

Osmond van Hemert
Author
Osmond van Hemert
Breaches & Zero-Days - This article is part of a series.
Part : This Article

Your application can validate every input it sees and still be exposed to an attack that starts before its first line runs. That is the uncomfortable distinction behind PHP2Shell: the vulnerable component is the PHP runtime, not a route handler you can repair with another validation rule.

On October 21, the PHP project released fixes for two critical remote code execution vulnerabilities, CVE-2026-109618 and CVE-2026-109619. The advisory covers PHP installations serving web requests and urges immediate updates. For Apache with mod_php, the project describes a demonstrated unauthenticated attack before application code executes.

The immediate task is not to rewrite your application. It is to identify which PHP builds actually serve requests, get patched code into every worker, and separately assess whether an exposed system needs incident response.

Two Bugs in the Request Lifecycle
#

Both vulnerabilities involve assumptions about the lifetime of request data, but they fail in different ways.

CVE-2026-109618: Out-of-Memory Handling Leaves Invalid State
#

The out-of-memory corruption advisory explains that PHP uses longjmp() to abandon execution when an allocation fails. If that happens while request data is only partly initialized, cleanup can subsequently treat invalid state as usable memory. The result can be remote code execution with the web worker’s privileges.

The important Apache detail is where this can happen. With mod_php, automatic request-body parsing can exhaust memory during startup, and shutdown processing still runs after startup fails. Application middleware has not had an opportunity to reject the request.

PHP-FPM behaves differently: it exits the child when request startup fails, blocking that particular sequence. But the advisory still identifies susceptibility after successful startup. A difference in request handling is not a declaration that FPM is safe.

CVE-2026-109619: One Request Leaves State for the Next
#

The stale global state advisory describes request-owned memory that remains referenced by a persistent worker after that memory is freed. A later request can encounter the stale reference before normal initialization clears it, creating a use-after-free.

Exploitation requires a producer that leaves the state behind and a consumer that uses it later in the same worker. The advisory reports validated session-lifecycle RCE against both mod_php and FPM. Multiple workers can make an attack less reliable per request, but they do not remove the underlying condition.

This is also why “we don’t call that feature in our application” is not a sufficient assessment. The advisory notes that session upload progress can run before ordinary session initialization without an application call to session_start().

These are runtime lifecycle bugs, not another compromised release pipeline. The earlier breach of PHP’s Git server concerned malicious source commits; this incident requires correcting the behavior of legitimate, vulnerable PHP builds.

Which PHP Releases Contain the Fixes?
#

The PHP project’s October 21 release advisory identifies these patched releases:

PHP BranchPatched Release
8.28.2.35
8.38.3.36
8.48.4.27
8.58.5.12

For distribution-managed PHP, check the distribution’s security notice and exact package build. PHP says Linux distributions and LTS vendors received patches in advance, so a vendor’s documented backport matters more than guessing from the upstream-looking version number alone.

PHP 8.1 and older no longer receive upstream updates. The project has provided minimal source backports for 7.4, 8.0, and 8.1 for this incident, and some vendors provide their own fixes. Do not mistake that emergency assistance for restored upstream support. If you depend on an older branch, obtain explicit confirmation of coverage from your vendor or move to a supported release.

Inventory the Runtime That Serves Traffic
#

My first recommendation is to make a deployment list, not just a repository list. A successful application dependency update says little about whether an old FPM service or container image remains in production.

The operational lesson resembles Log4Shell’s dependency-discovery problem, even though the exploit mechanics differ: the component you need to fix may be in infrastructure your application team does not directly maintain.

For each PHP-backed service, record:

  • How requests reach PHP: Apache module, FPM, CGI, FrankenPHP, LiteSpeed, or a managed hosting platform.
  • Who supplies the runtime: distribution package, container base image, hosting vendor, appliance supplier, or your own build.
  • What proves it is patched: the upstream release or vendor package advisory, mapped to the deployed artifact.
  • Who owns the rollout: an identified person or team, with a way to verify completion.

Treat a CLI version check as a clue, not proof of what the web process runs. Verify the service configuration and deployed artifact through your existing administrative tooling. Do not leave a public diagnostic page behind as part of the investigation.

For containers, rebuild or obtain patched images and replace running instances. For host-installed runtimes, update the packages and ensure serving processes are restarted into the patched build. Account for every replica and pool; checking one healthy instance is not evidence that the rest have been replaced.

I would close the rollout only when each deployment has a known patched artifact, no old workers serving traffic, and passing checks for its normal application flows—including uploads where relevant.

Configuration Changes Are Not a Complete Fix
#

The PHP advisories are unusually explicit: there is no complete generic configuration mitigation. They describe ways to reduce particular attack paths, not a configuration profile that makes an unpatched installation safe.

For example, disabling session upload progress addresses specific stale-session paths; disabling fatal_error_backtraces on PHP 8.5 addresses a particular producer. Neither covers all request-owned state. Suppressing warnings or turning off error display does not repair the lifecycle bug either.

The OOM advisory’s temporary memory-limit guidance deserves especially careful reading. Enforcing memory_limit=-1 removes a controllable PHP allocation limit, but underlying allocation failures remain possible. The advisory also warns against substituting RLIMIT_AS or RLIMIT_DATA, which can trigger the vulnerable allocation-failure path. This is not a setting to copy into production without understanding its availability consequences.

If you cannot patch promptly, the October 21 AHA advisory recommends taking the system offline until an update can be made. That is a clearer safety boundary than hoping a partial workaround covers your application’s exact combination of extensions, configuration, and request flow.

I would still enforce least privilege and limit unnecessary network access. Those controls can reduce what a compromised worker reaches, as discussed in container security hardening. They do not make the PHP process itself invulnerable.

Patching and Incident Response Are Separate Jobs
#

Installing a fixed runtime addresses the vulnerabilities. It does not establish what happened while the old runtime was reachable.

The AHA notice specifically warns that patching does not remove an attacker who already gained access. It recommends monitoring web servers for suspicious activity, including unexpected command launches and file downloads, and preserving evidence.

For an exposed deployment, I would review process activity, unexpected application-file changes, and unusual outbound connections alongside the patch rollout. If there is evidence of compromise, escalate through incident response: preserve the relevant records, establish the scope, and determine which credentials or systems the worker could access. Do not label a host clean just because its PHP version now looks correct.

Equally, do not present exposure as proof of exploitation. The distinction matters when communicating with stakeholders. A precise status report separates “patched,” “investigation ongoing,” and “compromise confirmed” rather than treating them as one checkbox.

My Take
#

PHP2Shell exposes an ownership gap that application teams cannot solve with better input validation. If a language runtime can process hostile input before the application starts, runtime patching has to be an explicit part of operating that application—not an assumption delegated indefinitely to a base image or hosting provider.

The response I would want from a platform team is modest but verifiable: a runtime inventory with owners, a repeatable replacement path, and evidence that every serving instance received the fix. A dashboard saying an image was rebuilt is not the same thing as proof that the old deployment stopped receiving requests.

My expectation is that teams with that operational discipline will recover faster from the next runtime emergency, whatever language it affects. The worthwhile investment after this patch is not another one-off workaround. It is making the next runtime replacement routine enough that nobody has to discover its owner during an incident.

Breaches & Zero-Days - This article is part of a series.
Part : This Article

Related