Ilustración abstracta de pintura fluida en tonos rojo, naranja y crema para la noticia: El fallo de Brotli salta de Node.js a .NET, FFmpeg y ClickHouse

Vulnerabilidades

El fallo de Brotli salta de Node.js a .NET, FFmpeg y ClickHouse

El fallo CVE-2026-111497 de Brotli alcanza ya .NET, FFmpeg, ClickHouse, Python y Envoy, y JFrog lo dispara en FFmpeg con una imagen JPEG XL

JFrog Security Research consiguió el 25 de octubre de 2026 controlar el puntero de instrucción de FFmpeg con un único fichero JPEG XL manipulado, la primera vía documentada por la que CVE-2026-111497, el fallo del decodificador Brotli que el blog cubrió como Node2Shell, llega a un proceso sin pasar por HTTP. El mismo día Microsoft publicó parches fuera de ciclo para .NET 8, 9 y 10, mientras ClickHouse y Apache Arrow seguían caídos ante una entrada Brotli diseñada para ello.

Salida de AddressSanitizer de FFmpeg con libjxl fallando en BrotliDecoderStateCleanup al abrir un JPEG XL manipulado
Fuente: JFrog Security Research, «Brotli2Shell: Beyond Node(2Shell) and HTTP(2Shell)»

Cómo una imagen JPEG XL lleva el fallo de Brotli hasta FFmpeg

El formato JPEG XL permite guardar metadatos comprimidos en una caja llamada brob, y libjxl, la biblioteca de referencia que FFmpeg usa para leer esas imágenes, los descomprime con el decodificador Brotli de Google. Aviv Engelberg, Eddy Tsalolikhin y Ofri Ouzan, del equipo de investigación de JFrog, construyeron con Python un fichero con firma válida, caja ftyp, una caja brob con una etiqueta Exif y bytes Brotli manipulados, y un codestream jxlc correcto para que el decodificador llegara hasta el final. El recorrido que describe el informe de JFrog es FFmpeg, libjxl, la caja brob y por último BrotliDecoderDecompressStream().

La captura de arriba es la salida de AddressSanitizer al abrir ese fichero con una compilación instrumentada de FFmpeg sobre libjxl 0.12.0. El proceso falla al liberar memoria dentro de BrotliDecoderStateCleanup con un puntero construido a partir de bytes 0x41, la marca clásica de que el contenido controlado por el atacante ha acabado en una dirección que el programa va a usar. JFrog es prudente con el alcance. Lo que demuestra es corrupción de memoria y control del puntero de instrucción, y llegar a ejecución de código exigiría más trabajo y depende de la compilación y de las mitigaciones del sistema.

El cambio de lectura es de fondo. Hasta ahora la conversación giraba en torno a cabeceras Content-Encoding: br, proxies y clientes HTTP. Con este resultado, cualquier servicio que genere miniaturas, transcodifique vídeo o procese imágenes subidas por usuarios con un FFmpeg compilado contra libjxl entra en el perímetro, aunque no sirva ni una sola respuesta comprimida. Según el registro de broccoli2shell.com, libjxl corrigió su rama principal el 24 de octubre sin publicar todavía una versión nueva.

JFrog lo anunció en X con el nombre Brotli2Shell, el cuarto alias que recibe el mismo CVE tras Node2Shell, HTTP2Shell y Broccoli2Shell.

ClickHouse cae con una petición y Apache Arrow entra en bucle

La base de datos analítica ClickHouse acepta cuerpos de petición comprimidos por HTTP. JFrog envió al puerto 8123 un cuerpo de unos 23 MB con la cabecera Content-Encoding: br y el servidor murió con una violación de segmento en el momento de liberar el estado del decodificador, cortando todas las consultas que tenía en curso. La captura muestra la versión 26.10.2.103, la última publicada cuando se hizo la prueba.

Terminal con ClickHouse 26.10.2.103 cayendo por segmentation fault tras una petición HTTP con Content-Encoding br
Fuente: JFrog Security Research, «Brotli2Shell: Beyond Node(2Shell) and HTTP(2Shell)»

ClickHouse fusionó el 25 de octubre la actualización a Brotli 1.2.1, marcada como corrección crítica y llevada a las ramas 26.10, 26.9, 26.8 y 26.3. El propio PR sitúa la corrección en las compilaciones 26.10.3.6, 26.9.26.18, 26.8.35.17 y 26.3.61.13. Quien tenga el puerto HTTP de ClickHouse accesible desde redes que no controla debería comprobar que su versión es igual o superior a esas.

