DEV Community

Cover image for React2Shell (CVE-2025-55182): RCE crítico en React Server Functions
lu1tr0n
lu1tr0n

Posted on • Originally published at elsolitario.org

React2Shell (CVE-2025-55182): RCE crítico en React Server Functions

Introducción

El 30 de noviembre de 2025, el investigador de seguridad neozelandés Lachlan Davidson reportó a Meta una vulnerabilidad crítica de ejecución remota de código (RCE) en React. Tres días después, el equipo de React publicaba un parche urgente y un advisory público bajo el identificador CVE-2025-55182, una falla bautizada por la comunidad como React2Shell. El bug vivía en el protocolo Flight, la pieza de plomería que sustenta a React Server Components (RSC) y Server Functions, y permitía a un atacante remoto sin autenticación tomar control del servidor que ejecutaba la aplicación. El alcance potencial es difícil de exagerar: millones de sitios web construidos con Next.js, Remix y otros frameworks modernos quedaron expuestos durante la ventana entre el inicio del bug y el rollout del parche.

React2Shell es relevante para cualquier desarrollador en LATAM que mantenga aplicaciones con el App Router de Next.js, formularios con Server Actions o cualquier flujo que dependa de RSC. En este artículo desglosamos qué falló, cómo se descubrió, qué tan grave fue, y qué pasos concretos hay que tomar para mitigar la exposición y prevenir clases de bugs similares en el futuro.

Qué pasó

El núcleo de la vulnerabilidad es un fallo de tipo prototype property lookup en el parser del protocolo Flight. Flight es el formato binario-ish de mensajería que React usa para mover datos entre cliente y servidor en el modelo RSC. Cuando un cliente envía una petición Flight (por ejemplo, al invocar una Server Function vía un <form action>), el servidor deserializa el mensaje y reconstruye los objetos JavaScript correspondientes en memoria.

El parser de Flight soporta una sintaxis que permite referenciar propiedades de un objeto serializado mediante el operador $x:y, donde $x apunta a otro chunk del mensaje y :y selecciona una propiedad de ese objeto. La vulnerabilidad consistía en que el parser no verificaba si la propiedad era propia (own property) o si venía heredada del prototipo. Así, un atacante podía pedir cualquier propiedad accesible a través de la cadena de prototipos del objeto.

El payload mínimo para demostrar la falla es escalofriantemente corto:

0 = {
  "exploit": "$1:toString"
}
&1 = 123
Enter fullscreen mode Exit fullscreen mode

En este ejemplo, el chunk 1 es el número 123. La referencia $1:toString debería fallar (los números no tienen una propiedad propia toString), pero el parser devolvía Number.prototype.toString, una función nativa, y la colocaba dentro del objeto bajo control del atacante.

Una vez que un atacante puede inyectar funciones nativas arbitrarias en los argumentos de una Server Function, la cadena de explotación se vuelve ejercicio de creatividad. En su writeup, Davidson detalla cómo combinó este primitivo con la mecánica de invocación de Server Functions de Next.js para encadenar accesos a Function.prototype.constructor y, eventualmente, ejecutar código arbitrario en el proceso de Node.js que sirve la aplicación.

⚠️ Ojo: el exploit no requería credenciales previas, ni una sesión activa, ni configuraciones exóticas. Bastaba con que la aplicación tuviera al menos un endpoint que consumiera mensajes Flight: una ruta del App Router con un <form> que llamara a una Server Function era suficiente.

Contexto e historia

Para entender por qué React2Shell pudo existir hay que mirar la trayectoria de React Server Components, una de las apuestas más ambiciosas del equipo de React desde 2020. La promesa de RSC es elegante: que un mismo árbol de componentes pueda ejecutarse parcialmente en el servidor (renderizando HTML y datos) y parcialmente en el cliente (con interactividad), pasando de un lado al otro de forma transparente para el desarrollador. Es la idea que hoy mueve al App Router de Next.js, a Remix v2 con RSC, y a frameworks emergentes como Waku.

Para que esa magia funcione, React necesitaba un protocolo capaz de serializar tipos que JSON no soporta: Date, BigInt, Map, Set, Promise, referencias circulares, streams asíncronos. Ese protocolo es Flight, y su sintaxis añade un puñado de marcadores especiales sobre JSON. El carácter $ denota tipos especiales: $D es un Date, $x referencia otro chunk del mensaje, $x:y selecciona una propiedad de ese chunk, $@ indica un Promise pendiente, y así sucesivamente. Los mensajes se descomponen en chunks que pueden llegar de forma asíncrona y desordenada, lo que da a las aplicaciones streaming verdadero a nivel de UI.

