# Python packages are also vulnerable to the Node2Shell Brotli flaw (CVE-2026-111497)

Brotli decoder's DecodeBlockTypeAndLength conflates malformed-stream and need-more-input returns, causing OOB write reachable via PyPI Brotli/brotlicffi in HTTP clients.

- Payout: Research
- Severity: critical
- Program: Brotli
- Write-up by: Aikido Security
- Disclosed: 2026-10-23
- CVE: CVE-2026-111497
- Write-up quality: 6/10
- Original: https://www.aikido.dev/blog/python-packages-vulnerable-node2shell-brotli-cve-2026-111497
- Library page: https://bugbountylibrary.com/r/blog%3Aaikido-dev-blog-python-packages-vulnerable-node2shell-brotli-cve-2026-111497
- Tags: area:oss domain:oss impact:rce kind:research platform:threat-intel sev:critical tech:nodejs tech:python technique:incomplete-fix technique:source-review vuln:dos vuln:memory-corruption

**Target:** Brotli decoder (DecodeBlockTypeAndLength) in PyPI Brotli and brotlicffi bindings
**Root cause:** DecodeBlockTypeAndLength() returns a plain boolean where BROTLI_FALSE conflates 'malformed stream' with 'need more input', and the fast path discards the return value entirely. A crafted stream declaring one block type then exhausting its length forces an impossible block switch, leading to an out-of-bounds write.
**Steps:**
1. Install vulnerable Brotli 1.2.0 or brotlicffi 1.2.0.x (transitively via urllib3/requests/httpx[aiohttp]/Scrapy).
2. Craft a Brotli stream that declares a single block type and exhausts that block's length, forcing a block switch that cannot succeed.
3. Deliver it either as a Brotli response body from an attacker-controlled server (outbound fetch) or as a request body with `Content-Encoding: br` (inbound).
4. Observe on 1.2.0: decoder consumes the ~23 MB stream and keeps reporting it needs more input, whether fed whole or in 1-byte chunks; `brotli.decompress()` raises generic `brotli.error: decoder failed`.
5. On 1.2.1 the same stream is rejected with error -17 (BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH).
**How found:** OSM diffed Brotli 1.2.0 vs 1.2.1 source distributions, found 1.2.1 differs only in version bump plus the decoder changes from google/brotli commit 0ed4268 (return error code + new BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH -17). They built both with AddressSanitizer and ran a crafted stream through each to confirm the behavioral difference.
**Exploitation:** Attacker sends a malicious Brotli-compressed payload to a Python app that has Brotli/brotlicffi installed, either as a response to an outbound fetch (webhook, URL preview, crawler) or as an inbound request body with Content-Encoding: br. The decoder reaches the vulnerable block-switch path and writes out of bounds. OSM did not pursue memory corruption or code execution in the Python extension; the same decoder code was exploited against unmodified Node.js 24.22.0.
**Insight:** When a decoder API returns a boolean for a tri-state result (error / need-more-input / ok), craft streams that force the ambiguous branch — here, declaring one block type then exhausting its length to force an impossible block switch — and diff patched vs unpatched builds under ASan to confirm which path is taken.

