senn-techsenn-tech
Security
Security2026-10-21· By Franz Senn

Two PHP RCE flaws on 21 October: what we measured on our own stack

PHP logo
PHP sits behind our group CMS, behind three synchronisation jobs and until recently behind the web analytics. On 21 October two flaws were fixed in it that an attacker can exploit without any credentials. (Quelle: Cvvaecc / Wikimedia, CC BY 4.0)

At 12:59 UTC the PHP project advisory was online, one hour ahead of the announced schedule according to independent reports. CERT-EU and the NSA followed within hours. PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35 were released, closing two vulnerabilities rated CVSS v4.0 9.2. We measured on that day instead of translating other people's words, and the most interesting finding is not in the advisory but in our container start times.

What the two flaws actually do

CVE-2026-109618 sits in the cleanup after memory exhaustion: if request processing aborts because memory ran out, the teardown walks through a state that no longer holds, and that turns into code execution. CVE-2026-109619 lets request state outlive the end of its own validity, so the next request works with wrong global data. Neither needs a login, and both start where any browser starts: at a request.

The disclosure group, meaning the PHP project together with OpenAI Codex Security, the firm Calif and Wiz Research, states on its shared page php2shell.com that the code in question has been shipping since PHP 5.4. That is more than ten years of exposure. By our own checking of secondary sources PHP sits at 71.8 percent of all websites whose server-side language is known. At that width, few operators rebuild their images in a single night.

What we switched off before the new image ran

Both flaws grow with what a request is allowed to bring along. The first is reachable through the path where PHP reserves and releases memory. On the affected host we found 512 MB of memory per script, 512 MB request body and 1 GB per upload, with the upload progress recording switched on. That recording creates session state for every chunk of an upload, and session state is precisely what the first flaw cleans up incorrectly after an abort.

We moved to a 32 MB request body and a 24 MB upload, switched the progress recording off, and the memory limit per script now sits at unlimited (added 22 Oct). A limit sounds like protection, and this limit is the trigger: once a request crosses it, PHP leaves the running function by jump instead of by error, and the cleanup is left holding a half-built state, which is where the first flaw comes from. The advisory therefore names unlimited as a partial measure and warns that the process then runs into system boundaries instead. Replaced is nothing by this. One further lesson: a size limit inside PHP is no boundary on its own, because a multipart request without a usable declared length passes the check. The frontend has to cap it.

Request limits on the affected web host, before and after 21 Oct 2026Request body before512 · megabytesRequest body after32 · megabytesUpload before1024 · megabytesUpload after24 · megabytes01100
Measured in the effective PHP settings of the host. (Quelle: Own measurement on 21 Oct 2026)

Nothing was purchased for this: no appliance and no virtual patching service that could have covered a flaw inside the language itself. One large WAF operator had its rules live nine hours before the disclosure, but we do not stand behind such an operator. Closed on that day were the admin panel and the internal API routes, address gates in our own edge, there before and now carried through to the last remaining routes.

On patch day the bottleneck was the image

We run the group CMS in an image we build ourselves. When the fixed versions became available, the official Docker catalogue initially carried only RC1 tags for 8.4.27 and 8.5.12; the finished Apache images arrived, per registry timestamps, at 16:48 UTC for 8.5.12-apache and 17:31 UTC for 8.4.27-apache. The Alpine package still stood at 8.5.11 then. Waiting for the official image would have left our production systems sitting on a version carrying the flaw over lunch.

So we built our own at 17:07 UTC, on the webdevops layer with the official FPM Alpine branch on top, because the official PHP image compiles the language from source. The 43 minutes between our build and the official Apache image are no heroics. They are the difference between a measured state and a hopeful one.

Order of our check on 21 OctoberRead advisory, noteversionsfour releases, two CVEs,CVSS 9.2, no completemitigationMeasure our ownexposureresolve hostnames, probeendpoints, read PHP insidethe containerBuild and swap theimageown image 8.5.12 at 17:07UTC, then a restart and noreloadClose the accesssurfacepanel and API routes onlyfrom our own networks, lowerupload limitsProve the restartcontainer start timesagainst the build time,never against the tag
Five steps, and the last one is missing from most reports. (Quelle: Own measurement on 21 Oct 2026)

How large our PHP surface really was

