Patch PHP2Shell Before Malicious Requests Reach Web Workers
Emergency PHP2Shell fixes close two unauthenticated RCE paths and require urgent patching across mod_php, PHP-FPM, CGI, FrankenPHP, and LiteSpeed.

Patches are available now for PHP2Shell, a pair of critical, unauthenticated remote code execution flaws affecting PHP installations that serve web requests. Patch PHP 8.x systems to PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35. Systems still running PHP 7 or earlier should migrate to a fixed release; some distributions and LTS vendors may provide backported fixes. Configuration mitigations can reduce exposure. They do not finish the job. The PHP security team is explicit that updating is the only complete defense.
The PHP security team has now released the emergency patches. The earlier Tuesday warning is no longer just a preparation notice; operators now have an active patching decision to make. The PHP2Shell security report says early attacks were already being observed before the fixes arrived, and the UK National Cyber Security Centre advised organizations to patch immediately or take affected systems offline. See also: PHP2Shell update alert. Here, I’m concentrating on the failure mechanisms, the differences between SAPIs, and the deployment decisions those differences force in practice.
Which PHP deployments are exposed to PHP2Shell?
Every PHP installation serving web requests is within the stated affected scope. That means Apache with mod_php, PHP-FPM, php-cgi, FrankenPHP and LiteSpeed. Apache mod_php carries the highest confirmed risk. An unauthenticated request can trigger the demonstrated attack before any application code gets a chance to execute.
That timing—before application code—is the boundary that matters. Middleware cannot intercept it. Neither can framework validation or application authentication, because the vulnerable state transition happens during PHP request startup. Updating WordPress, Joomla, Drupal or an application framework does not substitute for patching the PHP runtime. In my view, a compromised plugin should be handled as a separate problem. Related coverage: Rank Math PRO malware incident.
| PHP deployment | What the advisories establish | My operational priority |
|---|---|---|
| Apache mod_php | This is the highest-risk environment. Complete RCE was validated over HTTP, and request shutdown still runs after startup failure. | I’d patch this first, especially anywhere it accepts requests directly from the internet. |
| PHP-FPM | Source analysis supports susceptibility to the OOM flaw, although no complete FPM exploit was demonstrated for that path. Early configuration diagnostics can also expose stale state under specific frontend behavior. | I’d treat FPM as affected. Pretending it is a safe alternative to mod_php would be a mistake. |
| FrankenPHP | Its unconditional request shutdown makes application-independent OOM attacks likely. An equivalent default early-configuration consumer was not established for the stale-state flaw, but that absence does not establish immunity. | I’d patch it instead of depending on the absence of a demonstrated consumer. |
| LiteSpeed or LSAPI | LSAPI models FPM for the OOM path. Application-specific exploitation is considered likely but was not validated, and special environment INI values may create an early stale-state consumer. | I’d include every worker in the same emergency rollout, with no exception carved out. |
| php-cgi | The PHP security team explicitly includes php-cgi among web-serving installations affected by both flaws. | I would not defer it merely because the detailed advisory spends more time on persistent workers. |
A CLI-only PHP environment is outside the stated scope of installations serving web requests. Keep that distinction narrow. Very narrow. If the same host also runs any web SAPI, assess and patch that SAPI; having CLI PHP installed is not evidence that the server is unaffected.
How CVE-2026-109618 crosses request shutdown
CVE-2026-109618 turns an out-of-memory failure into code execution. PHP can leave partially initialized request memory in an invalid state and then process that memory during shutdown. The confirmed outcome is unauthenticated RCE running with the privileges of the web worker account.
Before the patch, PHP used longjmp() to escape the current context when an allocation encountered an out-of-memory condition. By then, some request memory can already be reachable even though initialization has not finished. If allocation fails at that precise point, the early exit does not fully account for the temporary state. Request shutdown can later read invalid memory or branch on values derived from it.
The failed allocation does not have to be directly related to the invalid object. That distinction matters. An attacker instead needs a repeatable way to exhaust memory while PHP is sitting in the vulnerable state. The advisory identifies several routes that create exactly that pressure:
- A multipart request without a usable declared content length can pass the initial
post_max_sizecheck and then continue sending decoded body data. - A multipart part can contain an unrestricted number of headers whose in-memory representation consumes substantially more space than the matching wire representation.
- Empty file controls do not count towards
max_file_uploads, but they still create file metadata arrays; nested field names multiply that array work. - Application-controlled input can trigger one large allocation or repeated memory use that reaches the same failure condition.
Apache mod_php is the easiest case to reason about. It invokes request shutdown even when request startup fails. Automatic body parsing can therefore exhaust the configured memory limit during startup, leave temporary state invalid, and pass that invalid state to shutdown before the application starts executing at all.
FPM takes a different path during startup. It exits the child after request startup failure, preventing that child from consuming the invalid state. That narrows the confirmed route. It does not clear FPM. After normal startup, another OOM can still happen in a context that mishandles the early exit and eventually reaches request shutdown.
| OOM handling | Runtime behavior | Security consequence |
|---|---|---|
| Before the patch | longjmp() immediately leaves the current context when OOM occurs. | Shutdown may encounter request state that was reachable but had not been completely initialized. |
| After the patch | A soft OOM is delayed until the next VM safe point. A second, higher threshold triggers an immediate hard OOM with _exit(). | Shutdown no longer receives the invalid state, while PHP preserves its normal ability to limit memory consumption. |
Exploit reliability will differ across installations. The advisory says it depends on the PHP build, installed extensions, frontend behavior and allocator layout. Extra workers and unrelated traffic may increase the number of attempts an attacker needs. That is friction, not a fix; neither removes the underlying vulnerable condition.
How stale global state survives into a later request
CVE-2026-109619 lets one request leave a worker-global pointer referring to freed request memory. A later request can then consume that stale pointer. Possible outcomes include heap use-after-free, memory disclosure and unauthenticated RCE as the web worker.
PHP workers retain global error, session, object and extension state while handling multiple requests. Any pointer from that persistent state into request-owned memory must be reset before PHP releases the request heap. A fatal interruption can skip that reset entirely. A warning during a later shutdown stage can also repopulate error state after the normal reset has already completed.
Exploitation needs both halves of the sequence: a producer and a consumer. One without the other is not enough.
- An attacker-reachable PHP operation stores request-owned state in a location visible to the persistent worker.
- A fatal error prevents the reset, or a later diagnostic publishes new request-owned information after the normal reset.
- PHP frees the request heap while the worker still retains the reference.
- The same persistent worker accepts a later request.
- An operation before normal request initialization reads, frees or branches on that stale state.
Fatal producers can include parse or compile failures, uncaught errors, a failed require, execution timeouts and OOM. The two confirmed early consumers are INI diagnostics and session upload progress.
An invalid or deprecated INI value can emit a warning, notice or deprecation before normal request initialization. Under mod_php, values from .htaccess and Apache php_value, php_flag or php_admin_* directory configuration are applied early enough to become this consumer. Normal php.ini startup configuration is not reapplied between requests in the same way.
FPM is conditionally exposed when a frontend sends PHP_VALUE or PHP_ADMIN_VALUE parameters that produce an early diagnostic. FPM pool settings are applied when a worker starts. By contrast, .user.ini is applied after normal initialization. Those two locations do not create the same early consumer.
Session upload progress supplies a separate consumer before ordinary session initialization. An interrupted upload can leave request-owned session or decoder state published. A later upload progress operation can then reach that state and handle it as if it were still valid.
PHP 8.5 has another confirmed path when fatal_error_backtraces is enabled. Fatal error reporting stores request-owned backtrace state, and shutdown misses the reset. A diagnostic from a later request can then reach the stale value. The advisory calls this the strongest demonstrated case and uses it as the basis for confirmed mod_php exploitation.
Patching PHP2Shell across PHP release lines
The permanent remediation is simple to state: deploy a fixed PHP release or an appropriate vendor-supplied backport. PHP 8.1 and older no longer receive updates from the PHP project. Legacy installations therefore need a supported vendor path, a source backport, or migration to a fixed release.
| Current release line | Fixed target or available path |
|---|---|
| PHP 8.5 | Update the runtime to PHP 8.5.12. |
| PHP 8.4 | Update the runtime to PHP 8.4.27. |
| PHP 8.3 | Update the runtime to PHP 8.3.36. |
| PHP 8.2 | Update the runtime to PHP 8.2.35. |
| PHP 8.1 or older | Use a fixed distribution or LTS vendor package, or migrate to a supported fixed release. |
| Source builds on PHP 7.4, 8.0 or 8.1 | The PHP security team supplied minimal source backports for installations that lack an LTS vendor. |
I’d use the minimal source backports as an emergency bridge, not as permission to keep an unsupported release indefinitely. That is my judgement, not a compatibility guarantee from the advisory. For an old application, a vendor-supported backport may be the lower-risk immediate option. The eventual destination should still be a supported PHP line.
- Inventory every web-facing PHP SAPI, including mod_php, FPM, CGI, FrankenPHP and LiteSpeed workers.
- Compare each installed release line with the fixed versions above. Then identify systems that depend on distribution or LTS vendor backports.
- Deploy the fixed runtime to mod_php systems first, then finish the remaining web-serving pools without treating FPM as unaffected.
- Restart every affected worker. No long-running process should retain old code or stale global state. The advisory explicitly requires worker restarts when applying the temporary stale-state mitigations.
- Verify that the runtime handling real web requests is the fixed build. I’d inspect each SAPI separately rather than assume that one host-level PHP installation represents every worker.
- Keep conservative request-size and multipart controls after patching, because the advisory says those controls continue to reduce hard-OOM and denial-of-service risk.
I would not roll back to a vulnerable binary because an application fails after deployment. Keep the boundary clear: remove the affected service from exposure while resolving compatibility, or use a fixed vendor backport. That matches the NCSC’s stated choice exactly—update immediately, or take the system offline until it can be patched.
Reducing exposure until every PHP worker is patched
Temporary configuration changes can remove confirmed trigger paths. None provides complete protection. I’d use these changes only to narrow the patching window, and I’d enforce them at a level that application or directory configuration cannot reverse.
Controls for CVE-2026-109618
The primary configuration mitigation is an unlimited PHP memory setting, enforced administratively:
memory_limit=-1
This removes the predictable per-request memory boundary used by the confirmed controllable-OOM exploit. It is still incomplete. An underlying allocation such as mmap() can fail and enter the vulnerable longjmp() path anyway. Restart every PHP worker, then verify that no pool, directory or application setting restores a finite limit.
The availability trade-off is real. Without a per-request PHP memory limit, the process needs an external boundary. The advisory recommends a container or cgroup limit such as memory.max, preferably configured so only the affected worker is killed. Killing an entire container or cgroup may also kill the master and every worker. Killing one FPM child, by contrast, loses its current requests and allows the FPM master to replace that child.
Do not swap in RLIMIT_AS or RLIMIT_DATA. The advisory warns that both can cause mmap() to fail at the limit and trigger the vulnerable path anyway.
Keep post_max_size low too. The published starting points are:
post_max_size=8M
Use 8M where the application does not require uploads, or 32M where uploads are necessary. The frontend should enforce decoded request size independently. Reducing max_input_nesting_level and bounding input variables, multipart parts, per-part headers and uploads reduces OOM pressure further. If the application accepts no multipart form data, block that data before PHP ever receives it.
Controls for CVE-2026-109619
Disable session upload progress. Then restart every worker:
session.upload_progress.enabled=0
On PHP 8.5, disable fatal error backtraces too. Again, restart every worker:
fatal_error_backtraces=0
That second setting blocks only the confirmed fatal-backtrace route. It does not cover the other possible producer-and-consumer pairs.
Remove invalid or deprecated per-request settings that generate diagnostics before initialization. Hiding those diagnostics with error_reporting, display_errors, log_errors or @ does not help. PHP touches the stale global state before deciding whether it should report, display or log the warning.
Fix attacker-reachable fatal errors and execution timeouts, especially around callbacks, uploads, imports and cleanup. That reduces known opportunities to leave state published. I still would not count application fixes as a substitute for deploying the patched runtime.
WAF rules and hidden diagnostics do not close the gap
A web application firewall can block known PHP2Shell request patterns. It cannot guarantee coverage for every route into corrupted state. Cloudflare had deployed rules for known methods and was already observing early attacks, but it described those protections as temporary and kept urging immediate patching.
My read is straightforward: a WAF is useful as a short-lived outer filter while workers are replaced, and nothing more. It is not a defensible stopping point. The OOM advisory includes application-specific allocation routes, and exploit reliability varies with the PHP build, extensions, frontend and allocator layout. A request signature designed around one demonstrated payload cannot prove that every other route is blocked.
I apply the same caution to FPM’s behavior after startup failure. Exiting a child removes one confirmed sequence. Source analysis still marks FPM as susceptible after normal startup, however. Architecture differences change which exploit paths are available; they do not override the security team’s statement about affected systems.
Signals and rollback boundaries after deployment
The supplied reports do not identify a definitive log message or standalone indicator that proves exploitation. Successful stale-state access can happen before PHP decides whether a diagnostic should be displayed or logged. Clean application logs are therefore not evidence that an unpatched worker is safe.
After rollout, I’d first check the version actually serving web requests. Then I’d verify that every old worker exited. During the transition, I’d also inspect OOM events, fatal errors during multipart processing, repeated worker replacement and unusual upload-progress traffic. Those are investigation signals derived from the documented mechanisms, not confirmed indicators of compromise.
Keep the rollback boundary on a fixed build. If the new release exposes an application compatibility problem, isolate that service or move it to a fixed vendor backport instead of restoring the vulnerable runtime. Then verify the replacement worker directly. Leave request-size controls and external memory boundaries in place: the advisory says they continue reducing hard-OOM and denial-of-service risk even after the corruption flaw has been patched.
Frequently asked questions
Is PHP-FPM protected from PHP2Shell because failed startup kills the child?
No. Killing the child prevents one confirmed startup sequence, but source analysis still considers FPM susceptible after normal startup. FPM can also be conditionally exposed when a frontend supplies PHP_VALUE or PHP_ADMIN_VALUE settings that emit an early diagnostic.
Can memory_limit=-1 replace the PHP2Shell patch?
No. It removes the predictable per-request memory boundary used by the confirmed OOM exploit, but an underlying allocation can still fail and reach the vulnerable path. Updating to a fixed release is the only complete mitigation.
What should a PHP 7 or PHP 8.1 server do?
PHP 8.1 and older no longer receive updates from the PHP project. Use a fixed distribution or LTS vendor backport, migrate to a supported fixed release, or apply the provided minimal source backport for PHP 7.4, 8.0 or 8.1 where appropriate.
Do Cloudflare or other WAF rules fully block PHP2Shell?
No. WAF rules can block known exploitation methods and reduce exposure during rollout, but the published guidance describes them as temporary. The official security patches remain the only permanent defense.
Source: www.bleepingcomputer.com
How this article was made: drafted with AI from the source above, then fact-checked against it before publishing.