Apache Arrow es el caso sin red. La biblioteca, que pyarrow empaqueta con una copia propia de Brotli 1.0.9, descomprime con ella páginas de ficheros Parquet. Una entrada de 534 bytes, derivada del vector de regresión que publicó Node.js, declara un bloque de 16 MB con una secuencia de cambio de bloque malformada y deja el hilo al 100 % de CPU sin terminar nunca. Con Brotli 1.2.1 la misma entrada devuelve un error en menos de un segundo.

Sesión de Python con pyarrow 26.0.0 atrapado en un bucle infinito al descomprimir una entrada Brotli de 534 bytes
Fuente: JFrog Security Research, «Brotli2Shell: Beyond Node(2Shell) and HTTP(2Shell)»

Para un pipeline de datos que ingiere Parquet de terceros, el efecto es una denegación de servicio silenciosa, un job que no falla y se queda colgado hasta que alguien lo mata. A 26 de octubre, Arrow no tenía versión corregida y la propuesta de arreglo estaba aprobada pero sin fusionar.

El parche fuera de ciclo de .NET y lo que Microsoft deja desactivado

Microsoft publicó el domingo 25 de octubre las versiones 8.0.33, 9.0.22 y 10.0.14 de .NET, además de la Release Candidate 3 de .NET 11, según el anuncio del equipo de .NET. El runtime incluye su propia copia del decodificador, y JFrog lo comprobó con una aplicación de consola que alimenta un stream en dos fases. La primera deja el decodificador a medias y la segunda dispara el cambio de bloque inválido en DecodeBlockTypeAndLength(). El resultado fue un SIGSEGV nativo que mata el proceso, algo que el código gestionado no puede capturar como excepción.

Contenedor con .NET 10.0.13 en Ubuntu terminando con segmentation fault al descomprimir un stream Brotli en dos fases
Fuente: JFrog Security Research, «Brotli2Shell: Beyond Node(2Shell) and HTTP(2Shell)»

La captura corresponde a .NET 10.0.13 con la copia de Brotli 1.1.0 que lleva dentro el runtime. Las vías de entrada son tres, la descompresión de peticiones de ASP.NET Core, la descompresión automática de respuestas de HttpClient (HttpClientHandler.AutomaticDecompression) y las API de Brotli de System.IO.Compression. Microsoft matiza que «Request decompression and automatic response decompression are not enabled by default» (la descompresión de peticiones y la automática de respuestas no están activadas por defecto), así que la exposición depende de la configuración de cada aplicación o del uso directo de BrotliStream.

Microsoft clasifica el fallo de forma distinta al resto del ecosistema. Su ficha en el Security Update Guide lo describe así, «Out-of-bounds write in .NET allows an unauthorized attacker to elevate privileges over a network» (una escritura fuera de límites en .NET permite a un atacante no autorizado elevar privilegios por la red), mientras que Google, como CNA, habla de ejecución de código. El anuncio aclara que «.NET Framework is not affected» (.NET Framework no está afectado) y añade una segunda corrección, CVE-2026-114148, para metadatos de propiedad inseguros en los tarballs de Linux, un CVE que NVD aún no había publicado el 26 de octubre.

Quedan dos detalles operativos. Las aplicaciones autocontenidas llevan el runtime empaquetado y no se corrigen instalando el nuevo en el servidor, hay que recompilarlas y volver a publicarlas. Y las compilaciones desde el código fuente de GitHub seguían afectadas a 26 de octubre, porque la incidencia para portar el arreglo a las ramas públicas del repositorio dotnet/runtime continuaba abierta.

Python, Envoy y las copias de Brotli que nadie declaró

El paquete Brotli de PyPI y su alternativa brotlicffi quedaron corregidos en las versiones 1.2.1 y 1.2.1.0, como recoge el aviso GHSA-9v8h-qqgm-4m35, publicado el 22 de octubre y actualizado al día siguiente. El riesgo en Python es que nadie llama a Brotli a propósito. Si cualquiera de los dos paquetes está instalado, urllib3 (y con ella requests), httpx con su extra brotli, aiohttp y Scrapy anuncian soporte br a los servidores y descomprimen la respuesta sin intervención del programador. «Your code doesn't have to import either package» (tu código no tiene que importar ninguno de los dos paquetes), resume el análisis de Aikido, que cifra en 82,8 millones las descargas mensuales de Brotli.