We resolved all 207 hostnames in our inventory publicly, 198 of them answer from outside, and probed those over HTTP. Six endpoints deliver content, two show a PHP in them. That sounds calming until we look at the same host from the other side: our English product site runs on PHP as well and hands out no marker that gives it away. Neither header, cookie path nor version line hints at it, and the instance does not vanish because of that.

From inventory to real PHP surface, own measurement on 21 Oct 2026Hostnames in inventory207 · our own listresolve publicly198 · DNS checked from outsideendpoints with content6 · HTTP probewith a PHP marker2 · a third one has none0230
The jump from 198 to 6 is the real gain of the exercise: attack surface is what answers, and configuration alone tells you nothing. (Quelle: Own measurement on 21 Oct 2026)

By the evening we counted eight PHP instances in operation: one on 8.5.12 and three on 8.4.27 on the group host, two more on 8.5.12 on our Hetzner machine, a feed reader on 8.5.11 behind an address gate and a domain controller with mod_php 8.3.6 that we cannot measure. Two of them were synchronisation jobs, one a tooling endpoint service, none a site. Which is why they would have slipped through a website-only count.

The restart appears in no version line

The fast way to check a version leads into the container and into the PHP command line. That result proves the installed file and very little about the running worker. The advisory names the step explicitly: Apache, PHP-FPM and long-running workers must be restarted after the update.

We proved the restart through container start times instead. Our image ran at 17:07 UTC, the affected containers started at 18:25:11, 20:10:16 and 20:28:10 UTC. All three sit after the build. A second reason is missing from almost every headline: CVE-2026-109619 can read server memory from other requests, including passwords, tokens, API keys and certificates, without an attacker executing any code. An unpatched host leaks. A patched host without a restart leaks as well.

So a rotation list runs after the swap on our side, and we check start times whenever somebody tells us the update was already applied.

What changes when a model finds the bug

The practical difference to an ordinary vulnerability is the window: a coordinated release together with the update, no public exploit with it. The knowledge is readable by everyone now anyway. The question whether an open model could have done the same work misses the point, and the finders write so themselves: models do not start at twenty-year-old exploitation primitives on their own, a person has to set that direction. Their researcher pointed them deliberately at the old PHP sites from 2004, 2020 and 2024; the models sped the work up, the finding stayed his.

Our own five-GPU setup is an inference lane and no vulnerability scanner, and none of these models ran against our estate. What remains is an assumption about time: for a flaw in a language that so many businesses run at once, we have to assume an exploit takes hours and not weeks. That moves our sequence, the tooling stays the same.

What is still open

Honest coverage is a fraction. Two public PHP endpoints are on the fixed lines and additionally gated by address at the panel and at the API paths, request and upload limits now stand at 32 MB and 24 MB, the memory limit per script is lifted and the progress recording is off, because one of the two flaws becomes reachable sooner through expensive request bodies. A feed reader still runs 8.5.11, reachable only from the management network, and swaps at the next scheduled image. The domain controller with mod_php stays unverified because we have no access route, and the Windows fleet is not in this count. Anyone expecting full coverage from us gets a list with gaps instead.

One system we took out completely on that day: our Matomo based web analytics, which with mod_php 8.4.25 and a publicly reachable tracker was the most awkward candidate of the set. The analytics now run without a PHP layer, and the article on the migration describes the rebuild and the history import. For the question of how to deal with affected software, that is the more practical answer than any nightly panic.

Further Reading

Questions?
Do we have to take every PHP system offline tonight?+

Not in our environment, because we moved to the fixed release lines the same day and the publicly reachable access paths are additionally closed by address gates. The recommendation to take systems offline addresses operators who cannot patch. The PHP project itself states that no complete mitigation exists and that updating remains the only safe path. So anyone who can patch, patches. Anyone who cannot, removes the system, separates the network or puts a maintenance page in front of it.

Is updating to 8.5.12 or 8.4.27 enough?+

No, and this is where most measurements stop too early. Apache, PHP-FPM and every long-running worker have to be restarted after the update, otherwise the vulnerable process keeps running with its old state. The second flaw can also read server memory belonging to other requests, including passwords, authentication tokens, API keys and certificates, without any code execution. A host that is patched but not restarted kept serving secrets during that window. That is why key rotation sits on our closing list.

Does this only affect our websites?+

No. Any PHP instance that serves web requests is in scope, including internal tools and synchronisation jobs with an HTTP endpoint. Three containers on 8.4.x in our count were byproducts of an image that nobody had on the radar as a website. Those are the awkward ones: rarely behind a public entrance, rarely on the patch plan.