Two runtimes, three CVEs, one week
In the third week of October 2026, security researchers disclosed critical vulnerabilities in the two most widely deployed web runtimes on the planet. PHP2Shell is a pair of memory-corruption bugs (CVE-2026-109618 + CVE-2026-109619) in PHP's core request handling. Node2Shell (CVE-2026-111497) is an out-of-bounds write in Node.js's bundled Brotli decompressor.
Both grant unauthenticated remote code execution. Both bypass every layer of application-level security: framework guards, auth middleware, input validation — none of it matters, because the bug fires before your code ever runs.
fetch() from external HTTPS origins) grants RCE.
How PHP2Shell works
PHP2Shell is not a single bug — it's a two-stage attack chain. The first CVE (109618) gives the attacker memory corruption. The second (109619) makes that corruption persistent across requests via stale worker state, and adds memory disclosure as a bonus. Here's the sequence:
www-data or nginx.
Because CVE-2026-109619 abuses in-memory worker state, a graceful reload
(systemctl reload php8.4-fpm) that keeps existing workers alive leaves the
hole open. You must fully restart: systemctl restart php8.4-fpm.
Disabling session.upload_progress alone is insufficient (confirmed by PHP core
developer Gina Peter Banyard). Setting memory_limit=-1 trades an RCE mitigation for an OOM risk.
Application-level auth cannot block this (confirmed by Datadog). The only fix is patching PHP.
How Node2Shell works
CVE-2026-111497 is an out-of-bounds write in the Brotli decompression library bundled with Node.js. Brotli is a compression algorithm — like gzip but better — used by default in HTTPS responses.
When a Next.js server (or any Node.js HTTP client) makes a fetch() request to an external HTTPS
origin, it automatically accepts Content-Encoding: br (Brotli). If that origin is malicious or
compromised, it can serve a crafted Brotli stream that triggers the OOB write during decompression,
giving the attacker code execution on the server.
Node 20 reached end of life on 30 April 2026. The October patch fixed versions 22, 24, and 26 — Node 20 gets nothing, ever. If your production is on Node 20, you have a published RCE with no fix available. You must upgrade to at minimum Node 22.23.4.
How it unfolded
Am I affected?
PHP2Shell affects nearly every PHP version back to 5.4. The most dangerous configuration
is Apache mod_php; nginx + php-fpm is also vulnerable but carries lower inherent risk
because FPM isolates workers.
| Runtime | Fixed In | Status |
|---|---|---|
| PHP 8.4.x | 8.4.27 |
PATCH NOW |
| PHP 8.5.x | 8.5.12 |
PATCH NOW |
| PHP 8.3.x | 8.3.36 |
PATCH NOW |
| PHP 8.2.x | 8.2.35 |
PATCH NOW |
| PHP ≤ 8.1 | No fix | ⚠ EOL — UPGRADE |
| Node.js 26.x | 26.12.1 |
PATCH NOW |
| Node.js 24.x | 24.22.1 |
PATCH NOW |
| Node.js 22.x | 22.23.4 |
PATCH NOW |
| Node.js ≤ 20 | No fix ever | ⚠ EOL — NO FIX |
What to do right now
systemctl restart, not reload.
npm install → npm ci in deploy scriptsnpm install resolves fresh versions and rewrites the lockfile.
npm ci installs exactly the lockfile with integrity hashes and fails loudly if
package.json and the lockfile disagree. Add --ignore-scripts to disable
lifecycle scripts — the delivery vector for every current npm worm.
package-lock.json and composer.lock are the files that decide which code gets
installed. If you have a build-integrity system, add them to the protected list. This only works with the
npm ci change, since npm install needs to write the lockfile.
php -v or node -v falls below a pinned minimum.
"We forgot to patch" becomes a page instead of a silent condition.
This didn't happen in a vacuum
PHP2Shell and Node2Shell dropped in the same week as several npm supply-chain events that make the ecosystem context even more hostile:
An active npm worm spreading via lifecycle scripts in popular packages. When you run
npm install, any package's postinstall script runs automatically
as your user. npm 12 (8 July 2026) disabled lifecycle scripts by default — npm 10.x still runs them.
A flaw in npm's Trusted Publishing system exposed ~1,500 packages to takeover via abandoned GitHub repository names. If the original maintainer deleted their GitHub account and someone re-registered the same username, they could push malicious updates to the original npm package.
The popular @subql/common package was caught shipping a postinstall
payload — a real-world example of exactly the lifecycle-script attack vector that
--ignore-scripts blocks.
References
- PHP Issues Emergency Patches — The Hacker News
- PHP2Shell detection and remediation — Datadog Security Labs
- PHP2Shell analysis — Sansec
- PHP2Shell: Find, Mitigate, and Patch Now — Qualys
- Critical Vulnerabilities in PHP — CERT-EU
- PHP2Shell RCE Exploited — watchTowr
- Node2Shell Brotli RCE — Rescana
- Node.js October 2026 Security Fixes — TuxCare
- Node2Shell: Fixed Versions — Brandefense
- npm Trusted Publishing flaw — Help Net Security
- Shai-Hulud npm worm advisory — Singapore CSA