9.2
CVSS 4.0 score PHP assigned to both CVEs
4
reporters credited for the same two bugs
48%
of PHP sites W3Techs measures run a branch PHP no longer patches
311
lines added in the PHP 8.1 minimal mitigation, against 10,759 in the 8.2 fix
The bug is not the lesson
The lesson of CVE-2026-109618 and CVE-2026-109619 is not "patch PHP". It is that any long-lived worker process that keeps per-request state in process globals has the same failure mode, and that the PHP 8.1 and older still serving a large share of the web will not receive the full fix from the PHP project.
Patching will happen. Fixed releases shipped for every supported branch on 21 Oct 2026, distributions and LTS vendors had the patch in advance, and Cloudflare had managed rules live hours before disclosure. A CISO who asks "are we patched" will hear "yes" for the main web tier by the end of the week. That answer is correct and incomplete.
Look at what PHP's own advisory says the bug is. Worker-global state "is retained by a worker but points to memory owned by one request", and that state "must be reset before the request memory is released". That describes PHP, and it describes a common design: a process starts once, serves thousands of requests, and trusts each one to clean up after itself on every exit path, including the paths where something went wrong. PHP-FPM pools, Apache prefork children and FrankenPHP workers work this way. So do application servers that boot a framework once and feed it requests; Laravel's documentation for its Octane server says Octane "does not always know how to reset the global state created by your application".
So the question for a security leader is not which PHP version is deployed. It is three questions. Which long-lived processes hold request data in shared state? What happens to that state when a request dies halfway? How long does a process live before it is replaced? PHP answered all three the hard way this month.
The second half of the argument is the fleet nobody is patching. PHP 8.1 reached end of life on 31 Dec 2025, and the project will not release a fix for it or anything older. It published minimal source backports instead, and the 8.1 one is a sliver of the real fix. W3Techs puts roughly 48% of the PHP sites it can measure on branches the project no longer patches.
What PHP disclosed on 21 October
The PHP security team published two advisories at 13:34 UTC on 21 Oct 2026, alongside releases 8.5.12, 8.4.27, 8.3.36 and 8.2.35. Both CVEs carry a CVSS 4.0 score of 9.2 from PHP as the CNA; NVD added its own CVSS 3.1 score of 9.8. The php.net announcement is blunt: "There are no complete mitigations, updating is the only safe choice."
CVE-2026-109618, "RCE via OOM corruption". PHP aborts a request that runs out of memory with a non-local jump. In some contexts request memory is reachable before it is fully initialised, and if any allocation fails at that moment, even one unrelated to the half-built data, shutdown later reads the invalid state. The advisory lists multipart parsing behaviours that let a request exhaust memory while staying inside its apparent limits, including bodies without a usable declared length and an unrestricted number of headers inside one part. On Apache mod_php, shutdown runs even when startup failed, so the sequence "happens entirely before any application-specific code begins execution". The advisory reports unauthenticated remote code execution validated over HTTP against mod_php with default input and memory limits.
CVE-2026-109619, "RCE via stale global state". Error, session, object and extension state that one request publishes can survive into the next request on the same worker after the memory behind it is freed. A fatal error, including running out of memory, can skip the reset, and an operation that runs before the next request initialises consumes the stale pointer: a use after free across requests. The confirmed cases are the PHP 8.5 fatal error backtrace path, on by default in 8.5, and the session upload progress lifecycle, enabled by default and needing no application call to start a session. RCE was validated on mod_php for both and on PHP-FPM for the session path.
-
Product //
PHP core runtime (php-src) -
Fixed in //
8.5.12, 8.4.27, 8.3.36, 8.2.35 -
No upstream release //
8.1 and older; minimal source backports for 8.1, 8.0, 7.4 -
CVSS //
9.2 (4.0, PHP as CNA); 9.8 (3.1, NVD) -
Highest risk //
Apache mod_php, before application code runs -
Also at risk //
PHP-FPM, php-cgi, FrankenPHP, LiteSpeed -
Exploited //
attack traffic reported by Cloudflare and Fastly; not in CISA KEV as of 21 Oct
The exploitation picture is split. Cloudflare says it "observed attack traffic targeting our customers" and saw payloads modified to get past its rules; Fastly reports evidence of attacks before disclosure. CISA's SSVC entry, recorded an hour after publication, says exploitation "none", and neither CVE is in KEV. The PHP Foundation adds that the CVE identifiers leaked the day before release. Treat both bugs as attacked and watch KEV.
The fix is instructive. PHP no longer jumps out of a failed allocation. It records the condition, stops the script at the next safe point, and ends the process outright if memory keeps climbing past a second threshold. It also resets the wider set of globals reachable before a request initialises. For 8.2 the patch touches 389 files.
Why two teams found it on the same day
PHP is heavily audited code. The PHP Foundation funded an external audit by Quarkslab in 2024, organised through OSTIF with money from Germany's Sovereign Tech Agency. Trail of Bits has also been auditing PHP. Earlier in 2026 the Foundation worked with Alpha-Omega to scan PHP with Anthropic's Claude Mythos. None of that surfaced this.
Then, on 11 Aug 2026, Calif reported an unconditional remote code execution, and OpenAI independently reported the same root cause the same day. Wiz supplied further attack vectors on 24 Aug, thirteen days later, and OpenAI added more on 18 Sep. The advisories credit four reporters in all. Three organisations, working apart, converged on one flaw within a fortnight of the first report.
The bug class was not new. Calif's write-up starts from a memory-limit remote code execution published in 2004 and a 2020 report of out of memory behaviour with long multipart variables, and notes that the 2024 audit found a related issue without proving exploitation. What changed was the cost of checking the idea everywhere.
Both reporters that describe their method used models. Wiz names its Atlas system, Gemini 4 Argon and GPT-5.6 Sol, and Google credits Gemini with contributions to discovery, exploit development, detection and patch validation. Calif describes an LLM "companion" for finding ways to hit the memory limit and an agent that reviewed allocation and shutdown code for interruption points. OpenAI's disclosure page did not load for us, so we do not characterise its method.
Our read: the bug is an invariant, not a pattern. "Every non-local exit leaves request state consistent" and "every global pointing into request memory is cleared before that memory is freed" are statements a reviewer can write down and check against every allocation site in a large C codebase. A human team runs out of patience before it runs out of sites. A model-assisted review does not, and anyone with the same public models can run the same check.
The Foundation's numbers show the other side. Its security team "receives far more reports than before", has worked through more than 100 that were invalid, duplicates or not reproducible, and has published 15 CVEs since 1 Jul. The planning assumption has to move with it: a serious flaw in famous code will be found by several parties at nearly the same time, and not all of them will report it.
One worker, two requests
The stale state bug is the clearest picture of the shape. Here it is at the level of what each stage achieves, the pattern the advisory calls a producer and a consumer.
Figure 01
How one worker carries a request into the next
- 01
Publish //
A request leaves its own data in a slot the worker keeps between requests.
- 02
Interrupt //
A fatal error, such as running out of memory, skips the cleanup.
- 03
Free //
The request memory is released; the worker and its stale pointer live on.
- 04
Reuse //
The next request on that worker reaches the stale pointer before it is reset.
- 05
Own //
Use after free turns into memory disclosure or code as the web worker.
Result //
No login, one shared worker, two requests.
Two details in the advisories carry the general lesson.
First, PHP-FPM "exits the child when request startup fails", which is what stopped the out of memory bug from being application-independent there. A worker that dies on a bad request cannot carry its corruption into the next one. Apache mod_php runs shutdown even when startup failed, and that is the configuration with the confirmed pre-application exploit. Nobody designed that difference as a security control. It is a lifecycle decision that turned out to matter.
Second, the advisory's fallback advice for the stale state bug is to recycle workers: "consider limiting the number of requests a worker may execute using pm.max_requests=1", accepting that recycling reduces success per request without removing the condition. PHP-FPM's default for that setting is 0, never recycle. Apache's MaxConnectionsPerChild also defaults to 0. Both defaults were chosen for performance, not for isolation between users.
Take PHP out of the sentence and the shape is everywhere. Any service that reuses a process or thread across users, keeps something request-owned in a global, singleton, thread-local or cache, and can be interrupted halfway through a request has to answer the question PHP got wrong: what does the next request see? In memory-unsafe code the answer can be code execution, as here. In memory-safe code it is usually the previous user's data; Octane's documentation warns that a singleton holding an earlier request will serve stale "headers, input, and query string data".
The PHP nobody is patching
PHP's end of life table is unambiguous. PHP 8.1 lost security support on 31 Dec 2025, with 8.1.34 as its last release; 8.0 ended on 26 Nov 2023 and 7.4 on 28 Nov 2022. The php.net advisory says "PHP 8.1 and older no longer receive updates from the PHP project". Google's guidance says applications on its PHP 5.5, 7.2, 7.3, 7.4 or 8.1 runtimes "remain vulnerable and must be upgraded". Google, Wiz and Calif all put the affected range back to PHP 5.4.
Given the severity, the PHP security team published source branches for 7.4, 8.0 and 8.1 with a backported mitigation. Each commit message says "Minimal mitigation for EOL version." The 8.1 change adds 311 lines across 23 files; the 8.2 fix adds 10,759 lines across 389 files, tests included. That backport helps people who build from source, and it is honest about what it is: not the full fix, and not a release any package manager will pick up.
Distribution vendors fill part of the gap, and they make inventories lie. Ubuntu fixed php8.1 on 22.04 LTS as 8.1.2-1ubuntu2.28, an end of life branch patched by the vendor, and php8.3 on 24.04 LTS as 8.3.6-0ubuntu0.24.04.13, a version string a naive scanner would call vulnerable. The volunteer Remi repository, common on RHEL-family hosts, was not updated on release day. Upstream version numbers alone cannot tell you which hosts are fixed.
Now the scale. W3Techs reported on 21 Oct 2026 that PHP runs 69.3% of the websites whose server-side language it can identify. Of those PHP sites, 27.0% report version 7, 7.7% version 5 and 0.1% version 4; of the 65.2% on version 8, 5.8% run 8.0 and 15.2% run 8.1. Together that is roughly 48% on versions the project no longer patches. W3Techs only counts sites that reveal a version, and some carry vendor backports under an old number. Neither caution makes the tail small.
The next cliff is close. The 8.2.35 announcement says 8.2 "reaches end of life on 31 December 2026". That is 71 days away, and W3Techs puts 27.7% of PHP 8 sites on 8.2.
End of life PHP hides where scanners reach last: vendor appliances that bundle a runtime, container images pinned to an old tag, internal tools nobody owns, CI images, and managed platforms with automatic base image updates switched off. The Docker official php:apache images deserve a note: they run mod_php under Apache's prefork module, the configuration the advisory validated. The fixed versions reached those images on release day, but a running container keeps its old build until someone rebuilds and redeploys it.
What it looks like on an engagement
On a sanctioned engagement we would not need these bugs to make the point, and we would not fire them at production. The work is mapping the shape.
We start with the runtime inventory, because it is usually wrong: every PHP endpoint behind every load balancer target, with the process model recorded beside the version, whether mod_php under prefork, PHP-FPM, FrankenPHP or LiteSpeed. For upstream builds of the supported branches there is a check that does not trust version strings: the fix adds a configuration directive, memory_hard_limit_percent, that a fixed 8.2 to 8.5 build knows and an unfixed one does not. The minimal end of life backports do not add it.
Then the lifecycle. How long does each worker live? Which per-directory or per-request overrides apply before a request starts, since the advisory names those as an early consumer? Does the edge bound multipart bodies that never declare a length, or only those that announce their size? A size rule keyed on the Content-Length header, the form Google's own Cloud Armor example takes, does not see a request that never sends one.
Finally we carry the invariant into the client's own services, which outlasts this patch: long-lived processes holding request data in shared state, error paths that leave work half done, workers never recycled. Those are review questions, not exploits, and they apply to a Python worker pool or a Java servlet container as much as to PHP.
For detection, the out of memory variant has a noisy precursor: bursts of multipart requests and memory exhaustion raised before any script runs, which PHP reports against the file name Unknown. Pair the rule below with access logs that record the request content type, which most default formats omit, and with worker crashes: PHP-FPM logs a child that "exited on signal", and Apache logs AH00052.
Figure 02
PHP memory exhaustion or input caps before any script runs
Sigma
title: PHP startup memory exhaustion or multipart caps (CVE-2026-109618 hunting)
status: experimental
description: Memory exhaustion reported against file Unknown, or multipart and input caps hit during request startup.
logsource:
product: php
service: error
detection:
oom:
message|contains:
- 'Allowed memory size of'
- 'Hard memory limit of'
early:
message|contains: ' in Unknown on line '
caps:
message|contains:
- 'Multipart body parts limit exceeded'
- 'Input variables exceeded'
condition: (oom and early) or caps
falsepositives:
- Legitimate large uploads against a low memory_limit
level: high What is still coming
Variants. Wiz says other variants likely exist. The stale state advisory lists further error, exception, object and extension state as "conditional", depending on the application and extensions, with no configuration mitigation that covers every case. Expect follow-up advisories on the supported branches. The end of life branches will not get them.
Rule churn. Cloudflare already reports payload changes aimed at its rules. Managed rules bought time on 21 Oct. They are not a reason to delay upgrades, and none of the three vendor posts we read says how the rules were tested.
The class beyond PHP. Non-local jumps for error handling are common in C; the Lua manual, for one, says Lua "uses the C longjmp facility to handle errors". Long-lived workers holding request state are a default architecture of the web. PHP's fix models a reasonable answer to both: when a process cannot unwind safely, stop at a safe point or exit outright rather than continue on corrupted state, and a process that serves many users should either prove its state is clean between them or not live long enough for that to matter.
What we do not know yet: how much attack traffic the CDNs have seen and from whom, whether the reported FPM demonstrations used the memory path or the session path, how far the minimal backports diverge from the full fix, and how long-running PHP servers such as Swoole and RoadRunner fare, since neither advisory discusses them.
What to do today
-
Upgrade every PHP runtime to 8.2.35, 8.3.36, 8.4.27 or 8.5.12, or your vendor's fixed package, and restart every worker. Rebuild and redeploy container images, and check FrankenPHP binaries separately.
-
Build the end of life list now. Every host or image on 8.1 or older gets an owner and a date: upgrade, move to a vendor backport, or isolate. PHP 8.2 joins the list on 31 Dec 2026.
-
Where you cannot upgrade today, apply the advisory's interim settings administratively and restart. memory_limit=-1 with a per-worker container or cgroup memory limit (never RLIMIT_AS or RLIMIT_DATA), session.upload_progress.enabled=0, and on 8.5 fatal_error_backtraces=0.
-
Bound multipart at the edge, including bodies with no declared length, and block it on routes that never take uploads. Keep post_max_size low and keep managed WAF rules on as a backstop, not a fix.
-
Recycle workers and ask the lifecycle questions of your own services. Set pm.max_requests or MaxConnectionsPerChild to a finite value, then list every long-lived process that holds request data in shared state and check what it does when a request dies halfway.
SOURCES
- php-src GHSA-w8hw-w9fx-j79w // CVE-2026-109618 root cause, mod_php RCE before application code, and the memory_limit mitigation
- php-src GHSA-6wmf-4rv5-p3rp // CVE-2026-109619 stale global state across requests on the same worker, confirmed on mod_php and FPM
- PHP security advisory // fixed releases, who is at risk, no updates for 8.1 and older, minimal source backports
- The PHP Foundation // Calif and OpenAI reported on 11 Aug, Wiz on 24 Aug; prior audits and the report volume
- Cloudflare // managed WAF rules live before disclosure and attack traffic observed against customers