DEV Community

Cover image for CVE-2025-55182 · React2Shell: RCE en React Server Components via Prototype Pollution
Annais Molina Fuentes
Annais Molina Fuentes

Posted on • Originally published at blog.deviannt.com

CVE-2025-55182 · React2Shell: RCE en React Server Components via Prototype Pollution

Este es un resumen. El análisis completo — walkthrough de la causa raíz, payload íntegro, framework de explotación, artefactos forenses y patch diffing — vive en blog.deviannt.com.

TL;DR: El deserializador Flight de React evalúa como Promise cualquier objeto que tenga un método .then, independientemente de su tipo real. Un atacante envenena Object.prototype.then mediante un POST multipart manipulado, forzando al servidor a ejecutar JavaScript arbitrario a través del constructor de Function. El resultado se exfiltra por la cabecera HTTP X-Action-Redirect. Sin autenticación. Determinista. CVSS v3.1: 10.0 (Critical).

La superficie de ataque

Los React Server Components (RSC) se estabilizaron en React 19 junto con las Server Actions — un modelo en el que los componentes de UI se ejecutan directamente en el servidor y se comunican con el cliente mediante una capa de serialización propia llamada el protocolo Flight. Cuando el cliente invoca una Server Action, envía una petición POST multipart con un payload serializado. El servidor lo deserializa, ejecuta la acción y devuelve el resultado en streaming.

El protocolo Flight no es JSON. Es un formato de streaming con chunks tipados. Su mecanismo central: si un objeto deserializado tiene una función .then, el runtime lo resuelve como una Promise.

Esa suposición es la raíz de esta vulnerabilidad.

⚠️ Cualquier aplicación Next.js que use el App Router con React Server Components está afectada — la configuración por defecto desde Next.js 14. No es necesario que el desarrollador haya definido Server Actions explícitamente. La presencia de los paquetes RSC afectados es suficiente.

Causa raíz

// VULNERABLE — React 19.0.0 / 19.1.0 / 19.1.1 / 19.2.0
if (obj && typeof obj.then === 'function') {
  // comprobación conductual — bypasseable vía cadena de prototipos
}
Enter fullscreen mode Exit fullscreen mode

Si un atacante escribe una función en Object.prototype.then, todos los objetos planos del runtime la heredan. El deserializador ya no puede distinguir una Promise real de un objeto contaminado — e invoca new Function(_prefix) sobre contenido controlado por el atacante.

La cadena de explotación

  1. Reconocimiento — identificar una aplicación Next.js con React 19.0.0–19.2.0 y App Router. Cualquier endpoint que procese multipart/form-data con cabecera Next-Action es un objetivo válido. No se necesita ruta específica ni conocimiento previo de la estructura de la aplicación.
  2. Construcción del payload — body multipart donde __proto__:then envenena Object.prototype, _formData.get se redirige a $1:constructor:constructor y _prefix transporta el JavaScript a ejecutar.
  3. Envío de la petición — un único POST al root con Next-Action: x. El WAF ve una petición multipart bien formada y la reenvía sin inspeccionarla. No se activan firmas de inyección estándar.
  4. Evaluación en el servidor — el deserializador Flight encuentra un objeto con .then (heredado del prototipo contaminado). Llama a new Function(_prefix). Ejecuta el código del atacante.
  5. Exfiltración — el resultado de execSync() se interpola en el digest del error NEXT_REDIRECT. Next.js lo convierte en un 307 con X-Action-Redirect: /login?a=<output>. Se decodifica el parámetro.

Sin inyección de shell. Sin subida de archivos. Sin autenticación. Una sola petición POST.

RCE básico: whoami, id y uname -a ejecutados sobre el servidor Node.js vulnerable. RCE básico: whoami, id y uname -a ejecutados sobre el servidor Node.js vulnerable.

El payload completo, el one-liner mínimo con curl y el framework de explotación completo react2shell.py — con módulos para shell interactiva persistente, exfiltración de variables de entorno, defacement y denegación de servicio selectiva — están documentados en blog.deviannt.com.

El parche

Publicado simultáneamente en las tres ramas activas de React 19: 19.0.1, 19.1.2, 19.2.1 (3 de diciembre de 2025).

// VULNERABLE
- resolvedValue = resolvedValue[key];

// PARCHEADO
+ if (!resolvedValue.hasOwnProperty(key)) break;
+ resolvedValue = resolvedValue[key];
Enter fullscreen mode Exit fullscreen mode

Los guards con hasOwnProperty bloquean el recorrido de la cadena de prototipos. El atacante ya no puede llegar al constructor de Function mediante $1:constructor:constructor. La cadena se rompe en su primer eslabón.

Verifica si tu instalación está parcheada:

node -e "const r = require('react'); const [maj,min,pat] = r.version.split('.').map(Number); \
  console.log('React:', r.version, (maj===19 && (min<2||(min===2&&pat<1))) ? '❌ VULNERABLE' : '✓ Parcheado')"
Enter fullscreen mode Exit fullscreen mode

🔴 Advisory post-parche: Las versiones inicialmente parcheadas (19.0.1, 19.1.2, 19.2.1) contienen dos CVEs derivados: CVE-2025-55184 (DoS, CVSS 7.5) y CVE-2025-55183 (Exposición de Código Fuente, CVSS 5.3). Actualiza a 19.0.2, 19.1.3 o 19.2.2.

Una lección estructural

La confianza conductual es más débil que la confianza de identidad. La comprobación typeof obj.then === 'function' fue diseñada para ser flexible y funcionar con cualquier thenable. Esa flexibilidad es precisamente lo que la hizo explotable. Cuando un límite de seguridad depende del comportamiento de un objeto en lugar de su identidad, la prototype pollution se convierte en una llave maestra.


Análisis completo → blog.deviannt.com · CVE-2025-55182 · React2Shell

— devianntsec // investigación en seguridad & más

Top comments (0)