Node2Shell (CVE-2026-111497): Brotli Flaw in Node.js Has Been Attacked, Check Your Version

By , Tim Engineering APIPedia · Published · Updated · 10 min read

The Brotli CVSS 9.2 loophole entered CISA KEV and has been exploited. Here's a safe version of Node.js, how to check, Express 5 mitigations, and attack signs.

APIPedia Cover: Node2Shell, Brotli loophole in Node.js has been attacked. CVE-2026-111497 fact card, Brotli 1.0.3 to 1.2.0, CVSS 4.0 9.2, exploited status, CISA deadline November 10, 2026, Secure Node.js 22.23.4, 24.22.1, 26.12.1

Node2Shell (CVE-2026-111497) is a buffer overflow vulnerability in Google's Brotli decoder that carries over to Node.js, Bun, Python packages, PHP on Windows, and .NET. CISA included it in the KEV catalog on October 27, 2026 because it had been exploited. If your API runs on Express 5 or uses fetch() on the server, check today's version of Node.js.

This gap is rated 9.2 (critical) in CVSS 4.0 by Google as CNA. The Node.js Advisory itself labels it "high". The worst impact is remote code execution (RCE), but the most common in public testing is a Node.js process crash. For API servers, repeated crashes also mean downtime.

What exactly is Node2Shell?

The root of the problem is in file c/dec/decode.c in the Brotli library versions 1.0.3 to 1.2.0. According to Sysdig's analysis, the specially assembled Brotli stream declares only one type of block, then requests a block switch. Unpatched decoders ignore errors from that step (CWE-252, unchecked return value) and write data outside the buffer bounds (CWE-787, out-of-bounds write).

The upstream fix is ​​in Brotli commit 0ed4268a: the decoder immediately fails if there is a block switch even though only one block type is declared. Node.js also added a new error constant, BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH.

This flaw is known by several names: Node2Shell, HTTP2Shell, and Broccoli2Shell. The name is similar to PHP2Shell which we discussed last week, and according to Cyber ​​Security News one of the attacker IPs seen was indeed related to the PHP2Shell exploit.

Quick summary

ItemsDetails
CVECVE-2026-111497 (Node2Shell / HTTP2Shell)
ComponentGoogle Brotli Decoder 1.0.3–1.2.0 ( c/dec/decode.c)
TypeOut-of-bounds write (CWE-787) + unchecked return value (CWE-252)
ScoreCVSS 4.0: 9.2 (Google). Node.js: "high"
VectorNetwork, no login, no user interaction, there are attack prerequisites
ImpactCrash (DoS), memory leak, possible RCE
StatusEnter CISA KEV Oct 27, 2026, federal deadline Nov 10, 2026
Node.js is safe22.23.4, 24.22.1, 26.12.1
Node.js without patch20.x, 23.x, 25.x (already EOL)

What's the chronology?

The gap between the patch and the first visible attack was only five days. The first public proof-of-concept repo even appeared about five hours after Node.js released the fix.

Node2Shell Timeline CVE-2026-111497: October 14 Node.js pre-announcement, October 21 Node.js 22.23.4, 24.22.1, 26.12.1 and Brotli 1.0.10, 1.1.1, 1.2.1 releases as well as first public PoC, October 22 CVE published with CVSS 9.2, October 25 .NET fix from Microsoft, October 26 ELLIO spots exploit in the wild, October 27 CISA KEV entry, November 10, 2026 deadline

  • October 21, 10:38 UTC: Node.js releases 22.23.4, 24.22.1, and 26.12.1. Around 19:27 UTC, the Brotli project released 1.0.10, 1.1.1, and 1.2.1.
  • October 22: CVE is published with a score of 9.2. Imperva Red Team published research that successfully ran the code on the official Node.js 24.22.0 (Linux arm64) image, after several attempts.
  • October 25: Microsoft releases .NET, Visual Studio, and PowerShell fixes outside of its Patch Tuesday schedule.
  • October 26, 15:08 UTC: ELLIO observed one exploit request measuring 23,069,983 bytes from IP 94.154.43.7.
  • October 27: CISA adds it to KEV. The entry did not say who the perpetrator was or whether ransomware was used.

How real is the RCE risk?

In this section the sources differ, so we write as is.

  • Imperva and Vercel report code execution on certain unpatched builds. Vercel also reports memory leaks in 22.x, 24.x, and 26.x.
  • ELLIO's test on x86-64 produced shells on four targets, but against one vulnerable clean target its exploit chain produced zero shells out of 50 tries and only crashed the process. Most of the 400 attempts to Express ended with the worker crashing.
  • Memory offset for Node.js 24.22.0 fails to be used in 22.23.3 and 26.12.0 in 10,000 tries. This means the RCE exploit relies heavily on the exact same build.
  • Another researcher believes the real impact is DoS. Claims of exploits from third parties have also not been confirmed by Node.js or Google.

