Vulnerabilidad de Node.js: parche de Node2Shell y cómo comprobar la versión de Node.js (WordPress)

Node2Shell es el apodo de CVE-2026-111497, una vulnerabilidad de Node.js cuyo parche se publicó el 21 de octubre de 2026; su título oficial describe un fallo de escritura fuera de límites en el decodificador Brotli de Google. Como descomprimir datos Brotli manipulados puede derivar en ejecución remota de código, lo primero es comprobar las versiones de Node.js, Bun y libbrotli de tu servidor y actualizarlas a las versiones parcheadas.
En un servidor Nginx + Apache con WordPress 7.1.3, comprobé qué paquetes usan el decodificador Brotli y por qué ruta pasan por libbrotli las peticiones remotas de WordPress.
1️⃣ Node2Shell, la vulnerabilidad de Node.js: cómo funciona el fallo y a qué afecta
El nombre Node2Shell es un apodo que le puso el equipo de investigación de la empresa de seguridad Imperva en un artículo del 22 de octubre. El título del aviso de nodejs.org es [Brotli decoder out-of-bounds write permits remote code execution]. El fallo está en el código fuente del decodificador Brotli de Google que incluye Node.js (c/dec/decode.c), así que no se soluciona cambiando el código de tu aplicación.
Según el registro de NVD, el decodificador de Google Brotli 1.0.3 a 1.2.0 escribe más allá del final del búfer al descomprimir un flujo manipulado y corrompe la memoria de estado del decodificador adyacente. Con este fallo, un atacante remoto sin iniciar sesión puede leer el contenido de la memoria del servidor, interrumpir el servicio y ejecutar código arbitrario.
Hay dos valoraciones de gravedad. El aviso de nodejs.org le asignó el nivel High, y Google, que emitió el CVE, le dio 9,2 (Critical) según CVSS 4.0. A fecha del 23 de octubre, NVD aún no ha hecho su propio análisis, así que todavía no hay puntuación de NVD. Los tipos de debilidad son un valor de retorno sin comprobar (CWE-252) y una escritura fuera de límites (CWE-787).
Según el aviso de seguridad de nodejs.org, todas las ramas con soporte de Node.js (22.x, 24.x y 26.x) están afectadas, y las versiones parcheadas son 22.23.4, 24.22.1 y 26.12.1. El mismo aviso indica que las ramas sin soporte se consideran siempre afectadas cada vez que se publica un aviso de seguridad.
Cualquier programa que use el mismo decodificador está afectado, también fuera de Node.js. El 21 de octubre, Google publicó tres versiones de la biblioteca Brotli que corrigen el mismo CVE: 1.0.10, 1.1.1 y 1.2.1. Bun incluyó Brotli en la lista de correcciones de seguridad del anuncio de la 1.4.4 el 22 de octubre, y una issue de su repositorio indica que esa versión contiene la corrección que actualizó brotli.
| destino | Versiones afectadas | Versión corregida | Fuente |
|---|---|---|---|
| Node.js | Todas las ramas con soporte 22.x · 24.x · 26.x · ramas sin soporte | 22.23.4 · 24.22.1 · 26.12.1 | Aviso de seguridad de nodejs.org (2026-10-21) |
| Biblioteca Brotli de Google | 1.0.3 ~ 1.2.0 | 1.0.10 · 1.1.1 · 1.2.1 | Registro de NVD · versiones de google/brotli (2026-10-21) |
| Bun | Brotli en la lista de correcciones de seguridad del anuncio de la 1.4.4 | 1.4.4 | Anuncio de Bun (2026-10-22) |
| Paquete libbrotli de la distribución | Paquetes compilados a partir de Brotli 1.0.3 a 1.2.0 | La versión que publique el aviso de seguridad de la distribución (aún no hay a fecha de 2026-10-23) | Página del rastreador de seguridad de cada distribución |
Node.js por sí solo no descomprime los datos Brotli que recibe. Según el aviso de seguridad de la empresa de hosting Vercel, los puntos expuestos son aquellos en los que una aplicación o biblioteca descomprime datos comprimidos que llegan de fuera. Por ejemplo, cuando el fetch() integrado recibe una respuesta br y cuando Express 5 o body-parser 2 descomprimen el cuerpo de una petición br; el servidor HTTP predeterminado, que solo recibe, no descomprime.
A fecha del 23 de octubre no se ha confirmado ninguna explotación real. Este CVE no figura en el catálogo de vulnerabilidades explotadas conocidas de CISA (KEV, versión del 21 de octubre). El nivel de explotación que CISA asigna en el registro del CVE cambió el 23 de octubre a [prueba de concepto (PoC) pública].
Dónde está el decodificador determina lo que tienes que hacer. Los binarios oficiales de Node.js llevan el decodificador integrado en el ejecutable, y también hay compilaciones que usan la libbrotli del sistema, como el paquete nodejs de Debian. Las distribuciones de Linux instalan el mismo código fuente como paquete libbrotli, que comparten programas como curl.
2️⃣ Los sitios WordPress y PHP también están afectados: la ruta por la que las peticiones remotas descomprimen respuestas con libbrotli
Incluso un servidor WordPress sin Node.js tiene el mismo decodificador instalado como paquete libbrotli. A fecha del 23 de octubre no hay ninguna mención oficial de este CVE por parte de WordPress, curl ni PHP, y tampoco figura en la lista oficial de vulnerabilidades de curl. Por eso esta sección solo confirma la estructura con el código del núcleo, la documentación de curl y mediciones en un servidor de pruebas.
En el servidor Nginx + Apache que medí, cinco paquetes requerían la biblioteca del decodificador (libbrotlidec), y el que conecta con WordPress es libcurl.
rpm -q --whatrequires 'libbrotlidec.so.1()(64bit)' | sed 's/-[0-9].*//'freetype
brotli-devel
libjxl
libcurl
libsouplibcurl requiere libbrotli para descomprimir las respuestas HTTP comprimidas. Según la documentación oficial de curl, libcurl compilado con la biblioteca brotli descomprime automáticamente las respuestas cuyo Content-Encoding es br. La extensión curl de PHP llama directamente a ese libcurl.
ldd /usr/lib64/libcurl.so.4 | grep brotli libbrotlidec.so.1 => /lib64/libbrotlidec.so.1 (0x00007f32803ed000)
libbrotlicommon.so.1 => /lib64/libbrotlicommon.so.1 (0x00007f327f858000)php -i | grep BROTLIBROTLI => YesLa versión de libcurl que usa WordPress aparece en la fila [Versión de cURL] al desplegar la sección [Servidor] en la pestaña [Información] de [Herramientas › Salud del sitio] del panel de administración. Esa fila no muestra si hay soporte de brotli, así que compruébalo con comandos del servidor como arriba.
La fila [Versión de cURL] de la imagen muestra la versión de libcurl junto con la biblioteca SSL con la que se compiló. Según la documentación de curl, br es compatible desde la 7.57.0, así que si este valor es menor, no puede descomprimir respuestas br. Si es igual o mayor, comprueba con comandos del servidor si la compilación incluye brotli.
Cuando la biblioteca Requests del núcleo de WordPress hace una petición a una URL externa, establece el valor [CURLOPT_ENCODING] del manejador de curl como una cadena vacía. Según la documentación de curl, una cadena vacía significa aceptar todos los métodos de compresión que admite ese libcurl. Por eso, si el otro servidor envía una respuesta comprimida con br, la libbrotli del servidor WordPress la descomprime.
grep -n CURLOPT_ENCODING wp-includes/Requests/src/Transport/Curl.php110: curl_setopt($this->handle, CURLOPT_ENCODING, '');
212: curl_setopt($this->handle, CURLOPT_ENCODING, 'none');La línea 110 es el valor predeterminado de todas las peticiones, y la línea 212 solo se ejecuta cuando una petición que no pudo descomprimirse se reenvía sin compresión. No hay forma de atacarla directamente desde fuera: primero tendría que estar comprometido un servidor del que WordPress recibe respuestas, como en las comprobaciones de actualizaciones del núcleo y los plugins, oEmbed o los feeds RSS.
El flujo empieza con datos comprimidos con br que llegan de fuera. El fallo se activa en el paso en que el fetch() de Node.js, Express 5, el fetch() de Bun o la libbrotli llamada por el libcurl de WordPress los descomprime. El resultado lleva a la ejecución de código en el servidor. Las versiones parcheadas bloquean este flujo, así que lo que hay que hacer es cambiar el decodificador por una versión parcheada.
El mod_brotli de Apache solo enlaza la biblioteca del codificador (libbrotlienc), que comprime las respuestas, así que una configuración que activa la compresión con este módulo no está en la ruta del fallo del decodificador.
ldd /usr/lib64/httpd/modules/mod_brotli.so | grep brotli libbrotlienc.so.1 => /lib64/libbrotlienc.so.1 (0x00007f34b4b79000)
libbrotlicommon.so.1 => /lib64/libbrotlicommon.so.1 (0x00007f34b4a79000)3️⃣ Comprobar la versión de Node.js: si el servidor tiene Node.js, Bun y libbrotli, y en qué versión
Lo primero es hacer inventario de lo que hay instalado en el servidor. En Node.js, mira la versión con [node -v]; si el valor es inferior a 22.23.4, 24.22.1 o 26.12.1 según su rama, está afectado.
| Tu entorno | Comando de comprobación | Qué se corrige |
|---|---|---|
| Instalador de nodejs.org · nvm · imagen oficial de Docker | [node -v] | El propio Node.js (22.23.4 · 24.22.1 · 26.12.1) |
| nodejs de la distribución · línea libbrotli en ldd | [ldd $(command -v node)] | Paquete libbrotli |
| nodejs de la distribución · resultado de ldd 0 | [ldd $(command -v node)] | Paquete nodejs (tras el aviso de seguridad de la distribución) |
| Node.js Selector del hosting compartido (CloudLinux alt-nodejs) | Anuncio del proveedor · número de release del paquete | Paquete distribuido por el proveedor + reinicio de la aplicación |
| Bun | [bun --version] | Bun 1.4.4 o posterior |
| Sin Node.js (solo WordPress) | Comprobar libcurl con [ldd] | Paquete libbrotli + reinicio de php-fpm · hasta entonces, medida temporal |
| Hosting gestionado | Anuncio del proveedor · consulta | Paquetes que actualiza el proveedor |
En un PC (Windows o macOS), reinstala con el instalador de la versión parcheada de nodejs.org; con nvm, mira la versión con [nvm ls] y vuelve a instalar por número de rama, como [nvm install 24]. Las etiquetas 22, 24 y 26 de la imagen oficial node de Docker se reconstruyeron con las versiones parcheadas el 22 de octubre, así que descarga de nuevo la imagen, compila y despliega. El aviso de Vercel también recomienda actualizar a las versiones parcheadas las imágenes de contenedor que compilas tú, los entornos de CI y los entornos de desarrollo locales.
En un servidor donde instalaste Node.js con un stream de dnf module, [node -v] muestra una versión de esa rama; aunque la rama coincida, actualiza si los últimos dígitos no llegan a la versión parcheada.
node -vv24.xx.xUsa [ldd] para comprobar si Node.js usa la libbrotli del sistema o su decodificador integrado. Si la salida tiene una línea libbrotlidec, es una compilación que usa la biblioteca compartida; si da 0, como abajo, el decodificador va integrado y solo se corrige actualizando el propio Node.js.
ldd $(command -v node) | grep -c brotli0En Bun, comprueba la versión con [bun --version] y actualiza si es inferior a 1.4.4.
En los sistemas basados en RPM, mira la versión del paquete libbrotli con [rpm -q libbrotli]. Los números de versión de los paquetes los asigna la distribución, así que no los compares directamente con los números de versión de Google, sino con la versión que publica el aviso de seguridad de la distribución para este número de CVE.
rpm -q libbrotlilibbrotli-1.0.x-x.el9_x.x86_64Comprueba si la distribución ha publicado un aviso con [dnf updateinfo info --cve CVE-2026-111497]. Si la salida está vacía, ese repositorio aún no tiene un aviso para este CVE.
dnf updateinfo info --cve CVE-2026-111497Un paquete que solo incorpora la corrección sin cambiar el número de versión deja el número de CVE en su registro de cambios. Por eso se comprueba con [rpm -q --changelog libbrotli | grep CVE-2026-111497]; si aparece una línea, el paquete está parcheado.
rpm -q --changelog libbrotli | grep CVE-2026-111497A fecha del 23 de octubre, los paquetes libbrotli de Debian, Ubuntu, Red Hat y AlmaLinux aún no tienen una versión parcheada para este CVE. Alpine solo tiene versión parcheada del paquete nodejs; la fuente es la página del rastreador de seguridad de cada distribución. Por eso, ahora mismo el orden es estar atento a que salga el aviso de seguridad de la distribución y actualizar cuando salga.
En Debian y Ubuntu el paquete se llama libbrotli1, y comprobé los mismos elementos en un servidor de pruebas con Debian 12 de un hosting gestionado. En este servidor también libcurl enlazaba libbrotlidec y la extensión curl de PHP admitía brotli. El registro de cambios se consulta con [zcat /usr/share/doc/libbrotli1/changelog.Debian.gz | grep CVE-2026-111497].
dpkg -l libbrotli1 | tail -1ii libbrotli1:amd64 1.1.0-2+0~20240305.8+debian12~1.gbp62df71 amd64 library implementing brotli encoder and decoder (shared libraries)ldd /usr/lib/x86_64-linux-gnu/libcurl.so.4 | grep brotli libbrotlidec.so.1 => /lib/x86_64-linux-gnu/libbrotlidec.so.1 (0x00007fa2baac6000)
libbrotlicommon.so.1 => /lib/x86_64-linux-gnu/libbrotlicommon.so.1 (0x00007fa2b9cb9000)4️⃣ Actualizar Node.js: subir a la versión parcheada y volver a comprobar
En los hostings gestionados y compartidos, el proveedor actualiza los paquetes del servidor, así que confirma con sus avisos o con soporte si ya se aplicó el parche de Node.js y libbrotli. Si administras tú el servidor, actualiza Node.js a la versión parcheada del aviso de nodejs.org y Bun a la 1.4.4 o posterior con [bun upgrade].
El Node.js instalado desde un stream de módulos de la distribución se actualiza con algo como [dnf upgrade nodejs] cuando la versión parcheada llega a ese repositorio. En un servidor donde descargaste e instalaste Node.js directamente desde nodejs.org, sustitúyelo por la versión parcheada del mismo sitio. Las ramas sin soporte no reciben versión parcheada, así que pásate a una rama con soporte: 22, 24 o 26.
El Node.js Selector del hosting compartido (CloudLinux alt-nodejs) lleva su propia copia de brotli, así que actualizar la libbrotli del sistema no lo corrige. Según el anuncio de CloudLinux, el nuevo binario solo se ejecuta cuando ya está el paquete distribuido por el proveedor y se reinicia la aplicación. El mismo anuncio indica que en las versiones sin soporte el valor de [node --version] no cambia, así que se comprueba con el número de release del paquete.
Con el paquete libbrotli, cuando salga el aviso de seguridad de la distribución, actualiza ese paquete y reinicia los procesos que tienen la biblioteca cargada en memoria para que pasen a la nueva biblioteca. Las bibliotecas compartidas se leen cuando arranca un proceso, así que en un servidor WordPress el reinicio de php-fpm forma parte del mismo paso.
Después de actualizar, comprueba que [node -v] muestra la versión parcheada o posterior y que [rpm -q libbrotli] muestra la versión del aviso de la distribución. Comprueba si el número de CVE está en el registro de cambios con [rpm -q --changelog libbrotli | grep CVE-2026-111497]. Si reiniciaste php-fpm, revisa también el estado del servicio y la respuesta del sitio.
systemctl is-active php-fpmactivecurl -s -o /dev/null -w '%{http_code}\n' 'https://example.com/?r=1'2005️⃣ Medida temporal antes del parche: desactivar la descompresión br en las peticiones remotas de WordPress
La página del CVE de Red Hat recomienda configurar las aplicaciones para que no acepten, o no descompriman automáticamente, compresión br procedente de fuentes no fiables.
Aunque todavía no haya paquetes parcheados, ya puedes bloquear la ruta del lado de WordPress. WordPress ofrece el hook [http_api_curl], que pasa el manejador de curl en cada petición remota. Si ahí pones [CURLOPT_ENCODING] a null, se desactiva la descompresión automática de libcurl.
Si colocas el archivo de abajo en la carpeta [wp-content/mu-plugins/], se aplica a todas las peticiones remotas; el nombre del archivo es solo un ejemplo. Para deshacerlo, borra este archivo.
<?php
// no-brotli-decode.php — WordPress remoto en las peticiones libcurl Automático descompresión Desactivado(libbrotli parche versión publicada hasta)
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
add_action( 'http_api_curl', function ( $handle ) {
curl_setopt( $handle, CURLOPT_ENCODING, null );
}, 99 );Es fácil pensar que limitar [CURLOPT_ENCODING] a [gzip, deflate] deja fuera br. Ese valor solo quita br de la cabecera Accept-Encoding de la petición, así que si el otro servidor responde con br, libcurl lo descomprime tal cual lo recibe. En el sitio de pruebas, al recibir httpbin.org/brotli con ese mismo valor, la respuesta con Content-Encoding br se descomprimió y se pudo leer.
Según la documentación de curl, el único valor que desactiva la descompresión automática es null; con null no se envía la cabecera Accept-Encoding y tampoco se descomprime el cuerpo recibido. Las respuestas gzip y deflate las descomprime directamente la biblioteca Requests de WordPress y se leen igual; solo las respuestas br quedan sin descomprimir.
Este hook es el que WordPress conectó al punto [curl.before_send], al que Requests llama justo antes de ejecutar curl. Se llama en la línea 174 de Curl.php y curl se ejecuta en la 205, así que el valor de cadena vacía que el núcleo puso en la línea 110 se sobrescribe entre medias.
Comprueba si se aplica con el archivo de prueba de abajo. httpbin.org es una dirección de pruebas que devuelve las cabeceras de la petición recibida y también respuestas comprimidas con br y gzip, así que se ve de una vez qué envía WordPress y qué descomprime.
<?php
// br-check.php — WordPress remoto petición qué compresión envía si descomprime Comprobar(httpbin.org prueba Dirección)
$h = json_decode( wp_remote_retrieve_body( wp_remote_get( 'https://httpbin.org/headers' ) ), true );
echo 'Accept-Encoding: ', isset( $h['headers']['Accept-Encoding'] ) ? $h['headers']['Accept-Encoding'] : '(none)', PHP_EOL;
foreach ( array( 'brotli', 'gzip' ) as $p ) {
$r = wp_remote_get( 'https://httpbin.org/' . $p );
$readable = strpos( wp_remote_retrieve_body( $r ), ': true' ) !== false;
echo $p, ': Content-Encoding=', wp_remote_retrieve_header( $r, 'content-encoding' ), ' / ', strlen( wp_remote_retrieve_body( $r ) ), ' bytes / ', ( $readable ? 'readable' : 'not decoded' ), PHP_EOL;
}Antes de añadir el archivo, Accept-Encoding incluye br y la respuesta br se descomprime y se puede leer.
wp eval-file br-check.phpAccept-Encoding: deflate, gzip, br
brotli: Content-Encoding=br / 316 bytes / readable
gzip: Content-Encoding=gzip / 317 bytes / readableDespués de añadir el archivo, desaparece la cabecera Accept-Encoding, la respuesta br queda sin descomprimir y la respuesta gzip se sigue leyendo.
wp eval-file br-check.phpAccept-Encoding: (none)
brotli: Content-Encoding=br / 199 bytes / not decoded
gzip: Content-Encoding=gzip / 272 bytes / readableCuando hayas actualizado al paquete libbrotli parcheado de la distribución y reiniciado php-fpm, borra este archivo para volver a la configuración original.
🔢 FAQ & contenido recomendado
Seguridad
Vulnerabilidad de OpenSSH CVE-2024-6387
Cómo bloqueé temporalmente el fallo de OpenSSH, apliqué el parche y lo comprobé.
Seguridad
Seguridad en WP: seguridad de wp-admin
Ajustes que protegen el panel de administración cambiando la URL de acceso, limitando los intentos de inicio de sesión y bloqueando XML-RPC.
Servidor
Actualización de Alma Linux: 9.4 → 9.8
Los pasos que seguí para actualizar AlmaLinux 9.4 a 9.8 y qué comprobar.
ℹ️ Aviso de afiliados
El contenido de este sitio incluye enlaces de afiliado. Cuando un visitante compra un producto o servicio a través de uno de ellos, el sitio recibe una comisión del vendedor. El importe que paga el comprador(baja durante las promociones ↓)no aumenta. Los precios, descuentos y existencias publicados corresponden al momento de redacción y pueden variar, así que confírmalos con el vendedor antes de comprar. Los productos se eligen y evalúan con criterios propios, y las comisiones no influyen en el orden ni en el contenido de las reseñas.