Escrita fora dos limites no decodificador Brotli, CVE-2026-111497, do Node.js ao .NET e ao Python
Apesar do nome, o HTTP2Shell não é uma falha do protocolo HTTP/2. É um defeito no decodificador da biblioteca de compressão Brotli, do Google, que dezenas de produtos embutem em cópia própria. Um fluxo comprimido malformado faz o decodificador pedir mais dados quando deveria rejeitar a entrada, e ele continua gravando bytes num buffer interno de oito posições até passar do fim. Quem descomprime dados de origem não confiável fica exposto: servidores Express 5 que aceitam corpo comprimido, clientes que usam fetch() contra um servidor hostil, aplicações .NET e Python, proxies e leitores de arquivos. A correção do Brotli estava no repositório desde fevereiro, mas só virou versão e aviso em 21 de outubro; em 27 de outubro a CISA incluiu a falha no catálogo de vulnerabilidades exploradas.
Quando um fluxo Brotli sinaliza uma troca de bloco inválida para a própria estrutura, o decodificador vulnerável relata uma necessidade temporária de mais entrada, em vez de um erro permanente de formato, e continua anexando bytes a um buffer interno de oito bytes até escrever além do fim [1] [2]. O defeito está na biblioteca C do Brotli, das versões 1.0.3 a 1.2.0, e alcança todo software que descomprime Brotli de origem não confiável com uma cópia sem correção [3]. Atualizar só o Node.js não corrige as outras cópias da biblioteca na mesma máquina [3] [4].
A CVE-2026-111497 é uma escrita fora dos limites (out-of-bounds write) no decodificador da biblioteca Brotli, o formato de compressão do Google usado na web, em fontes e em arquivos [1] [3]. Foi relatada ao Node.js por Khai Tran e Sylvie Mayer, do OpenAI Codex Security, e corrigida em 21 de outubro de 2026 [1] [4]. O Google, autoridade que emitiu a CVE, atribuiu 9.2 em CVSS v4; o Node.js classifica como alta, e a Red Hat, como importante [3] [4].
O nome HTTP2Shell é o último de uma série: a falha foi chamada de Node2Shell quando saiu a correção do Node.js, de Python2Shell quando as bibliotecas Python entraram na lista e de HTTP2Shell quando a lista se espalhou por toda a pilha HTTP [3]. Não há defeito no protocolo HTTP/2; o nome aponta para o canal mais comum de entrega, o cabeçalho Content-Encoding: br de requisições e respostas HTTP [3] [4].
A exploração é real. Em 22 de outubro, pesquisadores demonstraram execução de código no Node.js 24.22.0 oficial para arm64 [3] [4]; em 27 de outubro, a CISA incluiu a falha no catálogo KEV, com prazo de correção para órgãos federais em 10 de novembro [4]. Não há solução de contorno no Node.js: a única correção completa é atualizar [1] [2].
A ação é: atualize o Node.js para 22.23.4, 24.22.1 ou 26.12.1, e depois procure as outras cópias do Brotli na mesma infraestrutura, como Bun, .NET, Python, Envoy e Istio, com as versões da tabela 2 [1] [3] [4]. Reinicie os processos e reconstrua as imagens de contêiner. A seção 6 traz a verificação da plataforma CiberLab, que roda em Node.js.
Não houve submissão a sandbox, execução de exploit nem análise de amostra. O CiberLab não reproduziu a falha. A causa raiz vem do aviso oficial do Node.js [1] e da análise da Akamai [2]; o alcance entre ecossistemas, do rastreador da Phoenix Security [3]; versões, cronologia e inclusão no KEV, da Cycode [4]. A verificação da seção 6, ao contrário, foi feita diretamente sobre o código-fonte e a imagem de contêiner da plataforma CiberLab.
| Campo | Valor |
|---|---|
| Identificador | CVE-2026-111497, apelidada Node2Shell, Python2Shell, HTTP2Shell e Broccoli2Shell [3] |
| Componente afetado | Decodificador C da biblioteca Brotli, versões 1.0.3 a 1.2.0 [3] |
| Classe de fraqueza | Escrita fora dos limites de buffer [1] [2] |
| Ponto de entrada | Qualquer dado Brotli de origem não confiável que chegue ao decodificador [1] |
| Impacto | Queda do processo, vazamento de memória ou execução remota de código [1] |
| Pontuação | 9.2 em CVSS v4 (Google); alta (Node.js); importante (Red Hat); 8.1 em CVSS 3.1 (Microsoft) [3] [4] |
| Descoberta | Khai Tran e Sylvie Mayer, OpenAI Codex Security [1] [4] |
| Correção upstream | Brotli 1.0.10, 1.1.1 e 1.2.1, com o novo erro BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH [2] [3] |
| Inclusão no KEV | 27 de outubro de 2026, prazo federal em 10 de novembro [4] |
A falha está numa biblioteca, então cada produto que a embute tem sua própria versão corrigida. A tabela reúne as principais:
| Produto | Versões corrigidas |
|---|---|
| Node.js | 22.23.4, 24.22.1 e 26.12.1; as linhas 25.x, 23.x, 20.x e anteriores não recebem correção [1] [2] |
| Bun | 1.4.4; afetadas de 1.0.21 a 1.4.3 [3] [4] |
| .NET | Runtime 8.0.33, 9.0.22 e 10.0.14, em atualização extraordinária de 25 de outubro; o .NET Framework não é afetado [4] |
| PowerShell | 7.4.22, 7.5.13 e 7.6.9 [4] |
| Python (PyPI) | brotli 1.2.1 e brotlicffi 1.2.1.0; o pacote da distribuição é corrigido pela distribuição [3] [4] |
| Ruby, PHP, Java | Gem brotli 0.9.1, extensão PHP brotli 0.21.1, brotli4j 1.24.0 [3] [4] |
| Envoy | 1.40.0, 1.39.3, 1.38.6, 1.37.8 e 1.36.12 [3] [4] |
| Istio | 1.31.2, 1.30.6 e 1.29.9 [3] [4] |
| Ubuntu 24.04 | libbrotli1 1.1.0-2ubuntu0.1 [3] |
Brotli é um formato de compressão sem perdas criado pelo Google, adotado pelos navegadores e servidores web como alternativa ao gzip [3]. Na web, quem envia dados comprimidos declara o formato no cabeçalho Content-Encoding, e o valor br indica Brotli [4]. O ponto que importa para esta falha é quem descomprime, porque é o decodificador que tem o defeito, e não o compressor.
inflate começa ligada [2]. Um servidor HTTP do Node sem framework não descomprime corpo de requisição sozinho [2].fetch() nativo do Node descomprime automaticamente respostas em Brotli, então um servidor malicioso ou comprometido ataca quem o consulta [2]. O HttpClient do .NET com descompressão automática faz o mesmo [3].A Brotli é distribuída como código-fonte que os projetos copiam para dentro do próprio repositório (vendoring). O Node.js, o Bun, o .NET, o Chromium, o Firefox, o DuckDB, o ClickHouse e o Apache Arrow carregam cada um a sua cópia [3]. Por isso atualizar o pacote libbrotli do sistema não corrige esses programas, e atualizar o Node.js não corrige o .NET instalado na mesma máquina [3] [4]. Cada cópia precisa da sua própria atualização.
Um fluxo Brotli é dividido em blocos, e o fluxo pode sinalizar a troca de um tipo de bloco para outro [1]. Quando essa troca é inválida para a estrutura do próprio fluxo, o decodificador vulnerável não reconhece o erro: relata que precisa de mais entrada, uma condição temporária, em vez de um erro permanente de formato [1] [2]. O decodificador em modo de fluxo (streaming) então continua anexando os bytes que chegam a um buffer interno de oito bytes, e passa a escrever além do fim dele [1].
O resultado é corrupção da memória do processo que descomprime, com três desfechos possíveis: queda, revelação de memória do processo ou execução remota de código [1]. Nenhuma autenticação é necessária: basta que o atacante consiga entregar o fluxo ao decodificador, por uma requisição ou por uma resposta [2].
A correção do Brotli introduz um código de erro próprio, BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH, e faz o decodificador rejeitar o fluxo malformado em vez de esperar mais dados [2]. Isso dá um teste simples para cópias embutidas: um arquivo decode.h do Brotli sem esse identificador é uma cópia sem correção [3].
O commit da correção entrou no repositório do Brotli em 27 de fevereiro de 2026, sem versão nem aviso; as versões corrigidas e a CVE só saíram em 21 e 22 de outubro [4]. Durante esse intervalo, a correção estava pública para quem comparasse o código, e os produtos que embutem o Brotli continuavam vulneráveis. Não espere um aviso formal para atualizar bibliotecas de análise de dados não confiáveis: acompanhe os commits de segurança dos componentes críticos.
Ter uma versão afetada não basta: é preciso que dados Brotli não confiáveis cheguem ao decodificador. A tabela reúne os caminhos documentados:
| Caminho | Quem envia o fluxo | Exposição |
|---|---|---|
Express 5 ou body-parser 2 com inflate padrão | Qualquer cliente, sem login [2] | Alta |
Koa, @fastify/compress com descompressão de requisição | Qualquer cliente [4] | Alta |
zlib.brotliDecompress() ou createBrotliDecompress() sobre entrada externa | Quem fornece a entrada [1] [2] | Alta |
| ASP.NET Core com middleware de descompressão de requisição | Qualquer cliente [3] [4] | Alta |
fetch() de URL fornecida pelo usuário | O servidor que o usuário indicar [2] [4] | Alta |
fetch() para APIs fixas de terceiros | Só um servidor comprometido ou um intermediário que quebre o TLS [2] | Baixa |
| Envoy e Istio | Clientes, se a descompressão Brotli estiver habilitada [3] [4] | Condicional |
| Leitura de Parquet, 7z ou WOFF2 de origem externa | Quem fornece o arquivo [3] | Condicional |
Apache com mod_brotli e Nginx não decodificam Brotli por padrão; eles comprimem as respostas, e não descomprimem as requisições [3].
ikow/brotli2crash só executa código em compilações enfraquecidas; nas normais, causa queda e leitura limitada de memória [3].A versão de 21 de outubro corrigiu dez falhas; além da do Brotli, três de severidade alta merecem atenção de quem faz requisições de saída [1]:
| CVE | Falha | Linhas afetadas |
|---|---|---|
| CVE-2026-105482 | dns.resolve trunca nomes com caractere NUL [1] | 26.x, 24.x e 22.x [1] |
| CVE-2026-105483 | Desvio da checagem de nome do certificado TLS quando o IP é escrito com dígitos de largura total [1] | 26.x e 24.x [1] |
| CVE-2026-106069 | Desvio da checagem de nome do certificado TLS quando o host resolve frouxamente para um IP [1] | 26.x, 24.x e 22.x [1] |
A plataforma CiberLab roda em Node.js, então foi verificada contra cada caminho da tabela 3. A conclusão é que a exposição é baixa, mas a versão do Node precisa ser confirmada e fixada.
| Ponto verificado | Estado no CiberLab | Resultado |
|---|---|---|
| Versão do Node na imagem | O Dockerfile usa a etiqueta móvel node:22-bookworm-slim, atualizada no Docker Hub em 28 de outubro; a versão em produção depende da data do último build e do uso do cache | Confirmar |
| Express, body-parser ou Koa | Nenhum deles é dependência; a aplicação usa os manipuladores de rota do Next.js | Não se aplica |
Varredor de segurança (lib/security/scanner) | Busca sites indicados pelo usuário, mas usa o módulo http com Accept-Encoding: identity e não descomprime a resposta | Não exposto |
Coleta do boletim (lib/bulletins) | Usa fetch(), que descomprime Brotli, mas só contra hosts fixos por HTTPS: NVD, CISA, CVE.org, GitHub, OSV, FIRST, CERT-EU e Exploit-DB | Exposição baixa |
| Ambiente de desenvolvimento | A estação de trabalho roda Node 24.19.0, abaixo de 24.22.1 | Vulnerável |
Troque a etiqueta móvel por node:22.23.4-bookworm-slim nos dois estágios do Dockerfile e reconstrua sem cache, para que a versão corrigida fique registrada no repositório em vez de depender de quando o build rodou [4]. Atualize o Node da estação de desenvolvimento para 24.22.1 ou superior. Não conte com a etiqueta móvel: um build feito com a camada base em cache continua no Node antigo mesmo depois de a etiqueta mudar.
Para esta falha, inventário vem antes de assinatura: a Cycode recomenda começar pela verificação de versões, e não por varredura de rede [4]. A ordem abaixo vai do mais barato ao mais caro.
Os comandos abaixo cobrem os ambientes mais comuns, inclusive dentro de contêineres e nas máquinas de build [3] [4]:
# Runtimes JavaScript
node -v
bun --version
# Python
pip show brotli brotlicffi
# .NET
dotnet --list-runtimes
# Processos com o decodificador carregado dinamicamente
# (nao encontra copias estaticas ou embutidas)
grep -l brotlidec /proc/*/maps 2>/dev/null
# Copias embutidas no codigo-fonte, inclusive submodulos:
# um decode.h sem o novo erro e uma copia sem correcao
grep -rL BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH \
$(find . -path '*brotli*' -name decode.h)
Compare com a versão corrigida do pacote da distribuição, e não com a versão do upstream, para não acusar falsamente pacotes com correção retroportada [3].
Procure no código os pontos onde o decodificador é alcançado: AddRequestDecompression, UseRequestDecompression, AutomaticDecompression com Brotli ou All, BrotliStream e BrotliDecoder no .NET; Express 5, body-parser 2, Koa e @fastify/compress no Node; fetch() sobre URL fornecida pelo usuário; e chamadas diretas a node:zlib [4].
Onde a aplicação não espera corpo comprimido, uma requisição com Content-Encoding: br já é anômala. A regra abaixo alerta esse caso em sensores com visibilidade do tráfego decifrado:
alert http $EXTERNAL_NET any -> $HOME_NET any (msg:"CIBERLAB BROTLI CVE-2026-111497 REQUEST BODY WITH CONTENT-ENCODING BR"; \
flow:to_server,established; \
http.header_names; content:"Content-Encoding"; nocase; \
http.header; content:"Content-Encoding|3a 20|br"; nocase; \
classtype:attempted-admin; sid:9000301; rev:1; \
reference:cve,2026-111497; reference:url,ciberlab.seg.br/reports/http2shell;)
Uma exploração malsucedida derruba o processo, e uma bem-sucedida tende a gerar processos filhos inesperados [1] [2]. Alerte quedas repetidas de processos Node, Bun ou .NET logo após requisições com corpo comprimido ou após buscas de saída, shells filhos desses processos e conexões de saída novas a partir dos servidores de aplicação.
Atualize cada cópia do Brotli para a versão corrigida do seu produto (tabela 2), reinicie os processos e reconstrua as imagens de contêiner com docker build --pull [4]. Reconstrua também aplicações .NET autocontidas e binários independentes do Bun, que carregam o runtime dentro de si [4]. Linhas do Node.js fora de suporte não recebem correção e precisam migrar [1] [2].
inflate: false faz o servidor recusar corpos comprimidos com HTTP 415 [2]. Isso também recusa gzip [4].Content-Encoding: br na borda: no proxy ou balanceador, para requisições que chegam [4]. Cloudflare, Azure, Imperva e Vercel publicaram regras de WAF, que só cobrem o tráfego de entrada [3].br do Accept-Encoding não basta, porque um servidor hostil manda Brotli mesmo assim; recuse respostas com Content-Encoding contendo br antes de ler o corpo [3].Regras de WAF e de proxy só inspecionam o que entra. Um serviço que busca dados de fora com fetch() recebe a resposta maliciosa por uma conexão que ele mesmo abriu, e nenhuma regra de entrada a examina [3] [4]. Não declare um serviço protegido só porque a borda recusa Content-Encoding: br: atualize o runtime.
Se houver sinal de exploração, trate o processo afetado como comprometido: gire os segredos que ele podia ler, reconstrua a instância a partir de imagem limpa com o runtime corrigido e confirme, depois do reinício, que o componente corrigido é o que está rodando [3]. Revise as conexões de saída feitas pelo servidor após a requisição ou a busca suspeita.
IDs e nomes conferidos na base oficial do MITRE ATT&CK for Enterprise [5]:
| Técnica | Nome | Relação com o caso |
|---|---|---|
| T1190 | Exploit Public-Facing Application | Corpo Brotli malformado enviado a servidor que descomprime requisições [2] [4] |
| T1203 | Exploitation for Client Execution | Resposta Brotli maliciosa descomprimida pelo fetch() do cliente [2] [4] |
| T1005 | Data from Local System | Revelação de memória do processo que descomprime [1] |
| T1499.004 | Endpoint Denial of Service: Application or System Exploitation | Queda do processo, desfecho da prova de conceito pública em compilações normais [1] [3] |
| T1588.005 | Obtain Capabilities: Exploits | Prova de conceito pública e exploit da Imperva, não publicado [3] |
Nenhuma fonte publicou IPs, domínios ou hashes ligados à exploração. Os indicadores abaixo são de exposição e de comportamento:
| Tipo | Indicador | Severidade | Contexto e ação |
|---|---|---|---|
| Vulnerabilidade | CVE-2026-111497 | Crítica | Escrita fora dos limites no Brotli, no KEV desde 27/10. Atualize cada cópia [1] [4]. |
| Requisição | Content-Encoding: br | Alta | Corpo comprimido onde a aplicação não espera; recuse na borda [4]. |
| Resposta | Content-Encoding: br em busca de saída | Média | Comum e legítimo; relevante quando o destino foi indicado por usuário [3]. |
| Artefato de código | decode.h sem BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH | Alta | Cópia embutida do Brotli sem correção [3]. |
| Host | queda de processo node, bun ou dotnet | Alta | Repetida após requisição comprimida ou busca de saída [1] [3]. |
| Host | shell filho de node, bun ou dotnet | Crítica | Possível execução de código; isole o host. |
Dockerfile e a etiqueta do Docker Hub, mas não a versão do Node em execução no servidor, que depende de quando o build rodou. O comportamento do Next.js diante de corpo de requisição com Content-Encoding: br não foi testado.HTTP2Shell, escrita fora dos limites no decodificador Brotli. Relatório técnico de análise de vulnerabilidade, versão 1.0, emitido em 2 de novembro de 2026.
CiberLab, uma iniciativa do Ciência Embarcada.
Classificado TLP:CLEAR: distribuição livre, sem restrição de compartilhamento.