
1. React2Shell(CVE-2025-55182) Overview
React is one of the most widely used frontend technologies globally, continuously expanding its ecosystem based on high code reusability and fast rendering performance. Its influence is significant enough that over 5% of web services globally are developed based on React. The React2Shell vulnerability discussed in this blog post is a security vulnerability that allows Remote Code Execution (RCE), even in services based on the latest React version.

The React2Shell vulnerability allows attackers to execute arbitrary commands on a company's server simply by accessing the service over the internet without authentication, potentially leading to complete system takeover. This vulnerability is particularly concerning because it occurs in typical environments rather than those with additional options set, making it easy to exploit and posing a potential threat to all services using React. For this reason, it has been assigned the highest threat score of 10.0 by the Common Vulnerability Scoring System (CVSS). Since Next.js, broadly used in the Node.js ecosystem, also operates on React, both companies and development teams using Next.js should pay attention to this vulnerability as well.
We hope this article will help domestic and international developers and security personnel at companies to accurately understand the React2Shell vulnerability and perform prompt verification and response procedures.
2. Technical Context
Before diving into the React2Shell vulnerability, a light understanding of the concept of React Server Component, which may be somewhat unfamiliar to security personnel, and Prototype Pollution, which may be unfamiliar to developers, is needed.
React Server Component & Flight Protocol
When providing web services, processing the web pages displayed to users entirely on the server side and providing the completed DOM is called Server-Side Rendering (SSR). In contrast, delivering only data in an API format and processing the actual DOM composition on the user's web browser (Client-side) is called Client-Side Rendering (CSR).
CSR enables a richer web service experience and interaction by providing the skeletal structure of the page to the user, while all actual DOM composition is performed on the user's web browser. However, as frontend functionalities become increasingly complex, the amount of computations that the browser needs to handle has increased, leading to greater consumption of the user's device resources and decreased user experience due to performance degradation.
To address these issues, React introduced React Server Components (RSC), which handle significant portions of rendering on the server rather than the client. RSC is a technology where the execution of React components occurs on the server side, and the execution results are rendered on the client. It is a concept that combines the existing SSR and CSR, rendering the state of the new page only up to the React Component form on the server, and allowing clients to render the components, thereby reducing client-side load.
Although JSON is an excellent serialization format for handling data, it is unsuitable for dealing with complex React Components. To handle React Components appropriately, it must be able to process complex types and references such as Promise, Blob, and Map, beyond simple strings, dictionaries, and arrays. Therefore, RSC uses a unique protocol and serialization format called the Flight Protocol.
Expression | Type | Example | Description | |
|---|---|---|---|---|
$$ | Escaped $ | "$$hello" → "$hello" | Literal string starting with $ | |
$@ | Promise/Chunk | "$@0" | Reference to chunk ID 0 | |
$F | Server Reference | "$F0" | Server function reference | |
$T | Temporary Ref | "$T" | Opaque temporary reference | |
$Q | Map | "$Q0" | Map object at chunk 0 | |
$W | Set | "$W0" | Set object at chunk 0 | |
$K | FormData | "$K0" | FormData at chunk 0 | FormData object |
$B | Blob | "$B0" | Blob at chunk 0 | Blob object |
$n | BigInt | "$n123" | BigInt value | |
$D | Date | "$D2024-01-01" | Date object | |
$N | NaN | "$N" | NaN value | |
$I | Infinity | "$I" | Infinity | |
$- | -Infinity/-0 | "$-I" or "$-0" | Negative infinity or negative zero | |
$u | undefined | "$u" | Undefined value | |
$R | ReadableStream | "$R0" | ReadableStream | |
$0-9a-f | Chunk Reference | "$1", "$a" | Reference to chunk by hex ID |
Prototype Pollution
Objects in Javascript are quite distinct from the object style commonly known as 'object-oriented' in Java or C++. In Javascript, when objects are created, they are not inherited from a class but from another object. In other words, new objects do not clone from a specific template (Class) but expand their functionality based on another object they reference.
In this inheritance structure, a Prototype is the parent object referred to by an object, and it is the target where a lookup continues for properties or methods not directly possessed by the object. For example, in Javascript, an array uses Array.prototype as its prototype, where methods like toString and push are implemented, allowing them to be used through the prototype.
Due to this characteristic of Javascript, if a property can be set on a prototype object through any means, it can seem as if newly created objects have that property set. This act of polluting the prototype or accessing it inappropriately is called Prototype Pollution. It might be a somewhat unfamiliar concept at first, but it can be easily understood through the example code below, and we'll explore Prototype Pollution in more detail later.
3. Root Cause Analysis
Diff Analysis
Before analyzing the root cause of the vulnerability, the React2Shell vulnerability was patched via commit 7dc903c in the facebook/react repository on GitHub (GitHub Commit). Among the changes made in this commit, the modifications related to the Flight Protocol and Prototype were made in packages/react-server/src/ReactFlightReplyServer.js.