El detalle incómodo: hasta el incidente de React2Shell, Flight no tenía especificación pública. Davidson cuenta que para entender el formato tuvo que leer el código fuente de react-server-dom-webpack y rastrear hilos sueltos en X (Twitter). Encontrar siquiera el nombre del protocolo le tomó varias horas. Esa opacidad es exactamente la razón por la que la falla sobrevivió tanto tiempo: los auditores de seguridad externos tendían a probar las apps Next.js como si Flight fuera "JSON con cositas", sin profundizar en su semántica real. Como bien resume Davidson en su blog: la industria asumió que Flight era inofensivo y lo testeó superficialmente, ignorando que tenía una superficie de ataque mucho mayor.
Un payload mínimo de Flight bastaba para escalar al prototipo y obtener funciones nativas.

Datos y cifras

La línea de tiempo del incidente, según los relatos públicos de Davidson y Meta (en NZDT, GMT+13):

  • Lunes 24 de noviembre de 2025: Davidson decide investigar Flight a fondo, motivado por la frustración de no encontrar documentación.- Martes 25 de noviembre: tras una noche casi sin dormir, ya domina los fundamentos del protocolo y empieza a probar payloads creativos.- Domingo 30 de noviembre: confirma la cadena de explotación completa y reporta la vulnerabilidad a Meta vía su programa de bug bounty.- Miércoles 3 de diciembre de 2025: Meta publica el parche oficial, el advisory CVE-2025-55182 y un comunicado urgiendo a actualizar de inmediato.

Versiones afectadas:

  • react-server-dom-webpack < 19.1.2- react-server-dom-turbopack < 19.1.2- Cualquier versión de Next.js, Remix o framework custom que dependa de un parser Flight previo al fix

El score CVSS 3.1 asignado fue 9.4 (Crítico). Vector de ataque: red, baja complejidad, sin necesidad de autenticación previa en configuraciones por defecto. Confidencialidad, integridad y disponibilidad: alto impacto en las tres dimensiones. La remediación oficial es la actualización a versiones parcheadas; no existe mitigación parcial confiable a nivel de configuración.

Estimaciones de exposición agregadas:

  • Más de 7 millones de sitios usan Next.js según BuiltWith (diciembre 2025).- Aproximadamente el 45% de las aplicaciones Next.js en producción ya habían migrado al App Router con RSC al momento del bug.- Solo en LATAM, miles de e-commerces y aplicaciones SaaS construidos con Next.js quedaron potencialmente expuestos durante la ventana entre la introducción del bug y el deploy del parche.

Impacto y análisis

Para dimensionar React2Shell hay que separar dos planos distintos pero interconectados.

El plano técnico

El bug es lo que en seguridad se llama un prototype pollution gadget con escalada directa a RCE. No es novedad que el modelo de prototipos de JavaScript es una fuente recurrente de problemas; lo notable es que un parser oficial mantenido por uno de los equipos más grandes y mejor financiados del ecosistema haya cometido un error tan elemental como olvidar Object.hasOwn().

graph LR
  A["Cliente atacante"] --> B["Payload Flight con dollar1:toString"]
  B --> C["Parser de React"]
  C --> D["Lookup sin validar own property"]
  D --> E["Funcion nativa inyectada en argumentos"]
  E --> F["Server Function ejecuta codigo arbitrario"]
  F --> G["RCE en el servidor Next.js"]
Enter fullscreen mode Exit fullscreen mode

El parche oficial añade exactamente esa verificación antes de resolver propiedades vía el operador $:: si la propiedad no es propia del objeto referenciado, el parser ahora rechaza el mensaje en lugar de subir por la cadena de prototipos. Es la clase de fix de tres líneas que cierra una clase entera de bugs.

El plano cultural

Más interesante que el bug en sí es la lección sobre cómo la industria audita protocolos opacos. Guillermo Rauch, fundador de Vercel y autor original de Next.js, definió la falla como "una omisión llamativa de un control de seguridad". Pero la verdad incómoda es que esa omisión vivió en producción durante años porque casi nadie miraba ese código. La documentación inexistente de Flight no era un descuido: era una barrera de entrada para auditores externos.

💭 Clave: los protocolos sin especificación pública atraen menos auditoría externa, lo que paradójicamente los hace más peligrosos. La oscuridad no es seguridad, es deuda técnica con un cronómetro encendido.

Para equipos en LATAM, hay un takeaway práctico: cuando adoptás un framework que introduce un protocolo nuevo (RSC con Flight, tRPC con su formato propio, GraphQL con extensiones custom), parte del costo total de propiedad incluye seguir de cerca su evolución y testear más allá de los happy paths que la documentación cubre.

Qué sigue

Tras la publicación del CVE, el equipo de React tomó tres decisiones públicas que marcan dirección hacia adelante:

  • Documentar Flight: comenzar el proceso de publicar la especificación formal del protocolo, algo que la comunidad llevaba años pidiendo. La promesa es que para mediados de 2026 exista un draft revisado por pares.- Auditoría externa: contratar a una firma de seguridad para revisar el parser y los componentes adyacentes del runtime. La auditoría completa se publicará como reporte público.- Hardening de Server Functions: en versiones futuras, los argumentos pasados a una Server Function se validarán contra un schema (estilo Zod) declarado por el desarrollador. La deserialización ya no será la única línea de defensa.

