Escolha de linguagem levando também em conta a segurança sob a perspectiva do HTTP2Shell

12 min

language: ja bn en es hi pt ru zh-cn zh-tw

Olá, sou o Munou.

Recentemente, foi descoberta uma vulnerabilidade no decodificador Brotli, que parece estar sendo chamada de Node2Shell ou HTTP2Shell.

Trata-se da CVE-2026-111497: ao descomprimir dados Brotli malformados, ocorre uma escrita fora dos limites, que pode causar a falha do processo, o vazamento de informações da memória e, dependendo das condições, a execução arbitrária de código.

Ou seja, como o problema está na implementação do decodificador, os clientes HTTP também precisam ser atualizados. Se o Brotli estiver ativado no lado do servidor, acredito que também seja necessário providenciar a atualização, mas, em termos de impacto, parece-me que o lado do cliente HTTP é mais afetado.

Desta vez, mais do que falar da vulnerabilidade em si, quero comentar como, quando surgem problemas desse tipo, a facilidade de distribuir as correções pode variar bastante de acordo com a linguagem adotada e a configuração das bibliotecas.

Atualizar a biblioteca do sistema operacional nem sempre resolve tudo

Por exemplo, se o cliente utiliza a libbrotli como biblioteca compartilhada do sistema operacional, em geral é possível resolver o problema instalando a versão corrigida pelo gerenciador de pacotes de cada sistema. Claro, isso pressupõe que a distribuição já disponibilize a correção, e pode ser necessário reiniciar os processos em execução.

No entanto, isso se aplica quando a aplicação faz vinculação dinâmica com a biblioteca compartilhada do sistema operacional.

Mesmo usando o mesmo Brotli, se a biblioteca tiver sido incorporada ao executável por vinculação estática durante a compilação, atualizar a libbrotli do sistema operacional não altera o conteúdo do binário. É necessário esperar que o desenvolvedor distribua uma versão corrigida ou atualizar as dependências e recompilar por conta própria.

Portanto, o importante não é simplesmente saber “em que linguagem isso foi escrito”, mas sim “onde está o código problemático e a atualização de quem vai substituí-lo”.

Rust facilita a distribuição de binários, mas há outro lado

No caso de Rust, os crates comuns escritos em Rust costumam ser incorporados ao executável, o que facilita a distribuição como um único binário. Na verdade, eu também considero essa praticidade muito boa.

Por outro lado, quando é descoberta uma vulnerabilidade no código de uma dependência, há casos em que atualizar apenas os pacotes do sistema operacional não resolve. Para corrigir o código das dependências incluído no binário, em princípio é necessário recompilar e redistribuir a aplicação.

No caso deste problema com o Brotli, a situação também é diferente entre um wrapper como brotli2, que chama a implementação em C a partir de Rust, e uma biblioteca como brotli, implementada em Rust. Além disso, mesmo quando se usa a implementação em C, a forma de atualização muda conforme se faz vinculação dinâmica com uma biblioteca compartilhada ou vinculação estática também do código C. Não significa que uma aplicação seja necessariamente afetada por esta vulnerabilidade da implementação em C só porque foi feita em Rust.

Para começar, a segurança de memória do Rust e a facilidade de atualizar o código das dependências são questões distintas.

Embora eu considere Rust extremamente útil para desenvolver bibliotecas e partes em que o desempenho é importante, ao usá-lo em aplicações amplamente distribuídas, é preciso pensar também nas atualizações de segurança após a distribuição. A contrapartida da vantagem de criar binários rápidos e autossuficientes é que a responsabilidade por fazer as correções chegarem aos usuários também passa a recair mais sobre a aplicação.

No Node.js, poder atualizar o runtime facilita as coisas

O Node.js também foi afetado desta vez. Como a implementação do Brotli está incorporada ao próprio Node.js, atualizar apenas a libbrotli do sistema operacional não resolve: é necessário atualizar para uma versão corrigida do Node.js.

Neste caso, foram disponibilizadas as versões 22.23.4, 24.22.1 e 26.12.1 nas versões de segurança do Node.js.

