All posts
October 22, 2026·4 min read·Long Nguyen · CTO, ERPFit
securityphphostingdockerdevops

Patching the October 2026 PHP core RCE: hosting panels vs containers, and the traps of obsolete versions

CVE-2026-109618/109619 are PHP core RCEs. How we inventoried, mitigated and patched across hosting panels and containers — and twelve traps that make a 'patched' report untrue.

Table of Contents

Summary

On 21 October 2026 PHP shipped an out-of-band fix for two core remote-code-execution bugs, CVE-2026-109618 and CVE-2026-109619. The vendor's CVSS v4.0 score is 9.2 (the 10.0 circulating online is not an official figure). The reported scope is PHP 5.4 and later, and only 8.2, 8.3, 8.4 and 8.5 receive upstream fixes. This post covers how we responded, and where a patch process that looks finished is not.

What was announced, and what wasn't

Early public information named no component, no preconditions and no indicators of compromise, and the php.net announcement did not mention either CVE id. Third-party analysis pointed at a heap use-after-free in session upload-progress handling (php_session_rfc1867_callback) as one of two paths. Don't wait for detail: start with what you control, the inventory.

Inventory first

  • PHP existed on only a small part of our estate. Knowing that early bounded the whole response.
  • A hosting panel installs every PHP branch it supports, end-of-life ones included, and runs an FPM service for each. Our shared-hosting servers were in that state, and most of those services served no site.

Trap 1: the panel runs every branch. Hosting panels install many branches so customers can choose. An unused FPM is unpatchable attack surface with zero value. Disable it; a service that is only stopped comes back at the next reboot.

Trap 2: dummy pools inflate the inventory. The panel writes a dummy pool per branch. Exclude it as well as www.conf, or every branch looks busy, which hides exactly the services you could switch off.

Trap 3: error text parsed as a version. docker exec <c> php -v on a container without PHP can print OCI runtime exec failed on stdout. Accept only a real N.N.N. These two traps inflated our first "EOL and serving" count almost tenfold.

Mitigate while waiting

Trap 4: a commented-out line means ON. ;session.upload_progress.enabled = On is commented out, so the compiled default (On) applies. We disabled it with a conf.d drop-in rather than editing php.ini, which upgrades may overwrite, and applied it to disabled branches too.

Trap 5: check the FPM binary, not the CLI. php -i reads the CLI ini set; FPM reads another. Some images ship no php.ini at all and run on compiled defaults.

Trap 6: "Permission denied" is not a diagnosis. Images that drop privileges refuse writes. Use docker exec -u root, and print the real error. This is mitigation, not remediation: it closes one path of two.

Patch, and prove it

Trap 7: apt aborts, the script stays green. php8.x-json is a transitional package with no candidate; it aborted the whole transaction while the wrapper still restarted FPM and printed a tidy report. Filter to installable packages, and gate on the binary's version, never on "restart ran".

Trap 8: the package repo keeps only the current version. Old .debs cannot be fetched later, so making them a hard prerequisite would have left the most important server unpatched. Hard-gate on a version record and a backup of /etc/php instead.

Trap 9: trigger on today's stable, not on "newer than installed". Otherwise a merely lagging host "detects the fix" and installs nothing new.

Trap 10: a canary gate that passes on absent evidence. No post-patch checks must mean unverified, not nothing broke.

Containers have their own traps

Trap 11: nginx caches the FPM container's IP. After recreating PHP containers the site returned File not found. — nginx had resolved fastcgi_pass once at load, and the old IP now belonged to a different FPM container. Docker DNS was correct throughout. Restart nginx in the same change, or use a resolver with a variable upstream.

Trap 12: the repo's Dockerfile had drifted. It lacked the two lines that install the redis extension present in the deployed image. Sync the source to reality first, then gate the new image on two-way extension parity, not on a successful build.

Panels vs containers

Panels patch a whole branch in one apt run but share FPM across customers and accumulate EOL branches. Containers isolate and roll back cheaply via image tags, but drift between source and what actually runs, and upstream images lose drop-ins on recreate. Neither model is immune.

Obsolete versions and sources

EOL branches (5.6–8.1) will never be fixed; moving branch is a project. An EOL framework (Laravel 9) needs a rehearsal on a warm standby, compared byte-for-byte against the old output, not a changelog read. Unlicensed premium plugins that cannot be updated will block the upgrade. Raise that with customers before the incident. And patch the standby too: it runs the same code.

LN
Long Nguyen
CTO, ERPFit

CTO at ERPFit. Hands-on builder of the technical infrastructure, the Craft tool suite, and the service platform for Vietnamese businesses.

Share:𝕏FBin