It’s now evident that everyone knows – Node2Shell and HTTP2Shell are the same bug: CVE-2026-111497. It is an out-of-bounds write condition in the Brotli decoder that Node.js bundles. CISA added it to the KEV catalog on October 27, 2026, with a federal due date of November 10, 2026. Upgrade Node.js to 22.23.4, 24.22.1 or 26.12.1, rebuild your images, and restart every Node process to fix this vulnerability.
CISA lists this under vendor Google and product Brotli. If your CISA KEV matching uses “Node.js”, it will not flag your Node hosts.
What are Node2Shell and HTTP2Shell?
Node2Shell, HTTP2Shell, Python2Shell and Broccoli2Shell are four nicknames for one vulnerability: CVE-2026-111497, also tracked as GitHub advisory GHSA-9v8h-qqgm-4m35. Each name tracks who the industry thought was exposed that week.
| Name | Date | Reason |
|---|---|---|
| Node2Shell | October 22, 2026, by Imperva Red Team | Node.js shipped the fix first, on October 21 |
| Python2Shell | October 23, 2026 | The PyPI packages Brotli and brotlicffi compile the same C decoder |
| HTTP2Shell | October 23, 2026, by Aikido | The bug sits in the shared library, so anything that decodes Brotli over HTTP is in scope |
| Broccoli2Shell | October 24 2026 | A tracker site for affected products uses this name |

