PHP Shipped Two 9.2 RCEs. My Server Says 8.3.35, and That Answer Is Not Good Enough
Adil Sher
Author
On 21 October the PHP project published an advisory of a kind I had not seen in a while: two separate critical vulnerabilities, both rated CVSS 9.2, both capable of unauthenticated remote code execution. Not a privilege escalation that needs a foothold first. Not a denial of service you can shrug at. Code execution, from a request, before your application code runs.
This site runs on PHP. So before writing a word about it, I went to check my own version. That check did not give me a clean yes or no, and working out why turned out to be the more useful story.
The two bugs, in plain terms
Both live in how PHP handles a request going wrong, not in anything you wrote.
CVE-2026-109618, "RCE via OOM corruption." When PHP hits its memory limit it has historically used longjmp() to bail out of the request. If that allocation fails while PHP's internal request data is only half initialised, the jump leaves invalid memory behind, and request shutdown then reads it as though it were valid. The allocation that fails does not even have to be related to the memory that ends up corrupted. The advisory lists ways to steer PHP into that state on purpose, and the nastiest is that a multipart request without a usable declared content length can slip past the initial post_max_size check. Multipart parts can also carry unlimited headers, and headers cost far more memory inside PHP than they do on the wire.
CVE-2026-109619, "RCE via stale global state." Persistent workers keep global state across requests, and some of it points into memory owned by a single request. If that state is not reset before the request heap is freed, it becomes a dangling pointer, and anything reading it before normal initialisation gets a use after free. Exploitation needs a producer and a consumer landing on the same worker. The producers are ordinary fatal interruptions: a parse error, an uncaught error, a failed require, a maximum execution time exceeded, an out of memory abort. The consumers are the short, surprising list of things that run before request initialisation: diagnostics from early INI values, and session upload progress, which is on by default and does not need session_start().
The two chain together, which is why they shipped together. An out of memory during multipart upload processing can leave session globals pointing at freed memory, and a later upload progress operation then walks it.
Who is actually exposed
The confirmed, demonstrated, full unauthenticated RCE is on Apache with mod_php, at default limits, on a prefork configuration, validated over plain HTTP. If that is your stack, this is an emergency. PHP-FPM is susceptible by source analysis and the session case was validated against it, but no complete exploit has been published. FrankenPHP and LiteSpeed LSAPI are likely susceptible, unvalidated. Nobody is clearly safe, but mod_php sits at the wrong end of the gradient.
Both CVEs carry AC:H, high attack complexity. A day ago I would have read that as breathing room. Not now: press coverage on 22 October reports attackers working with these bugs before the patch landed, and the pre-announcement had reserved the right to ship early if details leaked or exploitation appeared. The PHP project itself has confirmed a demonstrated exploit, not attacks in the wild, and that distinction is worth keeping straight. But the window where this was theoretical is closed. The UK's NCSC has told organisations to prepare for immediate patching and, failing that, to consider taking systems offline. Nobody uses that language about a hypothetical.
Fixed versions, and why my own check was inconclusive
Patched releases: 8.2.35, 8.3.36, 8.4.27, 8.5.12, plus 8.6.0 RC4. Anything below those on each branch is affected. PHP 8.1 and older are out of support, with minimal source backports published for 7.4, 8.0 and 8.1.
Here is what my server said:
$ php -v
PHP 8.3.35 (cli) (built: Oct 21 2026 00:00:00) (NTS)
Read the version alone and I am one release behind the fix. Read the build date and the picture shifts, because 21 October is the day the advisory went out, and a build stamped that day is not a coincidence.
That is the shared hosting trap, and it cuts both ways. Managed hosts commonly run their own PHP packages and backport security fixes while keeping the upstream version string, bumping only their internal release number. So a server can be patched while php -v still reports a version the advisories call vulnerable. It can also just be unpatched. The version string cannot tell you which, and I could not find a published changelog for my host's build either way.
Two further gaps in that check. The binary I ran is the CLI one, and on managed hosting the version serving web requests is frequently different, with cron sometimes calling a third. CLI is the least relevant of the three for an advisory about requests. And the real question is not which number appears, it is whether the specific commits are in the build, which only the host can answer.
So my position: probably patched, asking the host to confirm, treating it as unpatched until they do. Take that order of operations rather than my version number. Check php -v, then your panel's version selector or a phpinfo() page for the web SAPI, then the absolute binary path your cron lines actually call. If any reports below the patched release for its branch, and your host has not stated in writing that the fixes are backported, assume exposure and ask.
If you cannot patch today
Updating is the only real fix, and the advisory is blunt that the mitigations only close specific paths. Still: set memory_limit=-1 through something your application cannot override, such as php_admin_value in an FPM pool. Tighten post_max_size, keep upload_max_filesize under it, enforce a decoded request size limit at the proxy, and block multipart requests at the edge if your app never uses them. Avoid RLIMIT_AS and RLIMIT_DATA, which make mmap() fail and walk you into the vulnerable path. Then set session.upload_progress.enabled=0 and restart every worker, because ini_set() and .user.ini will not do it. On PHP 8.5 also set fatal_error_backtraces=0, the state behind the confirmed mod_php exploit.
The part I keep coming back to
The producers in the second bug are the things every application does when it fails. A parse error. A timeout. Running out of memory. Those have always been filed as reliability problems, the kind you promise to clean up next sprint. This advisory moves them into the security column, which means a laggy endpoint that times out under load is not only slow, it is a lever.
I have an error log on this site full of entries I have been ignoring for weeks. I am going to go read them now.
Sources: the PHP project's advisory and release notes of 21 October 2026, the GitHub Security Advisories GHSA-w8hw-w9fx-j79w and GHSA-6wmf-4rv5-p3rp for the technical detail, and press coverage of 21 and 22 October for the exploitation reports and NCSC guidance.