
In the world of web application security, it is rare to see a vulnerability that is simultaneously ubiquitous, critical (CVSS 10.0), and architecturally fascinating. React2Shell (CVE-2025-55182) is exactly that.
Disclosed on December 3, 2025, this unauthenticated Remote Code Execution (RCE) vulnerability affects the core serialization protocol of React Server Components (RSC). It allows attackers to execute arbitrary code on servers running default configurations of Next.js and other RSC-enabled frameworks.
| CVE | Product | CVSS Score | Impact |
|---|---|---|---|
| CVE-2025-55182 | React (react-server-dom-*) | 10.0 | Remote Code Execution |
| CVE-2025-66478 | Next.js | 10.0 | Remote Code Execution |
This post dives into the technical details of the "Flight" protocol, the specific deserialization gadget chain used for exploitation, and the patch that killed the bug.
To understand React2Shell, you must understand Flight—the internal protocol React uses to stream component trees from server to client (and back via Server Actions).
Unlike REST or GraphQL, which typically traffic in JSON, Flight needs to serialize complex React-specific concepts:
$@): References to lazy-loaded code or modules$B): Binary data streams$): Asynchronous operationsWhen a client invokes a Server Action (e.g., submitting a form in Next.js), the arguments are serialized into a string that looks like this:
0:{"name":"$@1"}
1:{"id":"./src/actions.js","chunks":[],"name":"updateUser"}
The server parses this stream, resolving the references to execute the corresponding function. The vulnerability lies in the react-server-dom-webpack (and related) packages, where the parser failed to validate that the objects it was "rehydrating" actually belonged to the server's trusted internal state.
The Flight parser was designed with an implicit trust model—it assumed that incoming data would always conform to expected structures. This assumption proved fatal when researchers discovered that carefully crafted payloads could manipulate the parser's internal state through prototype pollution.
The exploit, first weaponized by researchers like maple3142, is a masterclass in JavaScript runtime manipulation. It leverages Server-Side Prototype Pollution to trick the Flight parser into executing a "gadget chain."
The attack requires a single HTTP POST request with a multipart/form-data body (standard for Server Actions).
The attacker sends a JSON object that looks like a React Chunk but contains a malicious then property:
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"value": "{\"then\":\"$B1337\"}",
"_response": {...}
}
The Trigger: The Flight parser treats any object with a then property as a Promise (a "thenable"). It attempts to await it.
The Trick: The attacker sets then to $1:__proto__:then. In the Flight syntax, this resolves to Chunk.prototype.then.
The Result: The server executes Chunk.prototype.then.call(fakeChunk). This forces the runtime to re-enter the parsing loop using the attacker's fake object as the context (this).
Once the parser is forced to process the malicious chunk, it attempts to resolve the value provided in the payload: "$B1337" (a Blob reference).
To resolve a Blob, the React internal code accesses a specific property on the response object: _formData. Under normal execution, this is a trusted FormData object. In the exploit, the attacker has polluted this (the fake chunk) with their own _response object.
The vulnerable code looks something like this:
response._formData.get(response._prefix + id)
The attacker controls the _response object to pivot execution to the global Function constructor:
"_response": {
"_prefix": "process.mainModule.require('child_process').execSync('xcalc');",
"_formData": {
"get": "$1:constructor:constructor"
}
}
Here is the substitution that happens at runtime:
_formData.get becomes $1:constructor:constructor, which resolves to the Function constructor_prefix + id becomes the attacker's payload string (the code to execute)The call _formData.get(prefix + id) effectively becomes:
new Function("process.mainModule.require('child_process').execSync('xcalc');...")
Because this occurs inside a Promise resolution chain, the created function is immediately invoked, executing the shell command on the server.
| Stage | Action | Result |
|---|---|---|
| 1 | Send fake chunk with then property |
Parser treats object as Promise |
| 2 | then resolves to Chunk.prototype.then |
Context hijacked to attacker's object |
| 3 | Blob resolution triggers _formData.get() |
Attacker controls method and arguments |
| 4 | Function constructor invoked with payload |
Arbitrary code execution |
The fix, merged in facebook/react#35277, introduces a strict check during module resolution. The developers replaced a direct property access with hasOwnProperty.call.
Before (Vulnerable):
return moduleExports[metadata[NAME]];
After (Patched):
if (hasOwnProperty.call(moduleExports, metadata[NAME])) {
return moduleExports[metadata[NAME]];
}
This prevents the parser from traversing up the prototype chain (__proto__) to access inherited methods like constructor or then, effectively neutralizing the gadget chain.
The hasOwnProperty.call() pattern ensures that only properties directly defined on the object (not inherited from the prototype chain) are accessed. This is a fundamental defensive pattern against prototype pollution attacks:
obj[key] → Can traverse prototype chain ❌hasOwnProperty.call(obj, key) → Only own properties ✅If you are running React 19 or Next.js 15/16, you are likely vulnerable by default.
| Framework | Patched Versions |
|---|---|
| Next.js | 15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7, 16.0.7 (or later) |
| React | react-server-dom-webpack 19.0.1, 19.1.2, 19.2.1 |
After updating, verify your versions:
npm list next react-server-dom-webpack
Deserialization is dangerous: Any protocol that reconstructs objects from untrusted input is a potential RCE vector. Flight's complexity made it especially vulnerable.
Prototype pollution is not theoretical: This real-world exploit demonstrates how JavaScript's prototype chain can be weaponized for code execution, not just property manipulation.
One line can make the difference: The fix was remarkably simple—a hasOwnProperty check. The lesson: defensive coding patterns matter at every level.
Framework trust is implicit risk: When you adopt a framework, you inherit its attack surface. React Server Components introduced powerful capabilities but also new vectors.
Patch velocity matters: From disclosure (December 3) to widespread awareness (December 7), the window for exploitation was narrow but real. Automated dependency updates are no longer optional.
The React2Shell vulnerability is a reminder that even the most widely-used, well-audited frameworks can harbor critical flaws. The sophistication of the exploit chain—leveraging thenables, prototype pollution, and the Function constructor in sequence—demonstrates the creativity of modern security research and the importance of defense-in-depth.

Ryan previously served as a PCI Professional Forensic Investigator (PFI) of record for 3 of the top 10 largest data breaches in history. With over two decades of experience in cybersecurity, digital forensics, and executive leadership, he has served Fortune 500 companies and government agencies worldwide.

A technical analysis of the MaliciousCorgi campaign that weaponized VS Code extensions to exfiltrate source code and credentials from over 1.5 million developers to servers in China.

Technical analysis of Devman ransomware's evolution from DragonForce affiliate to standalone Rust-based RaaS operation, including BYOVD techniques, SAP zero-day exploitation, and ESXi targeting.

In distressed M&A, you're not buying future cash flows—you're assuming a high-interest technical loan the previous owners stopped servicing. Learn how to quantify the hidden cyber liabilities before they destroy your deal value.