The vulnerability exists in deps/brotli/c/dec/decode.c source file that declares the DecodeBlockTypeAndLength() function because the brotli decoder fails to tell “this stream is broken” from “I need more bytes”. A Brotli stream declares how many block types it uses. If it declares one type and then asks to switch types, the switch is impossible. The vulnerable function answered that case with a plain BROTLI_FALSE, the same value it used for “not enough input yet”. The streaming decoder took it as “wait for more data” and kept appending input to an 8-byte internal buffer, past its end.
The patch converts the function’s true/false condition into a real error code. It adds a new error, BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH. The vulnerable fast path ignored the return value entirely, which is why the CVE record lists two weaknesses: CWE-252 (unchecked return value) and CWE-787 (out-of-bounds write).
The upstream fix is google/brotli commit 0ed4268, dated February 27, 2026. Its message reads like a cleanup and says nothing about security. Nobody released it until October 21, 2026, when Brotli shipped 1.0.10, 1.1.1 and 1.2.1. Microsoft also shipped out-of-band .NET fixes on October 25, 2026: .NET 8.0.33 and .NET 10.0.14. If you run .NET services that decode Brotli (BrotliStream, HttpClient automatic decompression), they need that update too.
The other nine CVEs in the October 2026 release
None of the other nice CVEs have a CVSS 4.0 score. HackerOne, their numbering authority, published CVSS 3.0 only. None is in the CISA KEV as of catalog Version 2026.10.28 either. Every patch is in 22.23.4, 24.22.1 and 26.12.1 versions.
| CVE | Issue | NodeJS Rating | CVSS 3.0 (CNA) | CWE | Preconditions |
|---|---|---|---|---|---|
| CVE-2026-105482 | dns.resolve*() truncates hostnames at a NUL byte | High | 7.4 | CWE-284 | App validates a hostname as a string, then resolves it with dns.resolve*(). Incomplete fix of CVE-2026-48930 |
| CVE-2026-105483 | TLS hostname bypass with fullwidth digits (127.0.0.1) | High | 7.4 | CWE-295 | A trusted CA issued a cert with a numeric DNS SAN. Regression after CVE-2026-48618 |
| CVE-2026-106069 | TLS hostname bypass with a leading-zero IPv4 (0127.0.0.1) | High | 7.4 | CWE-295 | A resolver that reads the octets as decimal, such as macOS |
| CVE-2026-106072 | DNS SAN authenticates a non-canonical IPv4 (127.1, 0x7f.0.0.1) | Medium | 6.7 | CWE-295 | Numeric DNS SAN from a trusted CA, plus a resolver like glibc |
| CVE-2026-105481 | Keep-alive socket accepts a response sent before the request | Medium | 6.5 | CWE-367 | Busy event loop when a reused socket is handed out. Incomplete fix of CVE-2026-48931 |
| CVE-2026-106074 | randomUUID() / randomInt() cache baked into snapshots and single executable apps | Medium | 4.8 | CWE-330 | Those functions called while the snapshot is built |
| CVE-2026-106070 | zlib.ZipFile returns attacker bytes after a failed header check | Low | 3.7 | CWE-840 | A Node.js-based ZIP scanner |
| CVE-2026-106071 | Permission Model wildcard over-grant | Low | 3.6 | CWE-284 | --permission with a wildcard grant at the tree root |
| CVE-2026-106073 | Permission Model bypass with fs.realpathSync() | Low | 3.3 | CWE-284 | --permission in use. Incomplete fix of CVE-2026-21715 |
Four of these vulnerabilities are incomplete fixes or regressions of earlier CVEs. A host that “fixed” CVE-2026-48930, CVE-2026-48931, CVE-2026-48618 or CVE-2026-21715 is exposed again until the October release is installed.
CVE-2026-111497 scorecard
CVE-2026-111497: Brotli decoder out-of-bounds write (Node2Shell / HTTP2Shell)
- In CISA KEV since 2026-10-27; CISA SSVC Exploitation: active
- CVSS 4.0 9.2, network, no privileges, no user interaction
- Code execution reported on official builds: arm64 (Imperva) and x86-64 (ELLIO)
- Needs an app that decodes attacker-controlled Brotli (Attack Requirements: Present)
- SSVC Automatable: no; RCE took several attempts in Imperva's lab
- One in-the-wild attempt reported by ELLIO on 2026-10-26; it crashed ELLIO's test target without a shell
- No public exploit for a stock build; public PoCs crash it or need a weakened build
Patch now
Upgrade to 22.23.4, 24.22.1 or 26.12.1, rebuild images, restart every Node process.
End-of-life Node.js (20, 23, 25) has no fix.
KEV due 2026-11-10
The CVSS 4.0 vector from Google is CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N. “AT:P” is the honest part of that score as the app has to decode Brotli the attacker controls. The impact stays inside the Node process (SC/SI/SA all None). Node.js itself rates this vulnerability as “High” and publishes no score.
Am I vulnerable to CVE-2026-111497?
How do you check if you are vulnerable to Node2Shell? Simple – ask the node binary whether it knows the new error code. It is the most reliable check I found, and I tested it on real builds. The version string can mislead you, especially on distribution packages that link the system Brotli.
node -p "require('zlib').constants.BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH"
# -17 -> the fix is in this binary
# undefined -> vulnerable
Do not use process.versions.brotli to check for this fix as in my tests, it reported unreliably. Patched versions still reported 1.2.0 which can be misleading.
How do you detect Node2Shell?
You can detect two things today: Brotli request bodies reaching a Node.js decoder, and a Node.js server spawning a shell or downloader after a successful exploit. You cannot yet detect the exploit itself as no request-level exploit signature is public and Imperva did not publish its stream or its WAF rule logic. The one in-the-wild request reported so far is known only by its size and hash. Every rule below was run in my lab against real Node.js 24.22.0 and 24.22.1 builds.
| Detection | What is caught? | Synthetic lab result |
|---|---|---|
Zeek brotli_exposure.zeek | Inbound br request bodies, br responses to internal clients | 12 of 12 expected notices, 0 on gzip or plain requests |
| Sigma “Node.js Server Process Spawning Shell, Downloader or Recon Tool” | Post-exploitation from any Node server, Linux and Windows | 8 of 8 attack events; no hit on 2 npm install events or a git call |
| ModSecurity rule 11497 | Inbound br request bodies (interim block) | Blocked br, BR, gzip, br, br, gzip, br; did not fire on gzip or brotli |
These rules were tested on synthetic lab traffic and on process events built from real execve traces, not on live exploit traffic. No exploit for a stock build is public.
Sigma: a Node.js server spawning a shell
I checked SigmaHQ first (latest commit 05d1031). Nothing there covers the path Imperva exploited:
- The Linux and Windows React2Shell rules only fire when the parent command line looks like Next.js. An Express app (
node server.js) slips past. The Linux rule also has a branch that can never match:Image|endswith: '-c', where-cbelongs in the command line. - Linux Webshell Indicators lists
/nodeas a parent but no shells. Node’sexec()runs the payload as/bin/sh -c <cmd>, so the recon tool’s parent issh, notnode, and the rule misses it.
So I wrote one. Get it from mayur-esh/sigma-rules. After conversion to SQL with pySigma this is the behaviour:
| Event | This rule | SigmaHQ React2Shell (Linux / Windows) | SigmaHQ Linux Webshell |
|---|---|---|---|
Express exec('id') → /bin/sh -c id | Hit | Miss | Miss |
Express execFile('/usr/bin/id') | Hit | Miss | Miss |
Express reverse shell via exec() | Hit | Miss | Miss |
Express spawns curl | Hit | Miss | Miss |
Next.js exec('id') | Hit | Hit | Miss |
Express on Windows, cmd.exe /d /s /c whoami | Hit | Miss | n/a |
Next.js on Windows, cmd.exe /d /s /c whoami | Hit | Hit | n/a |
| Express on Windows spawns PowerShell | Hit | Miss | n/a |
npm install lifecycle script (Linux, Windows) | No hit (correct) | No hit | No hit |
App runs git rev-parse | No hit (correct) | No hit | No hit |
ModSecurity: block Brotli request bodies while you patch
SecRule REQUEST_HEADERS:Content-Encoding "@rx (?i)(?:^|,)\s*br\s*(?:,|$)" \
"id:99999,phase:1,deny,status:415,log,msg:'Brotli request body rejected (CVE-2026-111497 interim)',tag:'CVE-2026-111497'"
Tested on Apache 2.4 with ModSecurity 2.
If you run the OWASP Core Rule Set, check before adding this rule. CRS 3.3.5 already lists content-encoding in tx.restricted_headers, so rule 920450 blocked every request carrying a Content-Encoding header in my lab at paranoia level 1. That only holds in blocking mode. Debian’s recommended configuration ships SecRuleEngine DetectionOnly, and many teams remove content-encoding from the list to allow compressed uploads. I did not test CRS 4.x. Also check for the number. It is set to the range ModSecurity reserves for local rules. Change it if it already exists.
Which public Node2Shell scanners and PoCs can you trust?
Most public CVE-2026-111497 checkers read a version number. A version check cannot see a backported fix, so on a back-patched build it reports “vulnerable”. In my lab, 22 of the 31 tools I could run did exactly that. All 7 tools that got every target right tested the decoder itself.
I used the same method as my post Quantitative Scoring of GitHub Hosted PoCs: A php2shell Case Study Every repository gets a safety status. Defensive repositories also get a reliability score: 50% content, 30% provenance, 20% hygiene, capped at 100 × content + 20 so a clean repository with weak content cannot score well. Exploit and PoC repositories get a safety status only, never a score or a ranking. As always – stars are never scored as they can be bought.
I started from the nomi-sec PoC-in-GitHub index (62 repositories) and added a GitHub search for the CVE ID, the GHSA ID and all four nicknames, one creation day at a time from October 21 to October 28, 2026. The search returned 200 repositories, including all 62 from the index. Of the 138 new ones, 38 are about this CVE. The other 100 only mention it: general scanners, news radars, unrelated applications and PHP2Shell tools. I cloned all 100 dedicated repositories and graded each at the commit I cloned.
100 public CVE-2026-111497 repositories by class
I ran every defensive tool that would run offline in default mode and inside a network namespace on loopback. Two tools did ask me for consent before scanning (--i-am-authorised, --yes). These are the results:
Lab grades for 31 CVE-2026-111497 checkers
In detail, this is what each repository was scored for:
| Respository | Tool type | Classification | Result |
|---|---|---|---|
| SYM01/cve-2026-111497 | Network | Active probe | On an app that collapses every body error into one response, it said “inconclusive” and explained why |
| assetnote/node2shell-scanner | Network | Active probe, compares decode behavior | Worked fine on both lab app configurations |
| AikidoSec/brotli-client-checker | Client test server | Serves a malformed Brotli response | Correct. fetch() on 24.22.0 died with a segmentation fault; 24.22.1 and the back-patched build rejected it with BLOCK_SWITCH |
| AmanSin001/node-brotli-check | Host | Fix constant | Correct, and named the back-patched build a vendor backport |
| shikatagana1/node2shell | Host | Fix constant | Correct |
| TahuCryptsi/node-brotli-check | Host | Vendor test stream | Correct |
| s1ko/poc-cve-2026-111497 | Host | Test streams | Correct |
| CompleTetech-Security/node2shell-check | Host | Version plus fix constant | Partial: “backport evidence unverified” on the back-patched build |
| Kaka201/scan | Network | Valid and broken Brotli bodies | Partial: reports whether a route decodes Brotli, not whether it is patched, and says so in its README |
| pt-act/node2shell-cve-2026-111497 | Host | Version wins over its own probe | False positive. It printed “-17 present, bundled decoder has the fix”, then VULNERABLE |
| trailofbits/brotli-checker | Host | Embedded Brotli version | Flagged the official fixed 24.22.1 too, because Node.js did not bump the Brotli version string |
| ashenroot/Node2Shell | Network | Version in headers and page bodies | False positive, labeled “CONFIRMED VULNERABLE” |
Of all, two detection repositories re promising. CyberThreatNet/hunt-lab has a Sigma hunt rule very similar to mine. However, in my 12 test events above, it caught 7 of 8 attack events with no false positives, missing only a direct execFile of a recon binary.
FAQ
Is my Express app vulnerable?
If it runs Express 5 on an unfixed Node.js and uses express.json(), raw(), text() or urlencoded(), it decodes Brotli request bodies by default and is exposed. Express 4 with body-parser 1.x rejects Brotli bodies. Any server-side fetch() of an untrusted URL is exposed on either version.
Is there a public exploit for CVE-2026-111497?
Public proof-of-concept code exists. In public reports it crashes stock builds or reaches code execution on deliberately weakened builds. Imperva (arm64) and ELLIO (x86-64) report code execution on official builds and have not published their exploits. ELLIO also saw one attempt in the wild on October 26 that failed in its own test.
Is Python affected by HTTP2Shell?
Yes, when the PyPI package Brotli or brotlicffi is installed. urllib3, requests, aiohttp and httpx then decode Brotli responses automatically, even if your code never imports Brotli. Upgrade Brotli to 1.2.1 and brotlicffi to 1.2.1.0 to mitigate HTTP2Shell.
Is Bun affected by CVE-2026-111497?
Yes. Bun 1.4.3 and earlier bundle the vulnerable decoder. Bun 1.4.4 patches Node2Shell.
Leave a Reply
You must be logged in to post a comment.