
RCE sin autenticación previa en React Server Components versiones 19.0.0, 19.1.0, 19.1.1 y 19.2.0.
Este repositorio contiene una reproducción de prueba de concepto (PoC) de CVE-2025-55182, una vulnerabilidad de seguridad crítica en React Server Components (RSC) que permite la ejecución de código arbitrario sin autenticación.
La vulnerabilidad existe en la forma en que React Server Components deserializa las "Server Actions" de las solicitudes del cliente. Específicamente, la función requireModule no validaba que el nombre de exportación solicitado fuera una propiedad directa del módulo. Esto permitía a los atacantes acceder a la propiedad constructor de las funciones exportadas, obteniendo una referencia al constructor global Function, que puede ser utilizado para ejecutar código arbitrario.
Este PoC utiliza un entorno mínimo de Node.js para aislar la vulnerabilidad en la librería react-server-dom-webpack, solo para asegurarse de que el exploit demuestre el error en la propia librería, NO una mala configuración en un framework.
npmnpm install
[!NOTE] El
package.jsonestá fijado a la versión vulnerable19.0.0.
Este script configura un servidor HTTP básico que utiliza el runtime vulnerable de React para decodificar solicitudes.
# tty1
node --conditions react-server server.js
En una terminal separada, ejecuta el exploit. Este envía un payload Flight malicioso al servidor.
# tty2
node exploit.js id
Deberías ver la salida del comando devuelta en la respuesta:
Expected Output:
Response: uid=0(root) gid=0(root) groups=0(root)
¿Por qué ocurrió la vulnerabilidad?
La función requireModule en ReactFlightDOMServerNode.js básicamente confiaba en cualquier name que enviara el cliente. Hacía moduleExports[metadata[NAME]] sin verificar si esa propiedad estaba realmente destinada a ser expuesta. Entonces, si el cliente decía "oye, quiero esta propiedad", el servidor simplemente decía "¡claro! aquí tienes, amigo".
¿Por qué es mala idea permitir que la gente acceda a cualquier propiedad?
Porque básicamente permite que cualquiera acceda a la cadena de prototipos, incluso al constructor, lo cual es súper peligroso. Si el módulo exporta una función (como module.exports = () => {}), entonces su constructor es literalmente el constructor global Function.
¿Por qué obtener el constructor Function significa RCE?
Una vez que un atacante obtiene el constructor Function, puede abusar de la característica "Bound Server Action". Enlazan una cadena que contiene JavaScript malicioso a él (básicamente convirtiéndolo en new Function("código malicioso")). Y una vez que eso se ejecuta, el servidor ejecuta cualquier código que hayan puesto.
¿Por qué React ejecutaría realmente esa función maliciosa?
Porque las Server Actions pueden ser activadas por ID. Si el atacante crea un payload con un ID de Acción que apunte a su referencia module#constructor, React lo resuelve como una acción normal y la ejecuta. Esa "acción" es en realidad su función maliciosa.
¿Por qué no se validó nada de esto?
El sistema simplemente asumía que id y name de los metadatos de la Referencia del Servidor siempre se referirían a exportaciones válidas definidas por el desarrollador. No había una verificación de seguridad como hasOwnProperty para asegurarse de que la propiedad solicitada fuera realmente una exportación real y no algo heredado de la cadena de prototipos.
server.js en lugar de Next.js?Uso un server.js básico (y un webpack-runtime.js auxiliar) para configurar manualmente el runtime de React Server Components. Esto nos permite:
Forzar la configuración vulnerable: El exploit solo funciona si un módulo se exporta como una función (module.exports = fn)s. Un bundler real podría cambiar cómo se envuelven las exportaciones, dependiendo de su configuración sin embargo.
Aislar el error: Esto nos permite mostrar que el problema está dentro de react-server-dom-webpack, no en Next.js.
Recrear el entorno del bundler: react-server-dom-webpack asume que se está ejecutando dentro de un bundle de Webpack. Nuestro webpack-runtime.js le proporciona las globales que espera (__webpack_require__, __webpack_chunk_load__).
Esto no es simular la vulnerabilidad, es solo proporcionar a la librería el runtime mínimo necesario para que realmente funcione.
Ha habido discusión sobre "PoCs inválidos" que solo funcionan si el desarrollador expone intencionadamente cosas peligrosas como child_process.exec.
Este PoC no es uno de esos. Funciona en una configuración normal y segura.
La función expuesta es inofensiva
La aplicación expone una función simple updateProfile que solo devuelve una cadena y nada sospechoso, sin comandos de shell.
El exploit escapa completamente de esa función
La vulnerabilidad permite al atacante ignorar la exportación segura y saltar directamente a updateProfile.constructor, que es el constructor global Function.
El problema central es el acceso a la propiedad
React NO debería haber permitido el acceso a .constructor. El desarrollador no tenía la intención de exponer el constructor Function, en cambio, la deserialización insegura hizo eso por ellos.
El único requisito real es que el módulo exporte una función directamente (module.exports = fn), lo cual es súper común en CommonJS y muchas configuraciones de bundler.
El payload en exploit.js crea un mensaje React Flight con tres fragmentos:
id: "user-profile-action#constructor", que significa "dame el constructor".bound: apunta al Chunk 2, que contiene los argumentos.["console.log('nice try, diddy!')"]: la cadena de código malicioso.Cuando React deserializa esto:
user-profile-action..constructor => obteniendo el Function global.new Function("console.log('nice try, diddy!')")¡Y eso es RCE!
Actualiza inmediatamente a las versiones parcheadas:
react-server-dom-webpack >= 19.0.1react-server-dom-parcel >= 19.0.1react-server-dom-turbopack >= 19.0.1El parche introduce comprobaciones hasOwnProperty para prevenir el acceso a propiedades heredadas y restringe las subidas de archivos base64.
Si ejecutas este PoC contra una versión parcheada, el servidor fallará o dará error con:
$ node --conditions react-server server.js
Listening on http://localhost:3000
/path/to/CVE-2025-55182/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js:2726
resolvedValue = resolvedValue.bind.apply(
^
TypeError: Cannot read properties of undefined (reading 'bind')
at /path/to/CVE-2025-55182/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js:2726:43
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
Node.js v20.19.3
Esto confirma que el exploit falló al acceder a la propiedad constructor (devolvió undefined en lugar de Function), y por lo tanto la llamada posterior a .bind falló.
Este código es solo para fines educativos y de prueba. No utilices este exploit contra sistemas que no poseas o para los que no tengas permiso explícito para probar.
Publicado bajo DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE.