
Un análisis de CVE-2025-55182 y CVE-2025-66478 -- las vulnerabilidades detrás de React2Shell. Herramientas, información técnica, etc.
CVE-2025-55182 (React) y CVE-2025-66478 (Next.js)
Este repositorio contiene código de exploit únicamente para pruebas de seguridad autorizadas y fines educativos.
Ver también: Múltiples investigadores han publicado análisis de esta vulnerabilidad. Consulta la sección Referencias para obtener perspectivas adicionales, técnicas de explotación y métodos de detección.
El miércoles 3 de diciembre de 2025 se reveló públicamente una vulnerabilidad crítica de ejecución remota de código en React Server Components. El fallo, bautizado como «React2Shell», permite a un atacante no autenticado ejecutar código arbitrario en cualquier servidor que ejecute versiones vulnerables de React RSC o Next.js App Router mediante el envío de una única petición HTTP.
Dado lo omnipresente que se ha vuelto Next.js - impulsa una parte significativa de la web moderna - el impacto de esta vulnerabilidad es grave. Cualquier aplicación Next.js que use App Router (el valor predeterminado para proyectos nuevos desde Next.js 13) con RSC habilitado es vulnerable. Sin configuración especial. Sin endpoints específicos. Solo una petición POST a cualquier ruta.
La vulnerabilidad reside en el protocolo «Flight» de React, el formato de serialización utilizado para transferir datos entre servidor y cliente en React Server Components. La ausencia de una comprobación de hasOwnProperty durante la deserialización permite recorrer la cadena de prototipos, llegando en última instancia al constructor Function de JavaScript para ejecutar código controlado por el atacante.
El fallo existe en los paquetes react-server-dom-webpack, react-server-dom-turbopack y react-server-dom-parcel de React. Next.js, como principal consumidor de RSC, hereda la vulnerabilidad a través de su App Router.
Muchos servicios son potencialmente vulnerables. Next.js es uno de los frameworks de React más populares, utilizado por empresas de todos los tamaños - desde startups hasta grandes corporaciones. App Router con React Server Components ha sido la arquitectura predeterminada para proyectos nuevos de Next.js desde la versión 13, lo que significa que la mayoría de los despliegues modernos de Next.js están afectados.
Cualquier aplicación que use:
react-server-dom-* vulnerables (19.0.0 - 19.2.0)Esto incluye despliegues en producción en Vercel, AWS, infraestructura autoalojada y cualquier otro lugar donde se ejecuten aplicaciones Next.js App Router.
No afectados:
process.mainModule disponible)| Paquete | Vulnerable | Parcheada |
|---|---|---|
Actualiza de inmediato. No hay soluciones alternativas.
Para aplicaciones Next.js:
npm install next@latest
# or
pnpm update next
Para uso directo de React RSC:
npm install react-server-dom-webpack@latest
Verifica tus versiones instaladas:
npm ls next react-server-dom-webpack react-server-dom-turbopack
Se han descubierto múltiples vectores de ataque para esta vulnerabilidad. El más común - y el que funciona sin ningún requisito previo - utiliza la contaminación de prototipos a través del sistema de referencias del protocolo Flight de React.
El exploit envía una petición POST multipart manipulada con una cabecera Next-Action. El payload abusa del sistema de referencias para:
$1:__proto__:thenFunction de JavaScriptPOST / HTTP/1.1
Host: target.com
Content-Type: multipart/form-data; boundary=----Boundary
Next-Action: x
------Boundary
Content-Disposition: form-data; name="0"
{"then":"$1:__proto__:then","status":"resolved_model","value":"{...}","_response":{...}}
------Boundary
Content-Disposition: form-data; name="1"
"$@0"
------Boundary--
El código se ejecuta durante la deserialización, antes de que se realice cualquier validación del ID de acción. Esto significa que cualquier valor de la cabecera Next-Action activa la ruta de código vulnerable - no se requiere un ID de acción válido.
Existen otros vectores de ataque, incluidas las referencias a funciones
$Fy los gadgets de módulo directos. Estos suelen requerir un ID de acción válido. Consulta Vectores de ataque alternativos para más detalles.
El protocolo Flight de React resuelve referencias como $1:path:to:value dividiendo por dos puntos y recorriendo el objeto:
// ReactFlightReplyServer.js - getOutlinedModel()
for (let i = 1; i < path.length; i++) {
value = value[path[i]]; // No hasOwnProperty check!
}
La ironía: En la parte superior de este mismo archivo, línea 35:
import hasOwnProperty from 'shared/hasOwnProperty';La protección se importó. Estaba disponible. Simplemente no se usó en el único bucle donde más importaba.
Esta única comprobación ausente permite que $1:__proto__:then recorra desde un objeto chunk, a través de la cadena de prototipos, hasta Chunk.prototype.then - una función que procesa objetos similares a Promise. Al crear un chunk falso con las propiedades adecuadas, controlamos qué código se ejecuta.
Clona el servidor de pruebas vulnerable:
git clone https://github.com/freeqaz/react2shell
cd react2shell/vulnerable-next-server
pnpm install
pnpm dev
En otra terminal:
./detect.sh http://localhost:3443
Un servidor vulnerable devuelve HTTP 500 con E{"digest" en el cuerpo de la respuesta. Para demostrar RCE:
./exploit-redirect.sh http://localhost:3443 "id"
La salida del comando aparece en la respuesta. Para exploración interactiva:
./shell.sh http://localhost:3443
El directorio vulnerable-next-server/ contiene una aplicación Next.js 16.0.6 + React 19.2.0 preconfigurada para pruebas locales seguras. Se ejecuta en el puerto 3443 por defecto. Es una configuración mínima de App Router que demuestra que las configuraciones predeterminadas son vulnerables.
Desarrollamos múltiples variantes de exploit para manejar diferentes escenarios:
Nota sobre producción: React elimina los mensajes de error en las compilaciones de producción, lo que rompe el método
throw. Soloexploit-redirect.shcaptura de forma fiable la salida de comandos en producción sin requisitos previos. La URL de redirección se almacena en la propiedaddigestdel error (metadatos), que no se sanea - a diferencia demessage, que se convierte únicamente en{digest: "..."}.
Scripts de utilidad:
detect.sh - Sonda de vulnerabilidad no destructiva (sin ejecución de código)enumerate-actions.sh - Descubre IDs de Server Action válidos en el HTML del objetivoexfil-file.sh - Exfiltración de archivos por fragmentos (maneja automáticamente archivos grandes)shell.sh - Pseudo-shell interactiva sobre RCEEl método de redirección es el recomendado porque funciona en producción, no requiere requisitos previos y devuelve la salida del comando directamente. Funciona lanzando un error NEXT_REDIRECT especialmente manipulado - la salida se codifica en base64 dentro de la URL de redirección y se devuelve en la cabecera x-action-redirect.
Para un uso detallado de cada script, consulta USAGE.md.
./detect.sh https://target.com
Esto envía una sonda mínima que activa la ruta de código vulnerable sin ejecutar código arbitrario.
Respuesta de un servidor vulnerable:
text/x-componentE{"digest"Servidor parcheado o sin RSC: Devuelve 404, un formato de error diferente o ninguna respuesta del protocolo Flight.
curl -s -o /dev/null -w "%{http_code}" -X POST https://target.com \
-H "Next-Action: x" \
-H "Content-Type: multipart/form-data; boundary=----Boundary" \
--data-binary $'------Boundary\r\nContent-Disposition: form-data; name="0"\r\n\r\n["$1:a:a"]\r\n------Boundary\r\nContent-Disposition: form-data; name="1"\r\n\r\n{}\r\n------Boundary--'
Esta sonda referencia una propiedad inexistente en un objeto vacío. Los servidores vulnerables se bloquean al intentar acceder a {}.a.a y devuelven 500. Los servidores parcheados tienen una protección hasOwnProperty que evita el bloqueo.
Busca estos indicadores:
<script> que contienen datos del protocolo Flight (0:, 1:, etc.)x-nextjs-cache o x-nextjs-matched-path/_next/$ACTION_ID_ en campos de formulario ocultosEl siguiente diagrama ilustra cómo una única petición HTTP logra la ejecución remota de código:
sequenceDiagram
participant A as Attacker
participant N as Next.js
participant F as Flight Parser
participant JS as JS Engine
A->>N: POST with Next-Action header + malicious payload
N->>F: Parse multipart form data
F->>JS: await getRoot - returns chunk as thenable
rect rgb(80, 20, 20)
Note over F,JS: VULNERABILITY - No hasOwnProperty check
JS->>F: chunk.then parses $1:__proto__:then
F-->>F: Traverses to Chunk.prototype.then
end
F->>JS: resolve(attackerObject)
Note over JS: JS Promise spec: resolve(thenable)<br/>calls thenable.then()
JS->>F: fakeChunk.then() with attacker's _response
rect rgb(80, 20, 20)
Note over F,JS: EXPLOITATION - Attacker controls _response
F->>F: $B0 → _formData.get(_prefix + "0")
Note over F: _formData.get = Function constructor<br/>_prefix = malicious code string
F->>JS: Function(code) invoked as thenable
end
Note over JS: RCE - execSync() runs
rect rgb(20, 60, 20)
Note over A,JS: OUTPUT EXFILTRATION (redirect method)
JS-->>F: throw NEXT_REDIRECT with base64(output)
F-->>N: Error propagates up
N-->>A: HTTP 303 + x-action-redirect header
end
React Server Components usa un formato de serialización personalizado llamado «Flight» para transmitir árboles de componentes del servidor al cliente. Utiliza códigos de prefijo para diferentes tipos de valores:
$1, $2, ... - Referencias a otros chunks por ID$@0 - Referencia al objeto chunk sin procesar (devuelve el chunk en sí, no su valor)$B0 - Referencia a blob (activa _formData.get(_prefix + id))$1:path:to:prop - Recorre una ruta sobre el valor de un chunk referenciadoLa vulnerabilidad explota la combinación de $@ (referencia sin procesar) y rutas delimitadas por dos puntos para acceder a __proto__.
Fase 1: Procesamiento de la petición
Next-Action activa el manejo de acciones RSCawait getRoot(response) devuelve el chunk 0 como un thenableFase 2: Recorrido de prototipos
then - la especificación de Promise de JS llama a thenable.then(resolve, reject)$1:__proto__:then se resuelve a Chunk.prototype.thenthen, status: "resolved_model" y _responseFase 3: Ejecución de código
resolve(ourObject) activa otra llamada a then() (especificación de thenable de JS)Chunk.prototype.then se ejecuta usando nuestro objeto _response controlado$B0 activa _formData.get(_prefix + "0") - ambos controlados por el atacanteFase 4: Exfiltración de la salida (opcional, método de redirección)
NEXT_REDIRECT con la salida del comando codificada en base64x-action-redirect antes de la validación de URLLa Function construida se invoca como un thenable: fn(resolve, reject). Cómo manejemos esto determina si recuperamos la salida:
Recomendado: Redirección. Funciona en producción, sin requisitos previos, salida en la cabecera x-action-redirect.
El enfoque ciego se cuelga porque la Promise nunca se resuelve - await bloquea para siempre. Esto es útil para escenarios de dispara y olvida (reverse shells, exfiltración fuera de banda mediante curl).
La vulnerabilidad principal se ha explotado mediante tres clases de ataque distintas. Este repositorio usa el primer enfoque; otros PoC demuestran las alternativas:
Por qué la contaminación de prototipos no requiere un ID de acción: El análisis del formulario multipart alimenta los chunks al deserializador Flight de inmediato. El RCE ocurre en getOutlinedModel() durante la resolución de la referencia al chunk - antes de que Next.js valide el ID de acción. Las peticiones codificadas en URL validan primero el ID de acción (ruta de código diferente en action-handler.ts:768).
Por qué la referencia $F requiere un ID de acción: La referencia $F activa loadServerReference(), que realiza una búsqueda en el manifiesto. Si la acción no existe, la petición falla antes de la ejecución de código.
Para un análisis detallado de todas las implementaciones de PoC y sus ventajas e inconvenientes, consulta external-pocs/COMPARISON.md.
React elimina los detalles de error en las compilaciones de producción. Esto afecta a la exfiltración basada en throw:
Desarrollo:
{"digest":"...","name":"Error","message":"uid=501(free)...","stack":[...]}
Producción:
{"digest":"..."}
El método de redirección evita esto porque la URL de redirección se almacena en la propiedad digest, no en message. La cabecera se establece de forma incondicional antes de la validación de URL, por lo que incluso las URLs inválidas reciben la cabecera.
La idea clave de la explotación - usar referencias $@ a chunks sin procesar para crear un chunk falso autorreferencial - se atribuye a maple3142. La metodología de detección referenciada aquí proviene de Searchlight Cyber / Assetnote.
Divulgaciones oficiales:
Investigación de la comunidad:
Para una comparación detallada de todas las implementaciones de PoC, consulta external-pocs/COMPARISON.md.
Antecedentes:
El código está bajo la Licencia MIT. La documentación (archivos *.md) está bajo CC-BY-SA 4.0.
react-server-dom-webpack| 19.0.0, 19.1.0, 19.1.1, 19.2.0 |
| 19.0.1, 19.1.2, 19.2.1+ |
react-server-dom-turbopack | 19.0.0, 19.1.0, 19.1.1, 19.2.0 | 19.0.1, 19.1.2, 19.2.1+ |
react-server-dom-parcel | 19.0.0, 19.1.0, 19.1.1, 19.2.0 | 19.0.1, 19.1.2, 19.2.1+ |
| Línea de versión | Vulnerable | Parcheada |
|---|
| 15.0.x | < 15.0.5 | 15.0.5+ |
| 15.1.x | < 15.1.9 | 15.1.9+ |
| 15.2.x | < 15.2.6 | 15.2.6+ |
| 15.3.x | < 15.3.6 | 15.3.6+ |
| 15.4.x | < 15.4.8 | 15.4.8+ |
| 15.5.x | < 15.5.7 | 15.5.7+ |
| 16.0.x | < 16.0.7 | 16.0.7+ |
| Script | HTTP | Salida | Producción | Notas |
|---|
exploit-redirect.sh | 303 | Cabecera x-action-redirect | Sí | Recomendado. Sin requisitos previos. |
exploit-throw.sh | 500 | Cuerpo de la respuesta de error | No | Solo modo dev (los errores se sanean en producción). |
exploit-blind.sh | 200 | Solo en el servidor | Sí | Dispara y olvida. Úsalo para exfiltración OOB. |
exploit-urlencoded.sh | 303 | Cabecera x-action-redirect | Sí | Firma WAF diferente. Requiere ID de acción. |
exploit-reflect.sh | 200 | Cuerpo de la respuesta | Sí | El más sigiloso. Requiere ID de acción. |
| Estrategia | Sufijo del payload | Cómo funciona |
|---|
| Ciego | execSync('CMD');0 | Ejecuta pero nunca se resuelve - la conexión se cuelga, sin salida |
| Lanzar | throw execSync('CMD').toString() | Rechaza la Promise, salida en el cuerpo del error (solo modo dev) |
| Redirección | throw {digest:'NEXT_REDIRECT;...;'+b64(output)} | Abusa del manejo de redirecciones de Next.js, salida en la cabecera |
| Reflejo | arguments[0](https://github.com/freeqaz/react2shell/blob/HEAD/%5BexecSync(%27CMD%27).toString()]) | Resuelve la Promise con la salida como argumento de la acción (requiere ID de acción válido) |
| Clase de ataque | Mecanismo | ID de acción requerido | Ejemplo de PoC |
|---|
| Contaminación de prototipos | Recorrido $1:__proto__:then hasta Chunk.prototype | No | react2shell, lachlan2k, joe-desimone |
| Referencia a función $F | $F1 + action#constructor para llegar a Function | Sí | shellinteractive |
| Gadget de módulo | Sintaxis module#export (p. ej., child_process#execSync) | Varía | Investigación de ejpir |
| Autor | Contribución | Ruta de ataque | Características destacadas |
|---|
| lachlan2k | Descubridor original | Contaminación de prototipos | Encadenamiento con Array.map, estructura de 5 chunks, soporte para Waku |
| ejpir | Investigación de gadgets | Todas las rutas | Catálogo de gadgets de módulo, ataques de persistencia, ruta data URI |
| joe-desimone | Herramientas en Python | Contaminación de prototipos | Asistente de reverse shell, exfiltración por callback, detección de timeouts |
| labubusDest / MrR0b0t19 | Shell interactiva | Referencia a función $F | REPL de Python, subida/descarga de archivos, suite de pruebas integrada |
| Searchlight Cyber | Métodos de detección | — | Metodología de detección de alta fidelidad, firmas de WAF |