Aikido dio la voz de alarma en X el 23 de octubre, cuando las herramientas de auditoría de dependencias aún no relacionaban el CVE con los paquetes de PyPI.

Nadie ha intentado todavía convertir el fallo en ejecución de código dentro de la extensión de Python. Los investigadores lo confirmaron con AddressSanitizer y se quedaron ahí, y la propia Aikido pide «Treat it as urgent anyway» (trátalo como urgente de todos modos). Es el mismo patrón que vimos con los intentos de explotar PHP2Shell, donde las PoC públicas tardaron en funcionar contra servidores reales sin que eso redujera el número de sistemas expuestos.

En la capa de red, Envoy corrigió el 23 de octubre las ramas 1.36 a 1.39 y publicó la 1.40.0. Su aviso de seguridad limita el impacto a «Envoy deployments that have Brotli decompressor configured» (despliegues de Envoy con el descompresor Brotli configurado) y atribuye el hallazgo a OpenAI Security Research. En una malla de servicios ese filtro suele llegar a través de un EnvoyFilter que alguien añadió hace tiempo, una configuración que conviene repasar con la guía de seguridad en Kubernetes del blog en la mano.

Wiz, que publicó su análisis el 25 de octubre, calcula con datos de sus clientes que el 91 % de los entornos cloud tiene algún recurso vulnerable y el 82 % expone Node.js a internet. Son cifras de su propia base instalada, sin desglose por país, y no hay un recuento público de exposición en España cuando se escribe esta noticia. Tampoco hemos encontrado, a 26 de octubre, un aviso específico de INCIBE-CERT o del CCN-CERT sobre el CVE.

Este es el segundo capítulo del hilo que abrimos con la demostración de Imperva sobre Node.js 24.22.0, y los datos de entonces se mantienen. La lista de software afectado ha crecido con cada día de la semana. El mantenedor anónimo de broccoli2shell.com contabilizaba a 26 de octubre el gem de Ruby (corregido en 0.9.1 el 24 de octubre), la extensión brotli de PHP (0.21.1, también el 24), MuPDF 1.28.x y Ghostscript entre los ya arreglados, y DuckDB, 7-Zip-zstd, p7zip, EDK II y la compilación con Makefile de woff2 entre los que seguían sin solución. Los ports de Brotli a Java, JavaScript y C# no están afectados, el de Go solo provocaba una caída y se corrigió el 23 de octubre, y el de Rust devuelve un error. La ejecución de código solo es posible en el decodificador en C.

Dato

Valor

CVE

CVE-2026-111497 (Node2Shell, HTTP2Shell, Broccoli2Shell o Brotli2Shell)

CVSS 4.0 (Google, CNA)

9,2 crítica (AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H)

Estado en NVD

Awaiting Analysis, última modificación el 26 de octubre de 2026

Producto

Decodificador Brotli en C de Google (c/dec/decode.c) y las copias que integran otros proyectos

Versiones afectadas

Brotli 1.0.3 a 1.2.0 y el software que las empaqueta

Versiones corregidas

Brotli 1.0.10, 1.1.1 y 1.2.1. .NET 8.0.33, 9.0.22 y 10.0.14. PyPI Brotli 1.2.1 y brotlicffi 1.2.1.0. Envoy 1.40.0, 1.39.3, 1.38.6, 1.37.8 y 1.36.12

Sin versión corregida a 26 de octubre

Apache Arrow, libjxl, DuckDB, 7-Zip-zstd, p7zip y EDK II

Explotación activa

No observada a 26 de octubre de 2026, fuera del catálogo KEV de CISA

Gravedad

Crítica según el CNA. Microsoft la trata como elevación de privilegios por red

Cómo localizar el decodificador de Brotli en servidores, imágenes y binarios

