Skip to content
Exploit for CVE-2026

Exploit for CVE-2026

Sploitus • October 22, 2026

CVE-2026-111497 — Node.js Brotli Decoder OOB Write: Deep Analysis & Structural Unreachability Proof

Key Finding · Analysis · PoC Tools · Detection · Lab

Independent security research into CVE-2026-111497, a High-severity OOB write in the Brotli decoder bundled with Node.js (`deps/brotli`). While the code bug is real — the `BROTLI_SAFE` macro silently drops the return value on the fast decoding path — **we prove the vulnerable code path is structurally unreachable from any Brotli stream**, valid or malformed.

The Brotli specification caps `MLEN` (metablock uncompressed length) at exactly `2^24 = 16,777,216`, which is identical to `BROTLI_BLOCK_SIZE_CAP`, the initial `block_length` when `NBLTYPES = 1`. This means the block-switch trigger condition (`block_length == 0` with `NBLTYPES ≤ 1`) can never fire during stream processing — the metablock ends before the counter is exhausted.

**Practical impact: DoS (infinite CPU loop) via header corruption, not RCE.**

**Author:** [Qi Deng](  |  **Date:** 2026-10-22

| **CVE** | [CVE-2026-111497]( |

| **CWE** | CWE-787 (Out-of-bounds Write), CWE-252 (Unchecked Return Value) |

| **CVSS 4.0** | 9.2 (Critical) — **disputed: we argue High is more accurate** |

| **Vendor Rating** | High (Node.js), no CVSS vector published |

| **Component** | `deps/brotli/c/dec/decode.c` (Google Brotli decoder) |

| **Upstream Fix** | [google/brotli@0ed4268a]( |

| **Affected** | Node.js num_block_types[tree_type];

if (max_block_type block_length[0] = BROTLI_BLOCK_SIZE_CAP; // reset to 16M

s->block_type_rb[0] = 1; s->block_type_rb[1] = 0; // reset

s->block_type_rb[2] = 1; s->block_type_rb[3] = 0; // reset

s->block_type_rb[4] = 1; s->block_type_rb[5] = 0; // reset

s->context_map = NULL; // freed and re-allocated

| DoS (CPU loop via header corruption) | **Trivial** | Malformed MLEN → decoder errors out, not infinite loop |

| DoS (infinite loop via block switch) | **Blocked** | MLEN = BLOCK_SIZE_CAP prevents reaching the trigger |

| Memory disclosure | **Theoretical** | Requires reachable OOB read, which has the same MLEN barrier |

| Local RCE (no ASLR) | **Artificial** | Demonstrated only with lowered BLOCK_SIZE_CAP or `--predictable` |

| Remote RCE (with ASLR) | **Not achievable** | No public demonstration; structurally blocked |

## Attack Surface (Real Risk: DoS via Decoder Errors)

Even though RCE is unreachable, malformed Brotli input can trigger decoder errors and resource consumption:

| `fetch()` (undici) | Yes — decodes `Content-Encoding: br` responses | Medium — malicious server can crash fetch |

| Express 5 / body-parser 2.x | Yes — decodes `br` request bodies | High — unauthenticated POST |

| axios | Yes — requests `br` by default | Medium — response decoding |

| Direct `zlib.brotliDecompress*` | N/A — explicit call | Depends on input source |

# One-liner: check if your Node.js is patched

node -p "require('zlib').constants.BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH || 'VULNERABLE'"

# VULNERABLE → missing error code, needs update

# Generate 67 malformed Brotli payloads (NBLTYPES flips, header bit-flips, MLEN overflows)

# Test all payloads against local Node.js (runs each in subprocess for crash isolation)

# Full attack surface demo: fetch() auto-decompression, streaming API, error code analysis

# Vulnerable endpoint (Express 5 with body-parser 2.x, accepts Content-Encoding: br)

curl -X POST \

"require('zlib').constants.BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH || 'VULNERABLE'"

# detection/check_version.sh — checks all node binaries on PATH

patched=$($n -p "require('zlib').constants.BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH||'NO'" 2>/dev/null)

echo "$n: $ver (patched: $([[ $patched == '-17' ]] && echo 'YES' || echo 'NO'))"

### Check System libbrotlidec (Debian/Ubuntu)

# Distro Node.js packages may link system brotli instead of bundled

dpkg -l libbrotli1 2>/dev/null | grep -E '^ii' | awk '{print $2, $3}'

│ ├── structural_proof.md # Formal proof: MLEN = BLOCK_SIZE_CAP

│ ├── exploit_chain.md # Theoretical exploitation chain analysis

│ └── upstream_diff.md # Annotated upstream fix (0ed4268a)

│ ├── craft_payloads.py # Generate malformed Brotli payloads

│ ├── test_vuln.js # Test payloads against local Node.js

│ └── demo_attack_surface.js # fetch() + streaming attack surface demo

│ ├── check_version.sh # Check all local Node.js binaries

│ └── check_system_brotli.sh # Check system libbrotlidec version

│ ├── docker-compose.yml # Vulnerable Express 5 endpoint

│ ├── Dockerfile # Node.js 24.19.0 (pre-patch)

│ └── server.js # Minimal Express 5 + body-parser 2.x app

└── cover.svg # Repo cover image

| 2026-10-21 | Node.js security release: 22.23.4, 24.22.1, 26.12.1 |

| 2026-10-21 | Google Brotli upstream fix: 1.0.10, 1.1.1, 1.2.1 |

| 2026-10-22 | Vercel emergency WAF rules + managed runtime patch |

| 2026-10-22 | This research: structural unreachability proof |

- [Node.js October 2026 Security Releases](

- [Vercel Advisory: Brotli decoder RCE](

- [Upstream Fix: google/brotli@0ed4268a](

- [HOL Guard Campaign Analysis](

- [The Hacker News Coverage](

- [RFC 7932: Brotli Compressed Data Format](

Extracted Entities