Para los desarrolladores en LATAM que mantienen aplicaciones React/Next.js en producción, las acciones inmediatas son claras:

  • Actualizar react, react-server-dom-webpack o react-server-dom-turbopack a versiones >= 19.1.2.- Si usás Next.js: ir a >= 15.0.5 (App Router) o >= 14.2.18 (Pages Router con RSC habilitado).- Auditar logs de los últimos 60 días buscando peticiones POST con Accept: text/x-component y bodies que contengan referencias sospechosas como $1:constructor, $1:__proto__ o $1:prototype.- Considerar una regla en tu WAF (Cloudflare, AWS WAF, Vercel Firewall) que bloquee mensajes Flight con accesos a propiedades del prototipo.

Comandos rápidos por sistema operativo para auditar tus dependencias:

# Linux / macOS
npm ls react-server-dom-webpack react-server-dom-turbopack

# Windows PowerShell
npm ls react-server-dom-webpack, react-server-dom-turbopack

# pnpm
pnpm why react-server-dom-webpack
Enter fullscreen mode Exit fullscreen mode

💡 Tip: si tenés un monorepo grande con múltiples apps Next.js, automatizá el chequeo con un job de CI semanal que falle si alguna versión vulnerable de react-server-dom-* aparece en el lockfile. Es la forma más barata de no quedarte atrás en la próxima.
Auditar lockfiles y forzar versiones mínimas en CI es la mejor defensa.
Mirando hacia adelante, React2Shell probablemente acelere una tendencia que ya venía gestándose: la separación clara entre protocolos de datos y protocolos de invocación. Hoy Flight mezcla los dos: serializa estructuras y, simultáneamente, vehicula llamadas a funciones del servidor. Esa fusión es elegante pero acumula superficie de ataque. No sería sorprendente ver, en los próximos 12 meses, RFCs que separen el plano de datos del plano de control en el modelo RSC.

📖 Resumen en Telegram: Ver resumen

Preguntas frecuentes

¿Qué versiones de React están afectadas por React2Shell?

Las versiones de react-server-dom-webpack y react-server-dom-turbopack anteriores a 19.1.2. Cualquier framework que dependa de esos parsers (Next.js con App Router, Remix con RSC, builds custom con webpack/turbopack) hereda la vulnerabilidad. Actualizar a 19.1.2 o superior cierra el bug.

¿Necesito actualizar si mi app usa Next.js Pages Router clásico?

Si tu aplicación no usa Server Components ni Server Actions, la superficie de ataque es mucho menor. Sin embargo, varias dependencias indirectas pueden traer react-server-dom-*. Lo más seguro es revisar el lockfile y actualizar de todos modos: el costo es mínimo y elimina el riesgo de regresión si en el futuro adoptás RSC.

¿Existe un exploit público funcional?

Davidson y el equipo de Meta acordaron no publicar el PoC completo durante 90 días para dar tiempo a actualizar al ecosistema. Sin embargo, el writeup público describe la cadena con suficiente detalle para que un atacante motivado reconstruya el exploit. Considerá la ventana de gracia como cerrada en la práctica.

¿Cómo detecto si fui explotado durante la ventana de exposición?

Buscá en los logs de tus servidores peticiones POST con Content-Type: text/x-component o multipart/form-data dirigidas a rutas con Server Functions, donde el body contenga patrones como $\d+:(toString|constructor|__proto__|prototype). Cualquier coincidencia merece investigación: revisá si en torno a esa petición hubo procesos hijos inesperados, conexiones salientes anómalas o cambios en el filesystem del contenedor.

¿Qué relación tiene React2Shell con bugs de prototype pollution clásicos?

Es la misma familia conceptual: un parser que confunde propiedades propias con heredadas y permite al atacante alcanzar miembros del prototipo global. La diferencia es que React2Shell ocurre en un contexto donde esos miembros se inyectan directamente como argumentos a funciones del servidor, lo que convierte un primitivo de lectura en RCE casi inmediato.

¿Cambia esto mi estrategia de seguridad para apps RSC?

Sí, en al menos tres formas: validar siempre los argumentos de Server Functions con un schema estricto (Zod, Valibot, ArkType), mantener versiones de React al día con un job de CI que falle ante CVEs abiertos, y monitorear payloads Flight en producción como lo harías con cualquier endpoint expuesto a internet.

Referencias

📱 ¿Te gusta este contenido? Únete a nuestro canal de Telegram @programacion donde publicamos a diario lo más relevante de tecnología, IA y desarrollo. Resúmenes rápidos, contenido fresco todos los días.

Top comments (0)