
Análisis técnico detallado de CVE-2025-55182, una RCE crítica sin autenticación en Next.js + React 19.0.0. Documenta el recorrido de investigación, el análisis del parche, el recorrido de prototipos y el sumidero de deserialización Blob que permite la explotación completa sin gadgets específicos de la aplicación.
Este documento describe mi proceso de investigación personal sobre CVE-2025-55182, incluyendo hallazgos confirmados y experimentos que realicé. Esto representa un relato honesto de mi investigación, incluyendo suposiciones iniciales incorrectas y el eventual avance.
Después de una investigación exhaustiva sobre CVE-2025-55182 (CVSS 10.0), mi conclusión inicial fue que no se había demostrado públicamente una RCE automática y que la explotación requería gadgets específicos de la aplicación. Esta conclusión era incorrecta.
El 5 de diciembre de 2025, tras recibir información adicional de otro investigador independiente en X (@maple3142), reproduje con éxito RCE completa no autenticada en Next.js vanilla sin requerir ninguna vulnerabilidad de código específica de la aplicación.
Mi enfoque inicial fue el primer cambio en el parche de React 19.0.1:
Vulnerable (19.0.0):
return fn.bind.apply(fn, [null].concat(_ref));
Parcheado (19.0.1):
if (Array.isArray(promiseValue)) {
promiseValue = promiseValue.slice(0);
} else {
promiseValue = [];
}
Supuse que el camino de ataque era a través de fn.bind.apply() con objetos maliciosos en lugar de arrays. Pude demostrar la inyección de argumentos en Server Actions usando $ACTION_REF_ con bound controlado por el atacante:
curl -X POST http://localhost:9000/ \
-F '$ACTION_REF_0=' \
-F '$ACTION_0:0={"id":"<ACTION_ID>","bound":["; id #","/etc/passwd"]}'
Resultado: Los argumentos se inyectaron con éxito en la Server Action. Sin embargo, esto solo lleva a RCE si la función objetivo utiliza esos argumentos de manera insegura.
Un análisis más profundo del parche reveló otro cambio importante en getOutlinedModel():
Vulnerable:
for (key = 1; key < reference.length; key++)
parentObject = parentObject[reference[key]];
Parcheado:
if (hasOwnProperty.call(value, name)) {
value = value[name];
}
Este comportamiento vulnerable permitía el recorrido de la cadena de prototipos usando referencias como:
$1:__proto__:constructor:constructor
Durante la investigación, probé un objeto thenable que contenía una propiedad .then:
{"then": "$1:__proto__:constructor:constructor"}
Cuando JavaScript procesa esto a través de await:
.then y trata el objeto como una Promiseobj.then(resolve, reject)then se resuelve en Function.constructor, JavaScript intenta ejecutar Function(resolve, reject)Resultado observado:
SyntaxError: Unexpected token 'function'
at Object.Function [as then] (<anonymous>)
Cuando Function.constructor se invoca como:
Function(resolve, reject)
// resolve.toString() = "function () { [native code] }"
// Function intenta analizar esto como código → SyntaxError
Los argumentos resolve y reject son siempre las funciones nativas de Promise. Function intenta interpretar el primer argumento como código fuente, lo cual no es JavaScript válido.
Aquí es donde mi investigación se estancó. Concluí que controlar los argumentos para Function.constructor era imposible sin un gadget específico de la aplicación.
Después de publicar mis hallazgos iniciales, otro investigador independiente me señaló una pieza crítica que había pasado por alto: el sumidero de deserialización $B (Blob).
En el código compilado del servidor React Flight (no visible en las fuentes TypeScript), existe:
case "B":
return response._formData.get(response._prefix + id);
Ubicación:
[email protected]cjs/react-server-dom-webpack-server.node.unbundled.development.js[email protected]/dist/compiled/react-server-dom-webpack/Este código permite a React llamar a response._formData.get() con valores derivados de la entrada controlada por el atacante, sin validación.
| Enfoque | Argumentos para Function.constructor | Resultado |
|---|---|---|
| Thenable (Fase 3) | resolve, reject (funciones nativas) | ❌ SyntaxError |
| Blob + Response Envenenado | _prefix (cadena controlada por el atacante) | ✅ RCE |
Al combinar:
$1:__proto__:then → Chunk.prototype.then)_response envenenado con:
_formData.get configurado como Function.constructor_prefix configurado como código JavaScript arbitrario$BEl manejador case "B": ejecuta:
Function.constructor("<código del atacante>" + id)
Esto evita por completo la limitación de vinculación de argumentos.
PoCs populares en GitHub que afirman RCE utilizan Action IDs como:
"child_process#execSync""vm#runInThisContext"Estos son falsos. Next.js solo acepta Action IDs definidos por la aplicación. IDs inválidos producen:
TypeError: Cannot read properties of undefined (reading 'workers')
Sin embargo, el exploit real no requiere Action IDs falsos. Cualquier ID de Server Action válido funciona.
Probé esta cadena de explotación en una aplicación mínima de Next.js 15.0.3 + React 19.0.0 con solo:
async function myAction(data) {
"use server";
console.log("Server Action called with:", data);
return { success: true, received: data };
}
Resultado: RCE completa confirmada. La aplicación no contenía código inseguro, ni eval, ni execSync, ni gadgets.
| Aspecto | Hallazgo |
|---|---|
| ¿Autenticación requerida? | ❌ No |
| ¿Gadget de aplicación requerido? | ❌ No |
| ¿Funciona en Next.js vanilla? | ✅ Sí |
| Número de solicitudes necesarias | 1 POST |
| Versiones afectadas | Next.js ≤15.0.4 + React 19.0.0 |
| Puntuación CVSS | 10.0 (justificado) |
Al 5 de diciembre de 2025, el sumidero $B aún existe en Next.js 15.0.5 (la versión supuestamente parcheada).
Verificación:
$ npm pack [email protected]
$ tar -xzf next-15.0.5.tgz
$ grep -A5 'case "B":' package/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js
Resultado:
case "B":
return response._formData.get(response._prefix + obj);
El código es idéntico al de la versión vulnerable.
Debido al descubrimiento de que la vulnerabilidad puede no estar completamente parcheada en las versiones "corregidas" declaradas, estoy reteniendo el payload completo de prueba de concepto pendiente de verificación con los equipos de seguridad de Vercel y Meta.
Los detalles técnicos proporcionados en este documento son suficientes para comprender el mecanismo de la vulnerabilidad, pero intencionalmente incompletos para evitar una explotación inmediata.
| Técnica | Estado |
|---|---|
Inyección de argumentos mediante bound | ✅ Confirmado (impacto limitado) |
| Recorrido de prototipos | ✅ Confirmado |
Acceso a Function.constructor vía thenable | ✅ Confirmado (pero no explotable por sí solo) |
| Detección de versiones vulnerables | ✅ Confirmado |
| Problema | Impacto |
|---|---|
El sumidero de deserialización $B (Blob) | ❌ Crítico - permite control de argumentos |
| Examinar código compilado vs. fuente | ❌ El sumidero solo existe en la salida compilada |
| Mecanismo de envenenamiento del objeto Response | ❌ Permite eludir todas las protecciones |
CVE-2025-55182 es explotable para RCE completa no autenticada en aplicaciones Next.js vanilla.
$B de un investigador independiente$B todavía acepta objetos _response arbitrariosNext-Action inusuales$@, __proto__, $B$B fue proporcionada por un investigador en X (@maple3142)Última actualización: 5 de diciembre de 2025