No entanto, em uma aplicação Node.js típica, basta atualizar o runtime Node.js e reiniciar a aplicação para aplicar a correção do Brotli incluído nele. Claro, a situação é diferente se algum pacote npm incluir outra implementação vulnerável ou se o próprio Node.js estiver empacotado no executável.

Nesse aspecto, sinto que, de certa forma, a operação é mais simples do que com a distribuição de binários independentes em Rust. Isso porque, em alguns casos, é possível resolver o problema atualizando um runtime comum, sem exigir que cada usuário recompile uma grande quantidade de aplicações.

Dito isso, o simples uso do Node.js não aciona esta vulnerabilidade. O problema surge quando há um caminho de execução que descomprime dados Brotli malformados. Tenho a impressão de que não são tão comuns as implementações de servidores HTTP que descomprimem explicitamente as requisições recebidas com Brotli. Por outro lado, o fetch(), usado para acessar serviços externos como cliente, descomprime automaticamente as respostas comprimidas com Brotli, então não basta olhar apenas para o lado do servidor que fica à escuta de conexões.

Antigamente eu era mais contrário ao Node.js, mas a abrangência recente de suas APIs padrão tem mudado bastante a minha impressão. Com fetch(), Web Streams, executor de testes e outros recursos, o próprio Node.js agora cobre boa parte do que antes dependia de pacotes externos.

Reduzir o número de pacotes de dependência também reduz a quantidade de componentes cujas atualizações precisam ser gerenciadas. Comparado ao passado, o Node.js tem me causado uma impressão bastante positiva como opção para desenvolver aplicações.

Continuo achando que o Go é muito bem projetado

Nesse sentido, acho que Go é uma linguagem projetada com bastante atenção ao desenvolvimento de aplicações.

A biblioteca padrão, incluindo net/http, é prática, e muitas vezes é possível implementar a maior parte de um cliente ou servidor HTTP apenas com os pacotes padrão, sem introduzir uma grande quantidade de dependências externas. Basicamente, o cliente HTTP padrão descomprime automaticamente gzip, e não utiliza por padrão o decodificador C do Google Brotli envolvido neste caso.

Claro, em Go também é comum incorporar o código das dependências ao binário, portanto, se houver uma vulnerabilidade nesse código, será necessário recompilar e redistribuir. Usar Go não significa que basta atualizar as bibliotecas do sistema operacional, e, se forem usadas bibliotecas externas por meio de cgo, a questão da vinculação dinâmica ou estática também se apresenta da mesma forma.

Ainda assim, considero bastante significativo, não apenas para a manutenção, mas também para a segurança, que seja possível fazer tanta coisa apenas com os pacotes padrão e manter reduzido o número de dependências necessárias.

O desempenho de execução não é o único fator a considerar na escolha de uma linguagem

Não estou dizendo que Rust é ruim, nem que Node.js ou Go são seguros. Para começar, esta vulnerabilidade está na implementação em C do Brotli, e não é possível determinar se algo é afetado apenas pelo nome da linguagem.

No entanto, acredito que, ao escolher uma linguagem, além do desempenho de execução, do uso de memória e da eficiência de desenvolvimento, também vale avaliar quem, em última instância, fará as atualizações das bibliotecas de dependência chegarem aos usuários e de que forma.

No caso de bibliotecas compartilhadas, pode ser possível corrigir tudo de uma vez com uma atualização de pacotes do sistema operacional. Já os binários com vinculação estática podem ser autossuficientes, incluindo o código das dependências, mas exigem uma atualização de cada aplicação quando há uma correção. Abordagens baseadas em runtime, como o Node.js, também têm a vantagem de permitir uma atualização comum do próprio runtime, mas, por outro lado, uma vulnerabilidade nele pode ter um impacto amplo.

No fim das contas, é uma questão de projeto: o que incorporar, onde incorporar, como distribuir e até onde assumir a responsabilidade pela manutenção.

Pessoalmente, embora eu queira usar Rust ativamente para criar bibliotecas e componentes que exigem desempenho, este caso do HTTP2Shell me fez perceber novamente que também há razões bastante fortes para escolher Go ou Node.js para aplicações em geral.

Related Posts