CRITICAL ADVISORY — OCTOBER 2026

PHP2Shell + Node2Shell

Unauthenticated remote code execution in the PHP and Node.js runtimes themselves. Your application code — however well-written — buys you nothing.

CVSS 9.2 23 October 2026 By Pushpendra Dev OverflowByte
SCROLL

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.

CVE-2026-109618
PHP2Shell — OOM Corruption
Memory corruption during out-of-memory handling in PHP-FPM. A crafted request triggers an OOM condition that corrupts internal state, giving the attacker write-what-where capabilities.
RCE
CVE-2026-109619
PHP2Shell — Stale Worker State
Stale global state in a PHP-FPM worker process. After the OOM corruption, subsequent requests to the same worker inherit the corrupted state — and leak passwords, tokens, and API keys from worker memory.
MEMORY DISCLOSURE
CVE-2026-111497
Node2Shell — Brotli OOB Write
Out-of-bounds write in Node.js's bundled Brotli decoder. Malicious Brotli-compressed content processed by the server (e.g., via fetch() from external HTTPS origins) grants RCE.
RCE
Part of 10-CVE release
Node.js TLS Bypasses
The same Node.js release also fixed two TLS certificate-hostname verification bypasses, which could allow MITM attacks against servers that validate upstream certificates.
TLS BYPASS

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:

⚡ Attack Flow — PHP2Shell
🗡️ ATTACKER
No auth needed
🌐 NGINX
Reverse proxy
⚙️ PHP-FPM
Worker process
← Secrets leak back: DB passwords, API keys, APP_KEY
1. Crafted HTTP Request
The attacker sends a specially crafted HTTP request to the target server. It doesn't need authentication, a login, or even a valid route. The request is designed to trigger PHP's internal memory allocation at a precise boundary.
2. Out-of-Memory Triggered
PHP's memory allocator hits an OOM condition. But instead of failing safely, the OOM handler itself has a memory corruption bug — it writes to freed memory, giving the attacker a write-what-where primitive.
3. CVE-2026-109618 — Code Execution
The corrupted memory is leveraged to overwrite function pointers or internal PHP structures, allowing the attacker to execute arbitrary code as the PHP-FPM worker process user — typically www-data or nginx.
4. CVE-2026-109619 — Worker State Persists
The corrupted global state survives within the worker process. Subsequent requests served by the same worker can read secrets from its memory space: database passwords, API keys, session tokens, APP_KEY. A graceful reload (SIGUSR2) does not fix this — workers must be fully restarted.
5. Extraction
The attacker now has RCE and memory disclosure. They can install backdoors, pivot to the database, exfiltrate data, or use the compromised server as a launchpad for lateral movement.
⚠️ Critical: Restart, Don't Reload

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.

🛑 What Doesn't Work

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.

⚡ Attack Surface — Node2Shell
🟢 NODE.JS
Next.js / SSR
🌐 HTTPS ORIGIN
External API / CDN
💣 MALICIOUS BR
Crafted Brotli stream
⚠️ Node 20 is End-of-Life

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

17 October 2026
First exploitation attempt observed
Amazon reports seeing exploitation attempts against PHP — four days before the patch existed.
20 October 2026
Embargo leaked
Details of PHP2Shell leak a day before the embargo date, turning a coordinated disclosure into a race.
21 October 2026
PHP patches released
PHP 8.4.27, 8.5.12, 8.3.36, 8.2.35 released with fixes for both CVEs.
21 October 2026
Datadog reproduces PHP2Shell
Datadog Security Labs publishes a full reproduction, confirms application-level auth is bypassed.
22 October 2026
Node.js 10-CVE release
Node.js releases patches for 22.23.4, 24.22.1, and 26.12.1, fixing CVE-2026-111497 and nine other CVEs.
22–23 October 2026
Fixed versions available
Both PHP and Node.js have patches. The window is closed — for anyone who applies them.

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
check your versions
$ php -v
PHP 8.4.26 (cli) — VULNERABLE
$ node -v
v20.20.2 — EOL, NO FIX EXISTS
$ php -v # after patching
PHP 8.4.27 (cli) — PATCHED ✓
$ node -v # after upgrading
v24.22.1 — PATCHED ✓

What to do right now

Patch and restart every worker
Restarting is not optional — CVE-2026-109619 abuses in-memory worker state, so a graceful reload that keeps workers alive leaves the hole open. Use systemctl restart, not reload.
Assume memory disclosure and rotate secrets
If your server was internet-facing and unpatched between October 17–21, treat everything in the PHP-FPM worker's reach as potentially read: database passwords, R2/S3 keys, APP_KEY, SENTRY_AUTH_TOKEN. Rotate them all.
Switch npm install → npm ci in deploy scripts
npm 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.
Protect your lockfiles
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.
Add runtime version checks to healthchecks
If your healthcheck already verifies build-file integrity, TLS expiry, and IP stability, add a version floor: alert when php -v or node -v falls below a pinned minimum. "We forgot to patch" becomes a page instead of a silent condition.
patch commands — PHP
# sudo apt update && sudo apt install --only-upgrade php8.4-fpm php8.4-cli
Setting up php8.4-fpm (8.4.27-1+ubuntu22.04) ...
# sudo systemctl restart php8.4-fpm # RESTART, not reload!
● php8.4-fpm.service: active (running)
# php -v | head -1
PHP 8.4.27 (cli) — PATCHED ✓
upgrade commands — Node.js
# nvm install 24.22.1 && nvm alias default 24.22.1
Downloading and installing node v24.22.1...
# npm rebuild && npm run build # rebuild .next — required for entropy-cache fix
✓ Build complete
# node -v
v24.22.1 — PATCHED ✓

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:

🪱 Shai-Hulud npm Worm

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.

📦 Trusted Publishing Flaw

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.

🚨 @subql/common Payload

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