System Design Simple logoSystem Design Simple Follow on YouTube

Security

PHP2Shell and Node2Shell: why they were so dangerous, and how to patch your servers

Two remote code execution bugs fixed on the same day, exploited within a week. How the fixes were prepared, why they matter, and how to patch the right process.

PHP2Shell and Node2Shell: why they were so dangerous, and how to patch your servers: diagrams from the video

Drafted with AI assistance, then checked and edited by a human engineer · How we write

Short answer

PHP fixed CVE-2026-109618 and CVE-2026-109619 (CVSS 9.2) and Node.js fixed CVE-2026-111497 on 2026-10-21. A public reproduction followed in one day and CISA listed all three as exploited within six days. Update to PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35 and Node.js 22.23.4, 24.22.1 or 26.12.1, restart every worker, and check the version inside the running process.

Key facts
PHP2ShellCVE-2026-109618 and CVE-2026-109619, CVSS v4.0 9.2, fixed in 8.5.12 / 8.4.27 / 8.3.36 / 8.2.35
Node2ShellCVE-2026-111497, High, fixed in 22.23.4 / 24.22.1 / 26.12.1
Both fixed2026-10-21
CISA KEVPHP added 2026-10-26 (due 2026-10-29), Brotli added 2026-10-27 (due 2026-11-10)
Public reproduction1 day after the Node.js fix
Patch is done whenthe running worker reports the fixed version, not when the package is installed

On 21 October 2026, PHP and Node.js each shipped a fix for a remote code execution bug that can be reached before your application code runs. One day later a researcher reported a working reproduction, and within six days CISA listed all three CVEs as exploited. If you run PHP or Node.js, update, restart every worker, and confirm the version inside the process that serves traffic. Skipping either of the last two steps can leave the old build serving requests.

This guide is for protecting servers and applications you own or maintain. It contains no exploit details.

The facts in one table

PHP2ShellNode2Shell
CVEsCVE-2026-109618, CVE-2026-109619CVE-2026-111497 (plus 9 more in the same release)
SeverityCVSS v4.0 9.2 eachHigh (Node.js rating)
Fixed in8.5.12, 8.4.27, 8.3.36, 8.2.3522.23.4, 24.22.1, 26.12.1
Released2026-10-212026-10-21
CISA KEVadded 2026-10-26, federal due date 2026-10-29added 2026-10-27, due 2026-11-10
No upstream fix8.1 and older (backported patches exist, ask your vendor)20.x and older (end of life)

Why these two were so dangerous

Most bugs sit in application code, behind your login, validation and framework. These two sit in the shared machinery that every request passes through first, so your application never gets a chance to refuse the request.

PHP2Shell hits the worker model. PHP-FPM, mod_php, php-cgi, FrankenPHP and LiteSpeed keep worker processes alive and reuse them for request after request. According to php.net, CVE-2026-109618 leaves request data half initialized when out-of-memory handling is interrupted, and cleanup then treats it as valid. CVE-2026-109619 lets state owned by one request survive after its memory is freed, so a later request on the same worker can use it. Both break PHP's promise that each request starts clean. On mod_php, php.net says an unauthenticated request can trigger it before any application code runs.

Node2Shell sits in decompression, which happens at the edge of a service, on bytes from outside, before business logic sees them. Node.js says its bundled Brotli decoder treated an invalid block switch as "send me more input" instead of a format error, and kept appending input to an 8 byte internal buffer. Malformed Brotli data decompressed through node:zlib can then crash the process, leak memory, or lead to code execution.

How the official projects prepared the fix