El problema de este CVE es de inventario. Cada proyecto lleva su propia copia de la biblioteca y actualizar la del sistema no arregla las demás, igual que ocurre con los runtimes empaquetados en imágenes de contenedor que ya pesaron en la versión troyanizada de LiteLLM. Este es el orden de urgencia que proponemos.

  1. Actualiza .NET a 8.0.33, 9.0.22 o 10.0.14, recompila las aplicaciones autocontenidas y reconstruye las imágenes de contenedor. Comprueba el resultado con dotnet --list-runtimes en cada host y dentro de cada imagen.
  2. Revisa los servicios que procesan ficheros de usuarios con FFmpeg, libjxl, MuPDF o Ghostscript, sobre todo generadores de miniaturas y transcodificadores, y aíslalos o desactiva el soporte JPEG XL hasta tener una compilación con Brotli 1.2.1.
  3. Actualiza ClickHouse a una compilación que incluya el PR 127419 y, mientras tanto, limita el acceso al puerto HTTP 8123 a las redes de tus aplicaciones.
  4. En Python, instala Brotli 1.2.1 y brotlicffi 1.2.1.0 o desinstala ambos paquetes si no los necesitas, para que los clientes HTTP vuelvan a gzip y deflate. Quita también extras como httpx[brotli], que los reinstalarían en el siguiente despliegue.
  5. Actualiza Envoy a 1.40.0, 1.39.3, 1.38.6, 1.37.8 o 1.36.12 y busca los EnvoyFilter que activen el descompresor Brotli.
  6. Detén los jobs que ingieren Parquet de fuentes externas con pyarrow o pon un límite de tiempo a cada descompresión hasta que Arrow publique versión corregida.
  7. Notifica a INCIBE-CERT cualquier caída o comportamiento anómalo que relaciones con datos Brotli, o al CCN-CERT si eres sector público.

Para encontrar copias ocultas, estos comandos recorren paquetes del sistema, procesos en ejecución, entornos de Python y mallas de Envoy. El último está tomado del análisis de Aikido.

# Paquetes de sistema con Brotli (Debian/Ubuntu, RHEL/Fedora, Alpine)
dpkg -l | grep -i brotli
rpm -qa | grep -i brotli
apk info -v | grep -i brotli

# Procesos que tienen cargado el decodificador ahora mismo
sudo grep -l brotlidec /proc/*/maps 2>/dev/null

# Python: versión instalada y qué paquete la arrastra
pip show Brotli brotlicffi
pipdeptree -r -p Brotli

# Envoy/Istio: filtros que activan el descompresor Brotli
kubectl get envoyfilters -A -o yaml | grep -nE 'compression\.brotli\.decompressor|brotli\.decompressor\.v3\.Brotli'

JFrog ha publicado también un escáner de código abierto que busca llamadas de descompresión en código JavaScript y Python y símbolos BrotliDecoderDecompress en binarios ELF. No comprueba si la versión está parcheada, solo dónde se usa el decodificador, y se ejecuta así.

pip install -r brotli2shell_cve_2026_111497/requirements.txt
python brotli2shell_cve_2026_111497/cve_2026_111497.py /srv/apps --exclude tests --json

Un resultado applicable no prueba que el servicio sea explotable, pero marca los puntos que hay que revisar primero, algo que encaja con cualquier programa de herramientas DevSecOps que ya tenga un inventario de dependencias por imagen. Ese inventario es lo que más tiempo va a costar en las distribuciones. Según el seguimiento de Aikido del 26 de octubre, Debian y Alpine ya habían publicado paquetes corregidos, Fedora y Red Hat solo en parte, y Ubuntu, SUSE y los repositorios estándar de RHEL aún no tenían arreglo.

Una corrección de febrero que nadie leyó como fallo de seguridad

El arreglo de raíz entró en el repositorio de Brotli en febrero de 2026, cuando Khai Tran y Sylvie Mayer, de OpenAI Codex Security, lo encontraron, según la cronología de Aikido y de broccoli2shell.com. Nadie lo trató entonces como vulnerabilidad y ninguna versión de Brotli lo incluyó hasta el 21 de octubre. Algunos proyectos, Chromium y Firefox entre ellos, lo heredaron sin saberlo al sincronizar con el código de Google antes del anuncio.

Esa ventana de ocho meses explica la lista desigual de esta semana. Quien actualiza dependencias de forma continua estaba protegido antes de que existiera el CVE, y quien congela versiones depende ahora de que cada mantenedor publique. Ya pasó con el renderizador .org de GitLab, donde un motor de expresiones de terceros arrastró el fallo durante años, y en los dos casos la parte cara fue saber dónde estaba la copia vulnerable.

La ficha del CVE en NVD seguía en análisis el 26 de octubre, con la puntuación 9,2 de Google como única métrica, y CISA no lo había incluido en KEV. No hay explotación registrada fuera de los laboratorios, y las únicas ejecuciones de código documentadas siguen siendo las de Node.js que contamos el 23 de octubre.

Temas

No hay comentarios todavía