Patch PHP and Node.js this week: what broke and who's exposed
Patch PHP and Node.js this week. PHP's two critical bugs need no login, and Node's Brotli flaw can end in code execution. Here's which release you need and how to fence in the app account.
part 5 of 7 · Platform releases, explained from scratchthe ideaPatch first. Then cut what the app account can touch if it's hijacked.
after reading, you can- Tell which fixed release each server needs, so nobody guesses on patch night
- Spot the servers an attacker would probably hit first, and patch those today
- Scope each app's accounts tightly enough that the next runtime bug stops short of your data
PHP and Node.js sit under a huge number of websites and apps, and both patched serious holes on October 21, 2026. If your site, shop or app runs on either, get it updated this week. The rest of this post explains what broke, then what to do if you think someone already got in.
Your website can be well built and still exposed, because the hole sits in the engine underneath
Almost nobody writes a website from scratch. Your code runs on a runtime, the engine that actually executes it on the server. PHP runs a huge share of websites and content systems, and Node.js runs JavaScript on servers, mostly for APIs and web apps. Both engines had holes patched on October 21. With PHP's two critical ones, a stranger who never logs in can send specially built requests and get your server to run their commands. The flaw is in the engine, so every site on an unpatched version carries it, however carefully the site itself was built. How easy it is to exploit depends on the server setup, and Apache mod_php is the riskiest.
Think of an office building with a faulty lock model on its front doors. It doesn't matter how tidy each office is: anyone who learns the trick may be able to walk in. The lock maker has shipped a new lock, and every building using the old one needs it fitted. What a burglar can reach once inside depends on which keys are left lying around, which is why the second half of this post is about keys.
- Anyone whose website runs on PHP
- Teams running Node.js APIs or web apps
- Businesses whose site an agency runs
- IT, platform and security teams
- Owners of older PHP 8.1 or Node 20 systems
Why it matters to you
If an attacker uses one of these holes, they get to run commands on your web server as the account your website runs under. From there they can read whatever that account can read: customer records in the database, passwords stored in configuration files, payment or API keys. They can also change your site, plant malware for your visitors, or use the server to move deeper into your network.
Exploitation reports for the PHP bugs were mixed in the first two days. Rapid7 says it hasn't seen exploitation in the wild but has seen reports of attempts, and expects real attacks. Tanium cites watchTowr's honeypots and Cloudflare's CTO reporting early attacks. Rapid7 and SANS both say the public proofs of concept look unreliable. Rapid7 says each one works only on a particular mix of OS, PHP version and web server. When we checked CISA's Known Exploited Vulnerabilities catalog on October 23, neither PHP CVE was listed.
Unreliable exploits still deserve a fast patch. They usually improve fast, and an attacker only needs one setup that matches yours. Rapid7 says to treat this as an emergency update outside the normal patch cycle, and we'd do the same.
-
01 A crafted request arrives
It starts with what looks like an ordinary file upload. The attacker doesn't log in, and PHP starts unpacking the request before your own code ever sees it.
-
02 PHP gets into a broken state
The request pushes PHP to run out of memory halfway through setting up. Or it leaves data behind from an earlier request that should have been wiped, and the next request trips over it.
-
03 PHP trusts the broken data
Cleanup then reads memory that was never set up properly, or the next request on that worker uses memory that's already been freed. PHP doesn't notice.
-
04 The attacker's code runs
If the attacker gets the corruption right, they can steer it so their own commands run. Those commands run as your web server's account, with everything that account can open.
-
05 Whatever that account can reach is exposed
Database, secrets in config files, cloud keys. How bad it gets depends on what the account is allowed to touch.
What PHP and Node.js fixed
Both projects released fixed versions the same day; the table below shows the one for your line. The fixes close the exact memory mistakes these attacks use. We'd patch rather than spend the week debating workarounds, because none of them removes the risk.
The fixes that matter most
If PHP runs out of memory while a request is half set up, cleanup treats the half-built data as valid. That can give an unauthenticated attacker code execution as the web worker account. Critical, CVSS 9.2.
State owned by one request can outlive the memory behind it, and a later request on the same worker uses it before it's reset. Use-after-free and RCE. Critical, CVSS 9.2.
Node's Brotli decoder could write past the end of its buffer, and Node says that's enough for remote code execution. Rated high on 22.x, 24.x and 26.x.
Certificate hostname checks could be bypassed with fullwidth digits in an IP, with hosts that loosely resolve to an IP, and with non-canonical IPv4 names. Two rated high, one medium.
dns.resolve used to cut a name short at a NUL character. Node rates it high, and it's fixed on all three supported lines.
crypto.randomUUID() and randomInt() entropy caches were saved into user-land startup snapshots and single executable apps, so every process repeated the same values. Medium.
A wildcard over-grant on 26.x and an fs.realpathSync bypass on all lines. Both low, and only relevant if you run Node with the Permission Model turned on.
Node 24.22.1 moves to OpenSSL 3.5.9, undici 7.30.1 and c-ares 1.34.9, and picks up an upstream Brotli fix. The eMudhra intermediate CA compromise is handled in the CA store.
PHP's two bugs are the urgent ones. Both are reachable without logging in, and the PHP project says there are no complete mitigations: updating is the only safe choice. The advisory's documented routes to the OOM bug run through multipart form parsing. PHP's advisory describes several ways to force an allocation failure there, and once that happens, request shutdown reads memory that was never fully set up.
Which server setup you run decides how worried you should be. PHP rates Apache with mod_phpdefinitionThe Apache module that runs PHP inside the web server's own processes. PHP calls it the riskiest setup for these bugs, since the attack can fire before your app code even runs. as the highest risk, because an unauthenticated request can trigger the attack before any application code runs, and the advisory says full RCE was validated over HTTP against mod_php. For the OOM bug, PHP says no complete exploit has been shown on FPM yet. The stale-state bug is different. Its advisory reports a validated session-lifecycle RCE on FPM too, so FPM shops don't get to relax.
The stale-state bug needs two requests that land on the same worker. One leaves state behind and a later one acts on it. On PHP 8.5, fatal error backtraces are the strongest confirmed path. Session upload progress is another, and it's on by default and doesn't need your code to call session_start(). Timeouts and other fatal errors can provide the interruption, so an app that throws a lot of fatal errors gives an attacker more chances.
-
01 Compressed data comes in
A request body, or a reply from someone else's API, arrives Brotli-compressed (marked Content-Encoding: br).
-
02 Node unpacks it
Your app, or a library working for it, decompresses the data, sometimes without you noticing. That's the moment the bug can fire.
-
03 The decoder flaw triggers
The data is shaped to hit a bug in Node's Brotli decoder, which writes past the end of its buffer.
-
04 Code execution is possible
Node's advisory says the flaw can end in remote code execution and lists no workaround. Updating is the only fix you get.
On the Node side, the Brotli bug is the one we'd worry about first. Brotli is the compression behind the Content-Encoding: br header, and that header is harmless on its own. The risk is a vulnerable Node process decompressing Brotli data that an attacker controls. That could be a compressed request body, a response from someone else's API, or a library that decompresses on your behalf without you noticing. The Node advisory gives no workaround, only the fixed versions.
The three TLS bugs matter wherever Node connects to things by IP address and trusts the certificate check to stop a man in the middle. Service meshes, internal APIs called by IP and health checkers all do this. The snapshot bug is narrower but nasty if it applies: if you build single executable apps or startup snapshots, every copy was handing out the same "random" UUIDs and integers until you rebuild on a fixed version.
| Runtime line | Fixed in | Support status | If you're stuck |
|---|---|---|---|
| PHP 8.5 | 8.5.12 | Security support to Dec 31, 2029 | Update. Set fatal_error_backtraces=0 meanwhile |
| PHP 8.4 | 8.4.27 | Security support to Dec 31, 2028 | Update |
| PHP 8.3 | 8.3.36 | Security support to Dec 31, 2027 | Update |
| PHP 8.2 | 8.2.35 | Security support ends Dec 31, 2026 | Update now, then plan the move off 8.2 |
| PHP 8.1 and older | No upstream release | End of life | Vendor backport, upgrade, or PHP's source backports for 7.4, 8.0 and 8.1 |
| Node.js 26.x | 26.12.1 | Current, Active LTS from Oct 28, 2026 | Update |
| Node.js 24.x | 24.22.1 | LTS, Maintenance from Oct 20, 2026 | Update |
| Node.js 22.x | 22.23.4 | Maintenance LTS, end of life Apr 30, 2027 | Update, then plan 24 or 26 |
| Node.js 20.x and older | No fix | End of life | Upgrade to a supported line |
Dates to plan around
- Oct 20, 2026PHP pre-announces a critical release
- Oct 21, 2026PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35
Two critical RCE fixes. Distributions and LTS vendors had the patches in advance, and CERT-EU publishes advisory 2026-018.
- Oct 21, 2026Node.js 26.12.1, 24.22.1 and 22.23.4
Ten CVEs, highest severity high.
- Oct 28, 2026Node.js 26 becomes Active LTS
- Dec 31, 2026PHP 8.2 security support ends
No more fixes from the PHP project after that.
- Apr 30, 2027Node.js 22 end of life
What to do now
What to do now, by who you are
- Ask whoever runs your website or app, whether that's an agency, a host or your own IT team, if it runs on PHP or Node.js. Most can answer in a minute.
- Ask them to confirm in writing the version it runs and the date they updated it to a fixed release.
- If they can't confirm by the end of the week, treat the site as at risk and escalate.
- If your site runs on PHP 8.1 or older, plan an upgrade. PHP no longer ships releases for it, only source backports that someone has to build and test.
- Find every PHP and Node.js copy you run. Check tools like XAMPP and old container images too, because they often carry their own.
- Update PHP to 8.5.12, 8.4.27, 8.3.36 or 8.2.35, starting with Apache mod_php servers.
- Update Node.js to 26.12.1, 24.22.1 or 22.23.4, then rebuild and redeploy container images.
- Check what's actually running afterwards: node -v, php-fpm -v, or phpinfo() through the web server. php -v only covers the command line.
- Rebuild any Node single executable apps and startup snapshots on a fixed version. Old builds keep the bug baked in, however often you patch the servers.
- Put a ceiling on how big a request may get once it's unpacked, as general hygiene. It doesn't fix the Brotli bug, so update Node as well.
- Look at code paths that throw many fatal errors: each one gives the PHP stale-state attack another chance.
- Search web server and PHP-FPM logs for worker crashes and malformed multipart uploads.
- Turn on your WAF provider's rules for these CVEs as a stopgap while patching.
- Write down which account each PHP and Node app runs as. Then check what that account could open if someone took it over.
What to do this week
- 01Find every PHP and Node runtime
Check servers, containers, CI runners and vendor products that bundle their own runtime. Tanium points out Windows copies inside XAMPP, WampServer or Laragon that often don't show up as installed software, and container images where the version is baked into the tag. Where a product ships its own PHP or Node, the fix comes from that vendor.
- 02Patch PHP first, starting with mod_php
Move to 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or your distribution's patched package, since distributions got the fixes in advance. If you use the volunteer-run rpms.remirepo.net, PHP warned it wouldn't have updates on release day and suggests distribution packages or building from source.
- 03Patch Node.js to 26.12.1, 24.22.1 or 22.23.4
Rebuild every image, redeploy and restart the processes. Then check the version that's actually running in production, because an old container that never restarted is still exposed. Rebuild single executable apps and startup snapshots so they stop sharing random values.
- 04If you can't patch today, isolate and mitigate
Pull internet-facing PHP off the internet or put it behind tighter proxy rules. Set session.upload_progress.enabled=0 and restart every worker, and on 8.5 set fatal_error_backtraces=0. Lower post_max_size. PHP says these reduce exposure but don't make the server safe.
- 05Cap decompression in Node apps
Find code that decompresses request bodies or third-party responses. Set maxOutputLength on zlib's convenience methods and enforce request size limits at the proxy, so a hostile compressed payload can't balloon in memory.
- 06Cut the worker's permissions
Review the service accounts, database users, container users and cloud identities your PHP and Node apps run as. Remove admin rights nobody can justify, and stop running app workers as root.
- 07Move secrets out of the box
Get the passwords out of .env and config files. The app should fetch them from a vault, or use a managed identity, where Microsoft says developers never handle the credential at all. Then there's nothing on disk to steal.
- 08Put admin access behind PAM or PIM
Make privileged roles eligible-only, time-bound and approval-based, with session recording or activation logs. Entra PIM does this for Entra and Azure roles. A PAM vault does it for servers and databases.
- 09Watch for signs someone got in
Look for odd child processes from the web worker, new outbound connections, unexpected file changes, service accounts doing things they never do and unusual secret reads. If anything looks wrong, rotate the credentials that app could reach.
How do I patch it on my setup?
Knowing the fixed version is the easy bit. Getting it onto every machine is where patch week usually slips: a laptop on nvm, a container built from a tag nobody re-pulled, a Next.js app whose Node version sits in a hosting setting. Then there's the Apache box where the PHP command line reports one version and the web server runs another. Below is how each setup gets the fix, and how you prove it landed.
| Setup | How you patch | How you confirm |
|---|---|---|
| Node on a laptop, server or CI runner (nvm) | nvm install 24 (or 22 or 26), then nvm alias default 24 so new shells pick it up | node -v prints v24.22.1, v22.23.4 or v26.12.1 |
| Node from the nodejs.org installer | Download the .msi or .pkg for 24.22.1, 22.23.4 or 26.12.1 and run it | node -v |
| Node from Homebrew | brew install node@24. Node.js says Homebrew and its scripts aren't maintained by the Node project | node -v |
| Node in Docker (official node image) | Rebuild with docker build --pull on node:24, or pin node:24.22.1, then redeploy | docker exec <container> node -v on the running container |
| Next.js on Vercel | Pick 24.x or 22.x. Vercel rolls out minor and patch updates itself, for security fixes among others | node -v in the Build Command, or log process.version |
| Next.js self-hosted (next start or Docker) | Update Node on the host, or raise NODE_VERSION in the Dockerfile and rebuild | node -v on the machine or container that runs next start |
| PHP from Debian or Ubuntu packages | Run your normal system update. Ubuntu's notice says a standard update makes all the changes needed | Compare the package version from dpkg-query -W with the fixed one in the table below |
| PHP from Ondřej Surý's PPA | Update from the PPA. Launchpad labels it unsupported packages from an untrusted PPA, so treat it as third party | dpkg-query -W against the PPA's latest build |
| PHP on RHEL or from Remi | Apply Red Hat errata as they ship. Remi's blog says its fixed 8.2.35 to 8.5.12 RPMs are out for EL 8 and later | Red Hat's CVE page and bulletin, or Remi's release notice |
| PHP in Docker (official php image) | Rebuild on a fixed tag, e.g. php:8.3.36-apache or php:8.3.36-fpm, and redeploy | docker exec <container> php -v |
| PHP on Windows | Download the fixed build from php.net's Windows downloads | php -v, plus the web server's own check below |
Local installs: laptops, servers and CI runners
- 01Find out how each copy got there
Check where the node on the path lives: under ~/.nvm, in a Homebrew prefix or in a system directory. The patch route depends on the answer. nvm is installed per user and invoked per shell, so a fix in your shell does nothing for the service account or the CI runner.
- 02Move nvm users to the fixed line
nvm install 24 is the command nodejs.org gives for the 24 line. Check that node -v then shows 24.22.1. Run nvm alias default 24 so new shells use it, and nvm reinstall-packages with the old version if you rely on global packages. nvm leaves npm itself alone when it copies them.
- 03Re-run installers and package managers
Machines set up from the nodejs.org installer need the new installer run again. Homebrew machines need the node@ formula updated. Node.js says those third-party installs aren't maintained by the project, so check that the build you get is actually 24.22.1, 22.23.4 or 26.12.1.
- 04Patch PHP through the same channel it came from
Distro PHP gets the distro's fixed package, whatever php.net's latest number is. php.net's own Linux instructions use the distribution's binaries and warn that those carry patches the PHP project doesn't control. Don't mix a PPA or Remi build onto a host that runs distro PHP.
One thing trips up a lot of patch checks on Debian and Ubuntu. The distributions backport the fix into the PHP version they already ship, so the upstream number often stays put. Ubuntu 24.04 is fixed in 8.3.6-0ubuntu0.24.04.13. The upstream part of that version is still 8.3.6, well below PHP's own 8.3.36, so judge these hosts by the full package version.
| Release | Package | Fixed version | Notice |
|---|---|---|---|
| Debian 12 (bookworm) | php8.2 | 8.2.35-1~deb12u1 | DLA-4848-1 |
| Debian 13 (trixie) | php8.4 | 8.4.27-1~deb13u1 | DSA-6574-1 |
| Ubuntu 26.10 | php8.5 | 8.5.9-0ubuntu2.1 | USN-8948-1 |
| Ubuntu 26.04 LTS | php8.5 | 8.5.4-0ubuntu1.5 | USN-8948-1 |
| Ubuntu 24.04 LTS | php8.3 | 8.3.6-0ubuntu0.24.04.13 | USN-8948-1 |
| Ubuntu 22.04 LTS | php8.1 | 8.1.2-1ubuntu2.28 | USN-8948-1 |
# Node: the version this shell runs $ node -v # Debian or Ubuntu PHP: the package versions, not just php -v $ dpkg-query -W 'php8.*' 'libapache2-mod-php*' # FPM serves the pages, so check its binary too $ php-fpm -v # On Debian the binary has the version in its name, e.g. php-fpm8.2 # Containers: ask the one that's running. The image tag can be out of date $ docker exec <container> node -v $ docker exec <container> php -v
Containers and base images
- 01Rebuild with --pull
A tag like node:24 or php:8.3-apache moves when the image is updated, but your build cache doesn't. Docker says --pull makes the build check for a newer base image, while --no-cache only rebuilds the layers. Use --pull, then redeploy every service built on that base.
- 02Pin a fixed tag, or a digest you keep current
Tags such as node:24.22.1-slim and php:8.3.36-fpm exist for the fixed releases. A digest pin (image@sha256:...) gives you the exact same image on every build. Docker's docs warn that it also opts you out of automatic security fixes, so pair it with something that bumps it.
- 03Let a bot raise the FROM line
Docker's own guide suggests Dependabot with package-ecosystem docker, which opens pull requests that update base image tags and digests. Reviewing a PR beats someone remembering to edit a Dockerfile next time.
- 04Check the running container
docker exec runs a command inside a container that's already up, so docker exec <container> node -v tells you what production is running. A container that never restarted still runs the old binary, however new the image in the registry is.
- 05Rebuild images you didn't write
Red Hat says containers built on RHEL or UBI base images should be rebuilt once those base images are updated. The same goes for any vendor image that carries its own PHP or Node: you need their new image, then a redeploy.
Node behind Next.js: which Node is it really?
Next.js isn't a runtime of its own. Its server code runs on Node, the default runtime it uses to render your app. The Edge runtime, with fewer APIs, is what Proxy uses. So the Node fix applies to every Next.js app you build and serve yourself. Next.js's docs set the minimum at Node 20.9, and Node 20 is end of life with no fix, so a build that still passes on 20 isn't safe.
Confirm the Node version in builds and at runtime
- 01Check the build
Add node -v to the build command, or print it as the first step of the CI job. On Vercel, the project's Node.js Version setting picks the major, and an engines.node range in package.json such as 24.x overrides it.
- 02Check the runtime
Log process.version once when the server starts. That's what Vercel's own docs suggest, and it works anywhere. On a host or container running next start, node -v there is the answer.
- 03Fix copied Dockerfiles
Next.js's Docker example sets ARG NODE_VERSION=24.13.0-slim, with a comment telling you to keep it on the latest LTS. That's older than 24.22.1. If your Dockerfile started from that example, raise the ARG and rebuild every stage.
- 04Don't forget build-only machines
CI runners and build containers run Node too, often a different version from production. Patch them in the same change, since they unpack dependencies and talk to outside services.
Content-Encoding: br: where Brotli actually gets unpacked
Node's advisory names the trigger: Brotli data that an application decompresses with node:zlib. The decoder ships inside Node, so every library that decompresses through node:zlib uses the runtime's copy. Updating Node fixes all of those at once. What's worth knowing is where your app decompresses data it didn't create, because those paths are exposed until the update is everywhere.
| Path | Who decompresses | What to check | Besides updating |
|---|---|---|---|
| Server-side fetch() | Node's built-in fetch decodes a br response through zlib.createBrotliDecompress | Calls from your server to APIs or sites you don't control | Nothing Node documents. Update first |
| Express body-parser 2.x | All its parsers inflate br request bodies automatically, by default | Public endpoints that accept JSON, form or raw bodies | inflate: false rejects compressed bodies. Keep the default limit |
| Your own zlib calls | brotliDecompress and createBrotliDecompress | Code that unpacks uploads, queue messages or cached blobs | maxOutputLength caps output size on the convenience methods. It's no fix |
| Next.js compress option | Nobody: Next.js gzips its responses, which is compression going out | Nothing for this bug | Leave it alone |
LAMP stacks: mod_php or PHP-FPM, and why php -v isn't enough
On a classic Linux, Apache, MySQL and PHP box, the PHP that serves your pages is a different binary from the one you run in a terminal. Ubuntu's notice lists libapache2-mod-php8.x, php8.x-fpm, php8.x-cli and php8.x-cgi as separate packages. php -v reports the CLI, and it says (cli) right in its first line. Update every package, restart the service that holds PHP in memory, and then check that service.
- PHP runs inside Apache's own processes
- Restart Apache after the update. PHP's manual says a restart is all a new module needs
- Most distro mod_php builds aren't thread safe, so Apache stays on prefork
- PHP's manual now says not to use mod_php for new installs
- PHP rates it the riskiest setup for these bugs
- PHP runs in its own pool of workers. Apache or nginx just hands requests over
- Restart php-fpm after the update, or send SIGUSR2, which reloads the workers and the binary
- php-fpm -v prints the FPM binary's version
- Red Hat says standard RHEL deployments use PHP-FPM. On RHEL, that's usually the service you restart
- Still exposed: the stale-state bug was shown working on FPM
How do we find the old Node and PHP we missed, and keep track?
Patching once is the easy week. The harder part is knowing, a month from now, which images and hosts still carry a vulnerable runtime. Open-source scanners, plus GitHub's Dependabot if your code lives there, can find most of them and feed one dashboard. Know the split before you trust a clean report: some tools check your app's packages, and only some can see the runtime itself.
npm audit sends a description of your project's dependencies to the registry and reports known advisories against them. It doesn't audit the Node binary. composer audit does the same for Composer packages. Composer's separate check-platform-reqs only compares your PHP version with what your packages need, so it won't flag a vulnerable PHP. Both are still worth running in CI, since app dependencies get patched on their own schedule.
| Tool | What it looks at | Sees the runtime? | How it feeds a dashboard |
|---|---|---|---|
| npm audit | Your project's npm dependency tree | No. Packages only | npm audit --json. --audit-level sets which severity fails the build |
| composer audit | Installed or locked Composer packages against Packagist advisories | No. Packages only | --format json. Exit code 1 when a package matches |
| OSV-Scanner | Lockfiles (package-lock.json, composer.lock and more) and container images, against OSV.dev | Debian, Ubuntu and Alpine packages in images, so distro PHP packages are in scope | --format json, sarif, html or cyclonedx-1-5. Exit code 1 on findings |
| Trivy | Container images, filesystems, git repos, VM images and Kubernetes | OS packages (apt, apk, rpm) and npm or Composer lockfiles | --format json, sarif, cyclonedx or a template |
| Syft with Grype | Syft builds an SBOM of an image or folder. Grype scans images, folders or that SBOM | Syft's binary classifier finds files named node and reads the Node version from inside the binary | Syft writes CycloneDX or SPDX. Grype reads the SBOM |
| Dependabot | GitHub pull requests for the npm, composer and docker ecosystems | It bumps the node: and php: tags in your FROM lines | Version update PRs for docker. Security and version update PRs for npm and composer |
| OWASP Dependency-Track | CycloneDX SBOMs you upload, tracked across every project | Whatever the SBOM lists | This is the dashboard: portfolio metrics, NVD, GitHub and OSV data, alerts to Slack, Teams, Jira or webhooks |
A dashboard you can stand up this week
- 01Make an SBOM for every image you build
Add syft <image> -o cyclonedx-json, or trivy image --format cyclonedx --output sbom.json <image>, to each pipeline. Keep the SBOM next to the image. Syft is the one to use if you need the node binary listed, because its classifier reads the version out of the binary itself.
- 02Send the SBOMs to Dependency-Track
Upload those SBOMs to Dependency-Track from the same pipeline step. It's built API-first for CI/CD and tracks every component across all your projects and versions. That gives the security team one place to look when the next advisory drops.
- 03Fail builds on what matters
OSV-Scanner and npm audit return a non-zero exit code when they find something, and grype --fail-on high sets one from a severity up. Start with high and critical only, so the first week doesn't drown in old findings nobody will fix.
- 04Keep pins moving
Turn on Dependabot for the docker, npm and composer ecosystems. Pinned digests stay safe to use because the bump arrives as a pull request someone reviews, instead of a pin that silently freezes out security fixes.
- 05Check what's running, not only what's built
Scanners see images and lockfiles. They don't see a container that never restarted or a server someone patched by hand. Add a small scheduled job that runs node -v, php-fpm -v or docker exec checks on live hosts and posts the output next to the dashboard.
Setting this up across many pipelines is mostly plumbing, and that's work our DevSecOps practice does: SBOMs, dependency and container scanning, and signed builds. If your containers run on OpenShift, the OpenShift page covers the platform and you can bring in OpenShift engineers. Or start with an assessment of where your runtimes stand today.
Harden so the next one hurts less
Both advisories describe the attacker landing as the web worker account. That's the part an identity team can do something about. If the account behind your PHP or Node app can read every database, every secret in the vault and half the cloud subscription, a single runtime bug turns into a much bigger incident. If it can only reach what that one app needs, the same bug stays a contained, ugly afternoon.
Before: one compromised worker reaches everything
The app runs as a broad service account with a database admin login and secrets in its config file.
After: patched, scoped and watched
A fixed runtime, a narrow workload identity, secrets fetched at run time and admin rights only on request.
- The worker runs as root or a local admin
- The app's database login can drop tables and read every schema
- Passwords and API keys sit in .env or config files, readable by anyone who lands on the box
- The cloud identity holds a broad role across the subscription, so it can touch far more than the app ever uses
- Admin rights are standing, so a stolen session works right away
- The worker runs as an unprivileged account
- The database login reaches only that app's tables
- Secrets come from a vault or managed identity when the app needs them, so nothing useful sits on disk
- The cloud identity can only touch the handful of resources this one app uses
- Nobody holds admin rights by default. Someone asks, gets approved, and the access expires
Patching closes these particular holes. Least privilege is what still protects you from the next one you haven't heard about yet, so we'd do both in the same change window. Here's the order we'd work in.
If you've been breached
If you think you've been breached
Patching closes the door, but someone may have come in before you did. If your logs show the signs below, or the server starts behaving oddly, work through these phases in order and don't skip ahead to the rebuild.
- 01Containfirst hour
- Pull the affected server out of public traffic, or put a maintenance page in front of it. Customers can wait an hour while you look.
- Don't wipe it yet: keep a copy of the disk and memory if you can, and save all logs.
- Block outbound traffic from the server, apart from what you need for the investigation. That cuts off this server's own channel out.
- If the web account could read cloud or API keys, revoke or rotate those now. Stolen keys work from anywhere, not only from this server.
- 02Investigatefirst day
- Look for the public exploit's pattern: multipart POSTs with very long filenames, a PHPSESSID cookie and a PHP_SESSION_UPLOAD_PROGRESS field.
- Check for PHP worker crashes and for files in the web root that changed when nobody deployed anything. New scheduled tasks or user accounts are a bad sign too.
- Work out what the web account could actually open: which databases, which config files, which cloud keys. That's your worst case.
- 03Remove and rebuilddays 1 to 3
- Rebuild the server or container from a clean image on a fixed PHP or Node version, rather than cleaning it in place.
- Rotate every password, key and token the web account could read, including database logins and cloud keys.
- 04Recoverdays 2 to 5
- Bring the rebuilt service back with tighter permissions, a scoped database login and secrets in a vault.
- Watch logs closely for a few weeks for repeat attempts from the same sources.
- 05Report and learnas required
- If personal data may have been read, check whether you have to tell customers, a regulator or your cyber insurer. Deadlines can be short, so ask early.
- Write down how they got in and what stopped them going further. Then go and fix the same gaps on your other servers too.
For the log search, the most useful public description is SANS's write-up of the proof of concept. Tanium also suggests looking for worker crashes and malformed multipart uploads in web server and PHP-FPM logs. WAF rules from Cloudflare and others help while you patch, but they don't remove the bug.
What this could look like: three typical setups
What this could look like: three typical setups
- the setup
- A local firm's website runs on PHP at a hosting company. Nobody at the firm knows which PHP version it's on, and nobody has asked.
- what happens
- The host is still on an old PHP build. Someone sends crafted uploads until one works, their code runs on the server, and the site's database login comes with it. Enquiries and orders are readable from there.
- what to do
- Ask the host to confirm the PHP version and update date in writing. If they can't, start moving hosts. If anything on the site looks odd, change the admin and database passwords today.
- the setup
- A Node.js 22 service in Kubernetes takes compressed data from partners all day. It runs with a workload identity that can only read its own tables, and that matters a lot.
- what happens
- A malicious Brotli body could still trigger the decoder flaw in one container. The scoped identity is what limits the damage: the attacker gets that service's data, not the rest of the company's.
- what to do
- Rebuild the image on Node 22.23.4, redeploy and confirm the running version. Keep the identity scoped. Size caps on decompression are fine to add, but they won't stop this bug.
- the setup
- A legacy intranet app runs PHP 8.1 under Apache mod_php. Its database login is an admin account, and passwords sit in a config file.
- what happens
- There's no upstream release for 8.1, and mod_php is the setup PHP rates riskiest. A successful attack runs as the web account, and the admin login exposes every database schema.
- what to do
- Apply a vendor backport or upgrade to a supported branch. Until then, keep it off the internet and behind a WAF. Move secrets to a vault and give the app a login that reaches only its own tables.
We work on the identity side of this. That usually means scoping the service accounts and workload identities apps run as, moving secrets into a vault and putting admin rights behind just-in-timedefinitionAdmin rights that exist only when someone asks for them, for a set time, usually with approval and logging, instead of sitting on the account all the time. access. If you want to know what a compromised app server in your estate could actually reach, our privileged access management page explains how we work. The CyberArk and HashiCorp Vault pages cover the platforms, you can bring in PAM engineers, or start with an assessment.
What to take from this
- 01
Move PHP to 8.5.12, 8.4.27, 8.3.36 or 8.2.35, depending on your branch. No setting or workaround fully covers these bugs, so the upgrade is the fix.
- 02
Update Node.js to 26.12.1, 24.22.1 or 22.23.4, rebuild images and confirm what's running.
- 03
Do the mod_php hosts first. PHP itself says they're the riskiest setup.
- 04
Assume the next runtime bug lands as the worker account, and cut what that account can reach.
- 05
PHP 8.2 loses security fixes at year end and Node 22 next April. Start those upgrades now.
- 06
Check the runtime that serves requests, not the command line, and track it with an SBOM and a dashboard so the next patch week starts from a list.
- How Cohash delivers CyberArk: scope, roles and architecture
- How Cohash delivers HashiCorp Vault: scope, roles and architecture
- How Cohash delivers Microsoft Entra ID: scope, roles and architecture
- Hire vetted CyberArk engineers
- Privileged access management
- Book a 30-minute working session with a senior engineer
01Is my website affected?
Look at what it runs on first. If it's PHP or Node.js and nobody has touched it since October 21, 2026, treat it as exposed. Whoever looks after the site can check the version in a few minutes, and the fixed versions are in the table above.
02Our hosting company looks after PHP. Do we still need to do anything?
Yes, one thing: ask them to confirm the version your site runs and when they updated it. Many hosts patch quickly, but you only know once they tell you.
03We're on PHP 8.3. Which release fixes CVE-2026-109618 and CVE-2026-109619?
On 8.3 you want 8.3.36. The other fixed releases are 8.5.12, 8.4.27 and 8.2.35, all released October 21, 2026. PHP 8.1 and older get no upstream release. Use a vendor backport, PHP's source backports for 7.4, 8.0 and 8.1, or upgrade to a supported branch.
04Can we mitigate the PHP bugs without patching yet?
Only partial ones. Turning off session upload progress, setting fatal_error_backtraces=0 on 8.5, an unoverridable memory_limit=-1 and a low post_max_size all cut down specific attack paths. PHP says none of them makes a server safe, so treat them as a bridge to the update.
05Are the PHP bugs being exploited?
Reports were mixed two days in. Rapid7 hadn't seen in-the-wild exploitation but had reports of attempts, while watchTowr and Cloudflare reported early attacks. Public proofs of concept look unreliable. Neither CVE was in CISA's KEV catalog when we checked on October 23, 2026.
06Our Node services run 22 and 24. Which versions do we move to?
Move 22 to 22.23.4 and 24 to 24.22.1. On 26 it's 26.12.1. Node 20 and older are end of life and don't get a fix. Node says end-of-life versions are always affected when a security release goes out, so plan an upgrade to a supported line.
07We see Content-Encoding: br in our traffic. Are we vulnerable?
Not because of the header itself. It just marks Brotli-compressed content. The risk is a vulnerable Node runtime decompressing Brotli data an attacker controls, such as a request body or a third-party API response. Patch, then cap decompressed output sizes.
08Is PHP-FPM safe from these bugs?
No. For the OOM bug, PHP says no complete FPM exploit has been shown, but FPM is likely susceptible. For the stale-state bug, a session-lifecycle RCE was validated on FPM. mod_php is the highest risk, but every SAPI serving requests should be updated.
09If we patch quickly, do we still need to bother with least privilege?
Both PHP advisories describe code running as the web worker account. Whatever that account can reach, the attacker can reach too. Scoped database logins, secrets from a vault or managed identity, and admin rights granted only on request keep one bad app from turning into a wider breach.
10php -v says we're patched. Does that mean our website is?
Don't stop at php -v, which reports the command-line PHP. On PHP-FPM run php-fpm -v, and on Apache mod_php read a short-lived phpinfo() page. On Debian or Ubuntu, compare the package version with the distro's notice, because backported fixes keep the old upstream number.
Three questions, about a minute
Pick an answer and we'll tell you if it's right, and why it is or isn't. The score stays in this browser.
-
Q1You run PHP 8.3 under Apache mod_php and can't patch until the weekend. What's the right call today?
show the answer
C. Isolate the host or tighten proxy rules, apply PHP's INI mitigations, and patch as soon as possible mod_php is PHP's highest-risk setup. The INI settings and isolation reduce exposure, but only the update fixes it. Hiding errors does nothing, and RLIMIT_AS can cause the allocation failure the bug needs.
-
Q2A Node 24 service decompresses gzip and Brotli request bodies from partners. What does it need?
show the answer
A. Update to 24.22.1, then cap decompressed size with maxOutputLength and proxy limits The Brotli bug sits in the decoder, so the fix is the patched runtime. Size limits keep a hostile payload contained. Node 20 is end of life and gets no fix at all.
-
Q3Your PHP app's database login has full admin rights 'to keep things simple'. Why does that matter this week?
show the answer
B. An attacker with code execution as the worker inherits that login, and with it every table The advisories put the attacker in as the web worker account. Whatever that app can reach, they can reach, so a scoped login limits the damage of this and the next bug.