The timestamps below (all UTC, from the projects' own release and commit history) show a well run release:

WhenWhat
14 Oct, 14:05Node.js pre-announces a security release: 7 days of notice
20 Octphp.net announces a critical release for the next day at 14:00: 1 day of notice
21 Oct, 10:38Node.js 22.23.4, 24.22.1 and 26.12.1 published
21 Oct, 10:43Official Node.js container images updated, 5 minutes later
21 Oct, 13:44Official PHP container images updated, before the announced 14:00
21 Oct, 14:00PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35 released; packagers had the patches in advance
21 Oct, 19:27Brotli publishes its own fixed releases, about 9 hours after Node.js shipped its patched copy

Downstream packagers received the patches in advance, so official images were ready within minutes. Node.js also fixed the Brotli code it bundles before Brotli itself cut a release, which meant software that bundles Brotli and waited for the upstream release started 9 hours behind.

The attacker's clock

Posts on X show how quickly things moved after the fix.

DatePostViews (read 2026-11-06)
20 OctFinder: "php2shell. Tomorrow is a helluva day."3,722,348
22 OctPre-auth RCE reproduction of the Node.js bug242,115
22 OctA training platform says it already teaches exploiting PHP2Shell122,323
23 OctPHP warns of another, unpatched issue (CVE-2026-115560, non-default setups)1,475,746
28 OctNode2Shell reported scanned in the wild95,358

Counted from the fix dates, reproduction came in 1 day, training material in 1 day, CISA's exploited list in 5 to 6 days and scanning in 7, so defenders had days to patch.

How to check and patch your servers

  1. Check the process that serves traffic, not your terminal. The php on your PATH and the PHP-FPM pool behind your web server can be different builds. Ask the worker itself:
# PHP: the binary your web server runs
php-fpm -v
# or drop a temporary page into the app, load it once, then delete it
echo '<?php echo PHP_VERSION;' > /var/www/html/v.php   # delete it right after
// Node.js: log this at startup, or expose it on an internal health route
console.log(process.versions.node);
  1. Update to the fixed release of your branch, then restart every worker. A package upgrade does nothing for a process that is still running the old binary. For containers that means rebuilding the image on a fixed tag and redeploying, not restarting the old container.

  2. Re-check inside the running process. If your PHP or Node.js came from an operating system package rather than the upstream project, the package may carry the fix without the upstream version number, so a version check can show "old" on a patched server. In that case your package vendor's advisory is the source of truth.

If you cannot update today, php.net lists temporary mitigations: set session.upload_progress.enabled=0 in php.ini or the FPM pool (not with ini_set or .user.ini) and restart every worker; on 8.5 also set fatal_error_backtraces=0; set memory_limit=-1 with php_admin_value; keep post_max_size small and limit request sizes at the proxy. Node.js gives no workaround for the Brotli bug, so there the only fix is the update.

What our lab showed

We ran both checks on our test machine, read-only, with no exploit.

Terminal output of our php2shell-check-lab: affected PHP build, three of four checks switched to safe by the php.net mitigations, sessions still working
The mitigations switched 3 of 4 checks to safe. The PHP build itself stayed affected.

The php.net settings took effect and normal sessions still worked, but the version row cannot change without an update, so the mitigations only buy a few days.

Terminal output of our node-brotli-check-lab: affected Node.js build, official fixed build verified by SHA-256, identical Brotli round trips on both
The official fixed build, verified by checksum, gave identical results on normal Brotli data.

Our own code had zero Brotli decompression calls in 295 source files, and the runtime was still an affected build. The vulnerable decoder ships inside the runtime, so never calling it yourself does not protect you.

Our take

The projects shipped in minutes and hours, and attackers needed days. The slowest step comes last, getting the fixed build into the process that is actually serving requests, and in many teams nobody owns it because "we updated the package" feels like done.

These habits close most of the gap. Watch pre-announcements, since Node.js gives about a week of notice and its next release is due on or after 11 November. Make the running version visible, on a health route or in startup logs, so you can check it in seconds. And treat runtime updates as more urgent than library updates: a bug in the runtime sits in front of every defense your code has.

What this does not prove

We tested one machine, read-only, and never sent malicious input. View counts are a snapshot from 6 November. Exploitation status comes from CISA's catalog and public reports, not from our own observation.

Frequently asked questions

We never decompress Brotli in our code. Is our Node.js service safe?

Not by that fact alone. The decoder ships inside Node.js, and HTTP clients and libraries call node:zlib for you. In our lab our own code had zero Brotli calls and the runtime was still an affected build. Update the runtime.

Are the PHP mitigations enough until we can update?

No. In our lab they switched 3 of 4 checks to safe, but the PHP build stayed affected. Treat them as a few days of cover while you schedule the update.

We updated. Why does our scanner still say vulnerable?

Either a worker was never restarted and still runs the old build, or the package you use applied the fix without changing the upstream version number. Check the version inside the running process, then your package vendor's own advisory.

Sources

  1. php.net: security releases 8.5.12, 8.4.27, 8.3.36, 8.2.35 (2026-10-21)php.net
  2. Node.js: Wednesday, October 21, 2026 Security Releasesnodejs.org
  3. Node.js: November 2026 security releases (pre-announcement)nodejs.org
  4. CISA: Known Exploited Vulnerabilities catalogcisa.gov
  5. google/brotli releases on GitHubgithub.com
  6. The Hacker News: Brotli decoder flaw behind the Node.js RCEthehackernews.com

phpnodejssecurityrceCVE-2026-109618CVE-2026-109619

System Design Simple logo
System Design Simple

Written by a software engineer and system architect with 10+ years of building production web, payment, ticketing and accounting systems in Japan and Nepal. About us