Selección de lenguajes teniendo también en cuenta la seguridad desde la perspectiva de HTTP2Shell
Hola, soy Munou.
Hace unos días se descubrió una vulnerabilidad en el decodificador de Brotli, a la que al parecer se está llamando Node2Shell o HTTP2Shell.
Se trata de CVE-2026-111497: al descomprimir datos Brotli malformados se produce una escritura fuera de los límites, lo que puede provocar la caída del proceso, la filtración de información de la memoria y, en determinadas condiciones, la ejecución de código arbitrario.
Es decir, como el problema está en la implementación del decodificador, también es necesario actualizar los clientes HTTP. Si se tiene habilitado Brotli en el lado del servidor, creo que también habrá que aplicar la actualización, pero, en cuanto al alcance del impacto, me parece que es mayor en los clientes HTTP.
Esta vez, más que hablar de la vulnerabilidad en sí, quiero comentar cómo, cuando surgen problemas de este tipo, la facilidad para hacer llegar las correcciones cambia bastante según el lenguaje elegido y la configuración de las bibliotecas.
No siempre basta con actualizar las bibliotecas del sistema operativo
Por ejemplo, si en el lado del cliente se utiliza libbrotli como biblioteca compartida del sistema operativo, por lo general se puede solucionar instalando la versión corregida desde el gestor de paquetes del sistema operativo correspondiente. Por supuesto, esto presupone que la distribución ya ofrece la corrección, y puede ser necesario reiniciar los procesos que estén en ejecución.
Sin embargo, esto se aplica cuando la aplicación está enlazada dinámicamente con la biblioteca compartida del sistema operativo.
Aunque se utilice el mismo Brotli, si la biblioteca se ha enlazado estáticamente con el ejecutable durante la compilación, actualizar libbrotli en el sistema operativo no cambia el contenido del binario. Hay que esperar a que el desarrollador distribuya una versión corregida o actualizar las dependencias y volver a compilar por cuenta propia.
Por tanto, lo importante no es simplemente «en qué lenguaje está escrito», sino «dónde se encuentra el código problemático y quién debe actualizarlo para que se sustituya».
Rust facilita la distribución de binarios, pero hay otra cara de la moneda
En Rust, los crates habituales escritos en Rust suelen incorporarse al ejecutable, lo que facilita distribuirlo como un único binario. De hecho, esa comodidad me parece una gran ventaja.
Por otro lado, cuando se descubre una vulnerabilidad en el código del que se depende, hay casos en los que actualizar únicamente los paquetes del sistema operativo no la corrige. Para corregir el código de las dependencias incluido en el binario, en principio es necesario volver a compilar y distribuir la aplicación.
También en este caso de Brotli, la situación es distinta entre un envoltorio como brotli2, que llama a la implementación en C desde Rust, y una biblioteca como brotli, implementada en Rust. Además, incluso si se utiliza la implementación en C, el método de actualización cambia según se enlace dinámicamente con una biblioteca compartida o se enlace estáticamente también el código C. No significa que, por estar escrito en Rust, un programa esté necesariamente afectado por esta vulnerabilidad de la implementación en C.
Para empezar, la seguridad de memoria de Rust y la facilidad para actualizar el código de las dependencias son cuestiones distintas.
Aunque creo que Rust es muy beneficioso para desarrollar bibliotecas y para las partes donde el rendimiento es importante, cuando se utiliza en aplicaciones de amplia distribución también hay que tener en cuenta las actualizaciones de seguridad posteriores a su distribución. La contrapartida de poder crear binarios rápidos y autosuficientes es que la responsabilidad de hacer llegar las correcciones a los usuarios recae en mayor medida en la aplicación.
En Node.js resulta cómodo poder actualizar el entorno de ejecución
Node.js también está afectado en esta ocasión. Como la implementación de Brotli está incorporada en el propio Node.js, no basta con actualizar libbrotli en el sistema operativo: es necesario actualizar a una versión corregida de Node.js.
En este caso se han publicado las versiones 22.23.4, 24.22.1 y 26.12.1 en los lanzamientos de seguridad de Node.js.
Ahora bien, en una aplicación típica de Node.js, basta con actualizar el entorno de ejecución Node.js y reiniciar la aplicación para incorporar la corrección de Brotli incluida en él. Por supuesto, la situación es distinta si algún paquete npm incluye otra implementación vulnerable o si el propio Node.js viene empaquetado dentro del ejecutable.
En cierto sentido, este es un aspecto que me parece más fácil de gestionar que la distribución de binarios independientes de Rust. En algunos casos se puede resolver con una actualización del entorno de ejecución compartido, sin tener que pedir a cada usuario que vuelva a compilar un gran número de aplicaciones.
Eso sí, el mero hecho de utilizar Node.js no desencadena esta vulnerabilidad. El problema aparece cuando existe una ruta que descomprime datos Brotli malformados. Tengo la impresión de que no hay tantas implementaciones de servidores HTTP que se tomen la molestia de descomprimir con Brotli las solicitudes entrantes, pero, por otro lado, fetch(), que accede a servicios externos como cliente, descomprime automáticamente las respuestas comprimidas con Brotli. Por tanto, no basta con fijarse únicamente en el lado del servidor que escucha las conexiones.
Antes era más bien detractor de Node.js, pero al ver lo completas que son sus API estándar últimamente, mi impresión ha cambiado bastante. Funciones como fetch(), Web Streams y el ejecutor de pruebas permiten cubrir solo con Node.js buena parte de lo que antes requería paquetes externos.
Si se reduce el número de paquetes de los que se depende, también se reduce el número de componentes cuyas actualizaciones hay que gestionar. En comparación con antes, Node.js me resulta una opción bastante atractiva para escribir aplicaciones.
Sigo pensando que Go está muy bien diseñado
En ese sentido, creo que Go es un lenguaje diseñado teniendo muy presente el desarrollo de aplicaciones.
Su biblioteca estándar, empezando por net/http, es práctica, y a menudo permite implementar la mayor parte de un cliente o servidor HTTP utilizando solo paquetes estándar, sin incorporar una gran cantidad de dependencias externas. El cliente HTTP estándar descomprime automáticamente, por lo general, solo gzip, y tampoco utiliza de forma predeterminada el decodificador C de Google Brotli afectado en este caso.
Por supuesto, en Go también es habitual incorporar el código de las dependencias al binario, de modo que, si ese código tiene una vulnerabilidad, será necesario volver a compilar y distribuir. No significa que, por ser Go, baste con actualizar las bibliotecas del sistema operativo; y si se utilizan bibliotecas externas mediante cgo, también surge la misma cuestión de si el enlace es dinámico o estático.
Aun así, creo que poder cubrir una amplia variedad de necesidades solo con los paquetes estándar y mantener fácilmente un número reducido de dependencias necesarias es una ventaja muy importante, no solo para el mantenimiento, sino también para la seguridad.
El rendimiento de ejecución no es lo único que importa al elegir un lenguaje
No estoy diciendo que Rust sea malo ni que Node.js o Go sean seguros. Para empezar, esta vulnerabilidad está en la implementación en C de Brotli, y no se puede determinar si algo está afectado únicamente por el nombre del lenguaje.
Pero creo que, al elegir un lenguaje, además del rendimiento de ejecución, el uso de memoria y la eficiencia del desarrollo, también conviene evaluar quién se encargará finalmente de hacer llegar a los usuarios las actualizaciones de las bibliotecas dependientes y cómo lo hará.
En el caso de las bibliotecas compartidas, puede ser posible corregirlas de una vez mediante una actualización de paquetes del sistema operativo. Por otro lado, aunque un binario enlazado estáticamente puede ser autosuficiente e incluir el código de sus dependencias, cuando hay una corrección es necesario actualizar cada aplicación. Los modelos basados en un entorno de ejecución, como Node.js, también tienen la ventaja de permitir actualizar ese entorno de forma compartida, pero, a cambio, una vulnerabilidad en el propio entorno puede tener un impacto amplio.
Al final, es una cuestión de diseño: qué se incorpora y dónde, cómo se distribuye y hasta dónde se asume la responsabilidad de mantenerlo.
Personalmente, aunque quiero utilizar Rust de forma activa para crear bibliotecas y componentes que requieran rendimiento, este caso de HTTP2Shell me ha hecho pensar de nuevo que también hay razones de peso para elegir Go o Node.js en aplicaciones de uso general.