caption - Added property value check within getOutlinedModel function implementation in ReactFlightReplyServer.js
Processing Flight Protocol - The First Gateway of RSC
If the getOutlinedModel function is called among the Flight Protocol data passed to the react-server, it is pre-processed via the following function calls in ReactFlightReplyServer.js.
initializeModelChunk()Initialize Chunk when a Flight Protocol request occursreviveModel(): Restore Model from request dataparseModelString(): Create Model from string data (deserialization)getOutlinedModel(): Handle Chunk Reference occurring during deserialization
Raw Chunk Reference
In the previous Flight Protocol overview, expressions like $@0 were described as a Reference to Chunk 0. Regarding this implementation, the parseModelString() function can be viewed as follows. (ReactFlightReplyServer.js:929)
For a Reference starting with @, it is implemented as a Raw reference that returns the Chunk Promise itself. Through this, a reference to the Promise can be obtained. (CAUSE #1)
Unserialize & Prototype Pollution - Towards the Essence of Chunk
The implementation of the getOutlinedModel() function just before the vulnerability patch can be seen as follows. (ReactFlightReplyServer.js:595)
In this function, when chunk.status is INITIALIZED, you can observe how it continues to reference members within value from the path obtained through reference.split(':'). During this process, the absence of checks like hasOwnProperty allows Prototype Pollution through the __proto__ member. (CAUSE #2)
For example, if the reference expression is $1:__proto__:aaa, the member named aaa of the Prototype of Chunk 1 will be referenced.
If Chunk 1 is an object of the Promise type like $@0 viewed earlier, $1:__proto__ will reflect (Chunk0).__proto__, ultimately allowing access to Chunk.prototype.
Through CAUSE #1 and CAUSE #2, the attacker gains access to Chunk.prototype. (PRIMITIVE #1)
Chunk.prototype - Make initializeModelChunk Great Again
Information about Chunk.prototype gained through PRIMITIVE #1 can also be confirmed within the same ReactFlightReplyServer.js file. (ReactFlightReplyServer.js:125)
By default, Chunk is a Promise object, and the .then() method branches to perform different actions depending on this.status.
Meanwhile, by utilizing PRIMITIVE #1, if you reference $1:__proto__:then, it becomes possible to set any property of the chunk to the Chunk.prototype.then function, thus making an attribute named then point to Chunk.prototype.then.
In the example above, if the chunk is set as shown, then is actually Chunk.prototype.then, and because this.status is resolved_model within then, if the chunk (actually a Promise) can simply be resolved, the attacker can make arbitrary initializeModelChunk function calls. (PRIMITIVE #2)
initializeModelChunk - Once Again
The attacker can now call the initializeModelChunk function using fully-controllable values thanks to PRIMITIVE #2. The implementation of this function is as follows. (ReactFlightReplyServer.js:446)
At this point, since the attacker can control the value of resolvedModel completely, it is possible to invoke the reviveModel function with an arbitrary JSON object. Additionally, since chunk._response was also a controllable value from the beginning of the initializeModelChunk() call, PRIMITIVE #2 reduces to arbitrary reviveModel function invocation.
reviveModel - Blob
Similar to before, the reviveModel() function internally calls the parseModelString() function. Within this parseModelString(), a logic for handling Blob data is present as follows. (ReactFlightReplyServer.js:446)
When referring to the code block, remember that the response is a manipulable value by the attacker, allowing the blobKey to finally be manipulated in the form of (desired string) || (arbitrary integer), and response._formData.get can also be manipulated to appropriate values.
Since response._formData.get must be a callable function, you can recall CAUSE #1 to try to apply it.
As shown above, because $1:constructor:constructor becomes Function.constructor, if you construct a chunk as follows, you can use Function.constructor for arbitrary function creation and assignment to value.
If you assume the above _response is processed through Blob, it ultimately results in Function.constructor("console.log(1337);//1") being returned and the final structure as follows.
As a result, the attacker can create an arbitrary desired Javascript function and further make the value itself into a Thenable possessing the function then.
If you return to the Chunk.prototype.then() function,
After the initializeModelChunk call, where the Blob is processed, value is a Thenable with the arbitrary function created by the attacker as then, so at the line resolve(chunk.value), an arbitrary Javascript function executes. (PRIMITIVE #3)
Sum Everything, Next Resolves Everything
At this point, it is necessary to revisit what information the attacker obtained with the Primitive.
As long as then of Chunk initially resolved in any form, the combination of PRIMITIVE #2 and PRIMITIVE #3 enables arbitrary function invocation.
Consider an attacker has constructed the Chunk as follows.
If structured as above, as long as then is successfully called, the following flow allows arbitrary code execution on the Server-side.
Since
.then()isChunk.prototype.then, the entire operation executes asthenonthisUsing
value = JSON.parse("{\\"then\\": \\"$B1\\"}"), invokereviveModelIn the
reviveModelprocess,$B1337is set toFunction.constructor("console.log(1);//1")Invoke
thenofvalueagain ⇒ CallFunction.constructor("console.log(1);//1)()Execute arbitrary javascript code stored in
_response._prefix
In the case of a very representative Framework using React like Next.js, the action handler that executes when the Next-Action header is passed has the following code. (action-handler.ts:879)
At that time, the decodeReplyFromBusboy function processes requests in the multipart/form-data format and returns a chunk.
If the above Chunk was provided in multipart/form-data format, the decodeReplyFromBusboy function would interpret the chunk and return the chunk below.
At this point, this object is a Thenable since it conforms to the definition in Javascript by possessing a then member that is a function. (MDN - Thenable)
Therefore, through the first Chunk.prototype.then, it is possible to call a second initializeModelChunk completely configured with status, value, and _response, ultimately leading to the execution of the console.log(1) code.
Since the JavaScript executed here is Server-side code running on node.js, not Client-side code, an attacker can configure something like process.mainModule.require('child_process').execSync('id > /tmp/test');, making arbitrary code execution possible on the server.
4. Course of Action
Since most of the recently released versions of React-based technology stacks are affected, it is recommended to check the currently used version and apply the latest patch that resolves vulnerabilities quickly if using vulnerable versions of React Base services.
Target | Affected Version |
|---|---|
React | 19.0.0, 19.1.0, 19.1.1, 19.2.0 |
Next.js | 15.x (15.0.0 ~ 15.5.6), 16.x (16.0.0 ~ 16.0.6), Next.js 14.3.0.canaray.77 and above |
React-based derivative services | - |
The latest patch versions that fix the vulnerabilities are as follows.
Target | Latest Patch Version with Fixed Vulnerabilities |
|---|---|
React | 19.0.1, 19.1.2, 19.2.1 |
Next.js | 15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7, 15.6.0, 16.0.7 |
It is possible to add rules against well-known attack payloads through WAF, but since this is a vulnerability that makes it easy to implement transformed payloads, it is difficult to effectively defend by adding WAF rules alone.
During the process of handling Flight requests in RSC, the JSON.parse function processes the attacker's input, allowing JSON syntax to be delivered, and manipulated to bypass detection rules by making malicious payloads undetectable through Unicode notation like \\uXXXX.
Type | Details |
|---|---|
RSC Flight Handling Code | const rawModel = JSON.parse(resolvedModel); |
WAF Bypass Example Payload | { "\u0074\u0068\u0065\u006e": "\u0024\u0031\u003a\u005f\u005f\u0070\u0072\u006f\u0074\u006f\u005f\u005f\u003a\u0074\u0068\u0065\u006e", "\u0073\u0074\u0061\u0074\u0075\u0073": "\u0072\u0065\u0073\u006f\u006c\u0076\u0065\u0064\u005f\u006d\u006f\u0064\u0065\u006c", "\u0072\u0065\u0061\u0073\u006f\u006e": -1, ... omitted } |
5. React2Shell Free Scanner Release
While the React2Shell vulnerability diagnosis can be performed by directly using publicly available PoC code, it can be safely and easily checked for vulnerabilities through the emergency scanner provided by Enki Whitehat’s attack surface management solution, OFFen ASM.
OFFen ASM Emergency Scanner Page

Reference
https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components
https://www.boho.or.kr/kr/bbs/view.do?bbsId=B0000133&pageIndex=1&nttId=71912&menuNo=205020
https://github.com/facebook/react/commit/7dc903cd29dac55efb4424853fd0442fef3a8700
https://gist.github.com/HerringtonDarkholme/87f14efca45f7d38740be9f53849a89f#flight-reference-types
https://gist.github.com/maple3142/48bc9393f45e068cf8c90ab865c0f5f3
Popular Articles