The practical conclusion is the same: patch now. Repeated crashes on the Express server are enough to bring down the service, and if RCE is successful, secrets in environment variables and cloud tokens in containers are also threatened.

Who is affected?

Node.js only decodes Brotli when application code or a library does so with external data. The default Server http does not decompress the body automatically. So the main question: does your code decompress Brotli data that an attacker can control?

Node2Shell attack surface diagram: attacker sends data with Content-Encoding br, parser calls Brotli decoder, invalid block switch causes write out of buffer, resulting in crash, memory leak, or RCE. Exposed: Express 5 with express.json, fetch on server side, zlib.createBrotliDecompress with external input. Need to check: Koa, Egg, Strapi, fastify compress. Not going this route: Express 4 express.json and built-in http server

Exposed paths in Node.js

  • Express 5 + express.json(): 2.x's body-parser decodes body br by default because option inflate has a value of true.
  • fetch() on the server: responses with Content-Encoding: br are still decoded, even if the request sends Accept-Encoding: identity. This is relevant if your server calls URLs from users (webhooks, link previews, scrapers).
  • zlib.createBrotliDecompress() which was given input from outside.
  • Other frameworks whose body parsers decode br: Koa (co-body, @koa/bodyparser, koa-body), Egg, Strapi, and Fastify with @fastify/compress.

Express 4.21.2 through 4.22.3 (body-parser 1.x) rejects encodings other than gzip and deflate with status 415, so they don't go the body-parser route. Keep upgrading, because line fetch() does not depend on the Express version.

Beyond Node.js

ProductProne toSafe version
Node.js 26.x< 26.12.126.12.1
Node.js 24.x< 24.22.124.22.1
Node.js 22.x< 22.23.422.23.4
Node.js 20.x / 23.x / 25.xAll (EOL)Nothing, moved to 22/24/26
Google Brotli1.0.3–1.2.01.0.10, 1.1.1, 1.2.1
Bun≤ 1.4.31.4.4
Python brotli / brotlicffi< 1.2.1 / < 1.2.1.01.2.1 / 1.2.1.0
PHP on Windows (cURL extension, automatic decompression)Old build8.5.12-1, 8.4.27-1, 8.3.36-1, 8.2.35-1
Alpine brotliOld version1.2.1-r0 (1.1.1-r0 in 3.21)
DebianView trackersDLA-4855-1 / DSA-6582-1

Debian Note: Debian package nodejs uses the system's Brotli library, so package brotli must also be updated.

How to check your server?

Don't test by sending an exploit-like payload to a production server. Version checks and code paths are sufficient and safe.

One important pitfall: Node.js patches Brotli without changing the version string, so process.versions.brotli still displays 1.2.0 in both secure and non-secure builds. The benchmark is the Node.js version, not the Brotli version.

# 1) Cek versi Node.js yang benar-benar berjalan
node -e 'const [a,b,c]=process.versions.node.split(".").map(Number);
const fix={22:[23,4],24:[22,1],26:[12,1]}[a];
if(!fix){console.log("EOL/tidak didukung, segera migrasi:",process.version);process.exit(2)}
const ok=b>fix[0]||(b===fix[0]&&c>=fix[1]);
console.log(ok?"AMAN":"RENTAN",process.version)'

# 2) Cek di dalam container yang sedang jalan (bukan tag image-nya)
docker exec <nama_container> node --version

# 3) Cek versi Express/body-parser dan jalur dekompresi di kode
npm ls express body-parser
grep -rnE "express\.json|bodyParser|createBrotliDecompress|brotli" src/

# 4) Cek Brotli di sistem dan paket Python
dpkg -l | grep -i brotli          # Debian/Ubuntu
apk list --installed | grep brotli # Alpine
pip show brotli brotlicffi 2>/dev/null | grep -E "^(Name|Version)"
bun --version                      # bila memakai Bun

Tags like node:24 don't give any answers. Old images that are still stored in the build cache may contain 24.22.0.

What is the mitigation?

1. Patch and restart everything

  1. Upgrade to Node.js 22.23.4, 24.22.1, or 26.12.1. If you're still on Node.js 20, it's time to move on. Our guide on upgrading to Node.js 26 LTS can be a starting point.
  2. Rebuild image from patched base ( docker pull node:24 then rebuild without cache).
  3. Restart every service, worker, cron job, and replica, including CI runners and serverless functions.
  4. Upgraded Bun to 1.4.4, Python brotli package to 1.2.1, and system Brotli package from the distro.
  5. Homebrew users need to double check, because Homebrew was lagging behind when released.

