Quando un decoder diventa un vettore di attacco
La compressione Brotli è ormai lo standard de facto per ridurre il peso delle risposte HTTP e, sempre più spesso, anche dei body delle richieste in ingresso. È implementata nativamente dentro Node.js, Bun e in una quantità di middleware che la maggior parte degli sviluppatori utilizza senza nemmeno saperlo: body-parser, i parser di Koa, @fastify/compress, e persino il fetch() globale. Quando un bug critico colpisce proprio questo strato, l’impatto non si limita a un singolo pacchetto npm: si propaga a runtime diversi, framework diversi e migliaia di applicazioni che non hanno mai scelto consapevolmente di usare quel codice.
È esattamente quello che è successo con Node2Shell, la vulnerabilità identificata come CVE-2026-111497, resa pubblica il 22 ottobre 2026 insieme alle versioni corrette di Node.js e Bun. Vale la pena capire bene il meccanismo, perché non si tratta di un bug “di libreria terza parte”, ma di un difetto nel cuore stesso del runtime.
Il meccanismo tecnico: un out-of-bounds write nel decoder Brotli
Node2Shell è classificata come CWE-787 (Out-of-bounds Write) e CWE-252, e risiede nella libreria C google/brotli che Node.js e Bun impacchettano al loro interno — non nel pacchetto npm brotli, che non è coinvolto. Il formato dello stream Brotli prevede la dichiarazione di “block type” multipli all’interno del flusso compresso. Il bug nasce quando uno stream dichiara un solo block type e poi, più avanti nello stream, tenta comunque di switchare verso un altro blocco: il decoder non rifiuta questa transizione illegale e continua a decodificare in uno stato interno non valido, finendo per scrivere oltre i limiti del buffer allocato.
Il fix è stato effettivamente committato nel repository google/brotli il 27 febbraio 2026, ma senza alcuna release o advisory pubblica collegata — il che significa che per quasi otto mesi la correzione è rimasta “silenziosa” finché i ricercatori di sicurezza OpenAI Codex (Khai Tran e Sylvie Mayer) non l’hanno segnalata formalmente al team Node.js, innescando il processo di CVE e il rilascio coordinato delle patch.
Versioni affette e versioni corrette
| Componente | Versioni affette | Versione con fix |
|---|---|---|
| Node.js 26.x | < 26.12.1 | 26.12.1 |
| Node.js 24.x | < 24.22.1 | 24.22.1 |
| Node.js 22.x | < 22.23.4 | 22.23.4 |
| Node.js 20.x e linee EOL | tutte le release che impacchettano Brotli | nessuna patch — va fatto l’upgrade a una linea supportata |
| Bun | < 1.4.4 | 1.4.4 |
| libreria Google Brotli | 1.0.3–1.0.9, 1.1.0, 1.2.0 | 1.0.10, 1.1.1, 1.2.1 |
Google, in qualità di CNA, ha assegnato un punteggio CVSS 4.0 pari a 9.2 (Critical); il team Node.js la classifica come “High” e conferma esplicitamente che il difetto può portare a remote code execution. Degno di nota: anche il pacchetto Python Brotli di Google impacchetta lo stesso decoder vulnerabile ed è corretto nella versione 1.2.1, quindi chi gestisce pipeline Python che processano contenuti Brotli non ufficiali dovrebbe verificare anche quel lato.
Dove si trova la superficie di attacco reale
Il punto cruciale per chi deve valutare l’esposizione non è “uso Node.js?”, ma “qualcosa nella mia stack decodifica automaticamente Brotli su dati non fidati?”. Le combinazioni più comuni ad alto rischio sono:
- Express 5 + body-parser 2.x: i metodi
json(),urlencoded(),text()eraw()decodificano automaticamente i body conContent-Encoding: br. Express 4 e body-parser 1.x, invece, non lo fanno — un buon motivo in più per non ritardare la migrazione a Express 5. - Koa, tramite
koa-bodyparser,@koa/bodyparser,koa-bodyoco-body. - Fastify, quando è attivo
@fastify/compresscon la decompressione delle richieste in ingresso. - Il
fetch()integrato in Node.js e Bun, che decodifica automaticamente le risposte Brotli: qualunque servizio che scarica contenuti da URL controllati (anche indirettamente) da un utente è potenzialmente esposto. - Uso diretto di
zlib.brotliDecompress,brotliDecompressSyncocreateBrotliDecompresssu dati non fidati.
Un semplice server http nativo, senza middleware di decompressione, non decodifica automaticamente i body Brotli e quindi non è esposto per quella via — un dettaglio utile per dare priorità alla remediation.
Stato dell’exploitation
Al 23 ottobre 2026 risultava già dimostrata pubblicamente l’esecuzione di codice sull’immagine Docker ufficiale di Node.js 24.22.0 arm64, con un proof-of-concept pubblicato. Non risultano, al momento, campagne di sfruttamento attivo in the wild, e la CVE non è ancora presente nel catalogo CISA KEV — ma dato che il PoC è pubblico e il bug è banalmente raggiungibile da qualunque endpoint HTTP che accetti body compressi, la finestra di rischio va considerata stretta.
Come individuare l’esposizione nella propria infrastruttura
Prima di tutto, va fatto un inventario mirato invece di un generico “controllare la versione di Node”:
- Cercare nei Dockerfile le immagini base che puntano a versioni vulnerabili di Node.js o Bun, incluse le linee end-of-life e le immagini pinnate per digest (che vanno riviste manualmente perché il tag non rivela la versione).
- Cercare righe
nvm installo script NodeSource che installano versioni vulnerabili nelle pipeline di CI/CD. - Dare priorità ai servizi che combinano un runtime vulnerabile con una dipendenza che decodifica Brotli (Express 5, body-parser 2, parser Koa,
@fastify/compress). - Ispezionare le immagini container già costruite per individuare runtime vulnerabili o librerie Brotli di sistema nel range affetto — attenzione: alcune distribuzioni Linux applicano backport dei fix mantenendo numeri di versione apparentemente “vecchi”, quindi va controllato l’advisory specifico della distribuzione.
- Verificare anche i binari standalone generati con
bun build --compile, che incorporano il runtime e vanno ricompilati.
Mitigazione immediata
# 1. Verifica rapida della versione installata
node -v
bun --version
# 2. Aggiornamento Node.js (esempio con nvm)
nvm install 24.22.1
nvm alias default 24.22.1
# 3. Aggiornamento Bun
bun upgrade --version 1.4.4
Se un upgrade immediato non è possibile, due mitigazioni temporanee (da sostituire appena possibile con la patch vera):
# A livello di reverse proxy (esempio nginx), rifiuta Content-Encoding: br
# anche quando compare in liste come "gzip, br"
if ($http_content_encoding ~* "br") {
return 415;
}
In alternativa, si può disattivare l’inflate nei body parser applicativi — ma attenzione: questo blocca anche i body compressi in gzip, quindi è un compromesso drastico da usare solo come tampone di emergenza. Nessuna delle due mitigazioni copre il caso di fetch() che decodifica risposte Brotli in arrivo da terzi: per quel vettore l’unica soluzione reale è l’aggiornamento del runtime.
Conclusione
Node2Shell è un promemoria scomodo ma utile: le librerie di decompressione sono codice “di sistema” tanto quanto il kernel o la libc, eppure vengono quasi sempre trattate come dettaglio implementativo invisibile. Un bug qui ha un raggio d’azione che attraversa runtime (Node.js, Bun), linguaggi (Python) e letteralmente ogni framework web che si appoggia a quella decodifica. Per chi gestisce infrastrutture Node.js in produzione, la lezione pratica è duplice: primo, includere esplicitamente la versione del runtime — non solo delle dipendenze npm — nei processi di vulnerability scanning; secondo, trattare le immagini container pinnate per digest come un rischio aggiuntivo, perché “non è cambiato il tag” non significa “non serve ricontrollare”.
Fonte: Cycode – node2shell (CVE-2026-111497): What to Patch and How to Find Exposed Services