2. Reduce attack paths while waiting for the patch

// Express 5: tolak body terkompresi (akan dibalas 415).
// Efek samping: body gzip juga ikut ditolak, uji klien dulu.
app.use(express.json({ inflate: false }));
app.use(express.urlencoded({ extended: true, inflate: false }));
# Nginx di depan aplikasi: tolak request dengan Content-Encoding br
# (taruh di blok server/location yang meneruskan ke Node.js)
if ($http_content_encoding ~* "br") {
    return 415;
}

The two steps above only close the body of the incoming request. Responses captured past fetch() remain exposed, and deleting br from Accept-Encoding does not prevent the malicious server from sending Brotli. For those paths, the solution is still to upgrade, or limit fetch() to trusted hosts only.

WAF rules are just an additional layer. Cloudflare's new rule for this loophole defaults to Log mode, so it doesn't block anything until the action is changed. In Azure, DRS 2.2 rule number 920450 blocks assembly requests that Microsoft analyzes.

3. Limit the impact if it penetrates

  • Run Node.js as a non-root user.
  • Don't store important secrets in web process environment variables. Use a secret manager with limited access.
  • Rotation of tokens and API keys if a vulnerable server was exposed to the internet since October 21.

How to detect attempted attacks?

Sysdig describes a typical post-exploitation pattern: a Node.js process crashes, then a new program execution appears in the same workload, or a network socket is redirected to stdin/stdout (reverse shell). Other indicators reported:

  • Requests with Content-Encoding: br are very large. The exploit request that ELLIO saw was about 23 MB in size.
  • IP 94.154.43.7 and payload SHA-256 hash 7fcbdaaecd4419e2930927844f3b772668204737a4e7602a2290c991c93c5c4b.
  • Scanner with identity node2shell-check/1.0 of the AS44620 that sends the Brotli body to search for a decompressing host.
  • Worker Express restarting repeatedly for no apparent reason.
# Cari request Brotli di access log Nginx (butuh $http_content_encoding di log_format)
grep -E '"br"' /var/log/nginx/access.log | tail -n 50

# Cari IP yang dilaporkan ELLIO
grep -F "94.154.43.7" /var/log/nginx/access.log*

# Cari jejak crash Node.js di log systemd/container
journalctl --since "2026-10-21" | grep -iE "node.*(segfault|core dumped|SIGSEGV)"
docker ps -a --filter "status=restarting" --filter "status=exited"

If you encounter a Node.js crash involving process sh, bash, curl, or wget as a child Node process, treat it as an incident: isolate the host, retrieve logs, then rotate credentials.

FAQs

Is my Express 4 app safe?

Express 4 with body-parser 1.x doesn't decode Brotli bodies, so it doesn't go that route. However, the Node.js runtime remains vulnerable if there is other code that uses fetch() to an untrusted server or createBrotliDecompress(). Runtime upgrades remain mandatory.

Why is process.versions.brotli still 1.2.0 even though it has been upgraded?

Node.js installed the fix as a backport without replacing the Brotli version string. Use the Node.js version (22.23.4, 24.22.1, 26.12.1 and above) as a reference.

I'm still on Node.js 20. Any patches?

No. Node.js 20 has been EOL since April 30, 2026 and did not receive this fix, nor did 23.x and 25.x. Move to 22.x, 24.x, or 26.x.

Is it enough to block Content-Encoding: br in the proxy?

Not enough. It just closes the body of the incoming request. Responses from other servers that you take via fetch() can still trigger bugs. Think of blocking at the proxy as a temporary hold.

Does this loophole also affect PHP and Python?

Yes, via the same Brotli library. PHP on Windows with cURL extension and automatic decompression is fixed in 8.5.12-1, 8.4.27-1, 8.3.36-1, and 8.2.35-1. In Python, update brotli to 1.2.1 and brotlicffi to 1.2.1.0.

If your service calls a third-party API via a gateway such as APIPedia Console, also make sure the server calling it uses a patched runtime, because the API response also passes through the same decoder.

Source

Topics:

${c.about}

Tim APIPedia — Tim Engineering APIPedia

Tim engineering di balik APIPedia yang membangun dan memelihara API data Indonesia - ongkir, OCR KTP, data wilayah, BMKG, hingga server MCP - dan menulis panduan integrasinya dari pengalaman langsung.