June 11, 2026
Critical React2Shell Vulnerability (CVE-2025β55182)
π Reader Note & Educational Disclaimer
Melike AteΕ
5 min read
π Reader Note & Educational Disclaimer
This article has been written for cybersecurity education and awareness purposes. The information presented here is designed to develop technical understanding of an already patched and publicly disclosed vulnerability.
β οΈ Legal and Ethical Warning
- β Educational Purpose: This content is prepared to enhance the knowledge level of security professionals and software developers
- β Information: Provides the technical depth needed to understand the vulnerability and protect your own systems
- β Unauthorized Access Prohibited: Do not use this information against any system without explicit authorization
π¬ Your Support Matters
Hello! π
If you find this content valuable, I have a small request:
1. Give It a Clap π
Use the platform's "clap" or "like" feature to support this article. This helps the algorithm reach more people.
2. Leave a Comment π
Answering one of these questions would really help me:
- "What did this explanation teach me?"
- "Which section was most helpful?"
- "What topic would you like deeper coverage on?"
- "Is this vulnerability related to technology your company uses?"
3. Share It π
If you know other engineers or security professionals, share this article with them. Sharing knowledge keeps the entire community safer.
Introduction β The Emergence and Importance of the Vulnerability
The React2Shell vulnerability emerged in December 2025, shaking the modern React ecosystem. This remote code execution (RCE) vulnerability affecting applications using React Server Components (RSC) earned a perfect CVSS score of 10.0, highlighting its critical severity.
Why This Vulnerability Is So Critical:
- Widespread Framework: Millions of sites worldwide use React
- Fast Exploitation: PoC was released quickly
- No Authentication Required: Pre-authentication is not needed to exploit the vulnerability
- Internal Infrastructure Access: Allows executing commands on the server
- Single HTTP Request: RCE can be achieved with one HTTP request instead of a complex chain of exploits
Affected Versions:
React:
- 19.0.0β19.2.0
Next.js:
- 15.0.0β15.0.4
- 15.1.0β15.1.8
- 15.2.0β15.2.5
- 15.3.0β15.3.5
- 15.4.0β15.4.7
- 15.5.0β15.5.6
- 16.0.0β16.0.6
Affected Packages:
- react-server-dom-webpack
- react-server-dom-parcel
- react-server-dom-turbopack
Technical Analysis of the Vulnerability
When React processes HTTP requests to React Server Components endpoints, it deserializes payload data without adequate security controls. This creates an opportunity for attackers to execute arbitrary code.
Flight Protocol Format
Marker Format | Meaning | Example
--------------------------------------------------
I{...} | Inline JSON | I{"name":"Melike"}
--------------------------------------------------
$L[id]:[path] | Module Reference | $L1:MyComponent
--------------------------------------------------
$@[id] | Promise Reference | $@0
--------------------------------------------------
$B[id] | Blob Reference | $B1337
--------------------------------------------------
[id]:[prop]:[prop] | Property Traversal | $1:constructor:constructor
--------------------------------------------------
: | Path Separator | Used in traversal
--------------------------------------------------
__proto__ | Prototype Access | $1:__proto__:thenMarker Format | Meaning | Example
--------------------------------------------------
I{...} | Inline JSON | I{"name":"Melike"}
--------------------------------------------------
$L[id]:[path] | Module Reference | $L1:MyComponent
--------------------------------------------------
$@[id] | Promise Reference | $@0
--------------------------------------------------
$B[id] | Blob Reference | $B1337
--------------------------------------------------
[id]:[prop]:[prop] | Property Traversal | $1:constructor:constructor
--------------------------------------------------
: | Path Separator | Used in traversal
--------------------------------------------------
__proto__ | Prototype Access | $1:__proto__:thenHow Flight Protocol Works
The client sends data chunks via form data. These chunks can reference each other:
files = {
"0": (None, '["$1"]'),
# Chunk 0: Array with reference to Chunk 1
"1": (None, '{"object":"fruit","name":"$2:fruitName"}'),
# Chunk 1: Fruit object, fruitName property comes from Chunk 2
"2": (None, '{"fruitName":"cherry"}'),
# Chunk 2: Value of fruitName property
}files = {
"0": (None, '["$1"]'),
# Chunk 0: Array with reference to Chunk 1
"1": (None, '{"object":"fruit","name":"$2:fruitName"}'),
# Chunk 1: Fruit object, fruitName property comes from Chunk 2
"2": (None, '{"fruitName":"cherry"}'),
# Chunk 2: Value of fruitName property
}Deserialized result:
{ object: 'fruit', name: 'cherry' }{ object: 'fruit', name: 'cherry' }The Vulnerability
In vulnerable versions, React doesn't check whether a property actually exists on an object when resolving references. This allows attackers to traverse the prototype chain.
Example:
files = {
"0": (None, '["$1:__proto__:constructor:constructor"]'),
# $1:__proto__ = Chunk 1's prototype
# :constructor:constructor = function constructor
"1": (None, '{"x":1}'),
# A simple object
}files = {
"0": (None, '["$1:__proto__:constructor:constructor"]'),
# $1:__proto__ = Chunk 1's prototype
# :constructor:constructor = function constructor
"1": (None, '{"x":1}'),
# A simple object
}Deserialized result:
[Function: Function]
// Global Function Constructor![Function: Function]
// Global Function Constructor!Vulnerable Code (Before Patch)
// ============================================================
// VULNERABLE REFERENCE RESOLVER
// ============================================================
function resolveReference(response, value) {
// value = "1:__proto__:constructor:constructor"
// Split:
// [0] = "1" (chunk ID)
// [1:] = ["__proto__", "constructor", "constructor"]
const parts = value.substring(1).split(':');
const chunkId = parseInt(parts[0], 16);
const path = parts.slice(1);
let obj = getChunk(response, chunkId);
// obj = {"x": 1}
// β οΈ VULNERABILITY: No validation!
for (let prop of path) {
obj = obj[prop];
// obj["__proto__"] β Object.prototype
// obj["constructor"] β [Function: Object]
// obj["constructor"] β [Function: Function] β RCE!
}
return obj;
}// ============================================================
// VULNERABLE REFERENCE RESOLVER
// ============================================================
function resolveReference(response, value) {
// value = "1:__proto__:constructor:constructor"
// Split:
// [0] = "1" (chunk ID)
// [1:] = ["__proto__", "constructor", "constructor"]
const parts = value.substring(1).split(':');
const chunkId = parseInt(parts[0], 16);
const path = parts.slice(1);
let obj = getChunk(response, chunkId);
// obj = {"x": 1}
// β οΈ VULNERABILITY: No validation!
for (let prop of path) {
obj = obj[prop];
// obj["__proto__"] β Object.prototype
// obj["constructor"] β [Function: Object]
// obj["constructor"] β [Function: Function] β RCE!
}
return obj;
}Exploitation Method β Step by Step
The exploit found in GitHub repositories uses thenable hijacking and gadget chains. Let's examine it step by step.
Step 1: Creating a Thenable Object
# ============================================================
# STEP 1: THENABLE HIJACKING
# ============================================================
crafted_chunk = {
"then": "$1:__proto__:then",
# Use the then method from Chunk 1's prototype
# This points to Chunk.prototype.then
"status": "resolved_model",
# Make the chunk appear "resolved"
"reason": -1,
# Prevent errors during toString() invocation
}
# React sees this object:
# "It has a then method, so it's thenable!"
# β await crafted_chunk
# β crafted_chunk.then() is called
# β Chunk.prototype.then executes# ============================================================
# STEP 1: THENABLE HIJACKING
# ============================================================
crafted_chunk = {
"then": "$1:__proto__:then",
# Use the then method from Chunk 1's prototype
# This points to Chunk.prototype.then
"status": "resolved_model",
# Make the chunk appear "resolved"
"reason": -1,
# Prevent errors during toString() invocation
}
# React sees this object:
# "It has a then method, so it's thenable!"
# β await crafted_chunk
# β crafted_chunk.then() is called
# β Chunk.prototype.then executesWhy -1?
// In initializeModelChunk:
var rootReference = -1 === chunk.reason ? void 0 : chunk.reason.toString(16);
// If reason === -1, undefined will be assigned and toString() won't error// In initializeModelChunk:
var rootReference = -1 === chunk.reason ? void 0 : chunk.reason.toString(16);
// If reason === -1, undefined will be assigned and toString() won't errorStep 2: Injecting Blob Reference in Value
(Since Medium doesn't allow me to embed code this way, I had to use screenshots :) )
Step 3: Blob Handler Execution
The blob handler in the Flight protocol works like this:
In the attacker's _formData:
Step 4: Assembling the Full Payload (With $@)
# ============================================================
# STEP 4: SELF-REFERENCE ($@)
# ============================================================
files = {
"0": (None, json.dumps(crafted_chunk)),
# Malicious chunk
"1": (None, '"$@0"'),
# $@0 = Reference Chunk 0 in "raw" form
# (Not resolved, as a chunk object)
}# ============================================================
# STEP 4: SELF-REFERENCE ($@)
# ============================================================
files = {
"0": (None, json.dumps(crafted_chunk)),
# Malicious chunk
"1": (None, '"$@0"'),
# $@0 = Reference Chunk 0 in "raw" form
# (Not resolved, as a chunk object)
}In the Flight protocol, $@ works like this:
case "@":
return (
(obj = parseInt(value.slice(2), 16)),
getChunk(response, obj)
// Return raw chunk (not resolved value)
);case "@":
return (
(obj = parseInt(value.slice(2), 16)),
getChunk(response, obj)
// Return raw chunk (not resolved value)
);By doing this, we circumvent the Chunk.prototype.then rules.
Complete RCE Payload
For the complete working exploit code, check the GitHub repository: https://github.com/msanft/CVE-2025-55182
After the Patch β What Changed?
// ============================================================
// AFTER PATCH - SECURE CODE
// ============================================================
function resolveReference(response, value) {
const parts = value.substring(1).split(':');
const chunkId = parseInt(parts[0], 16);
const path = parts.slice(1);
let obj = getChunk(response, chunkId);
// β
PATCH 1: Only own properties!
// Don't traverse the prototype chain!
for (let prop of path) {
// Check if property exists on object itself, NOT on prototype
if (!Object.prototype.hasOwnProperty.call(obj, prop)) {
// Property doesn't exist β ERROR
throw new Error(`Property not found: ${prop}`);
}
obj = obj[prop];
}
return obj;
}
// β
PATCH 2: Whitelist allowed properties
const ALLOWED_PROPERTIES = new Set([
'then',
'catch',
'finally',
'value',
'status',
'error'
]);
for (let prop of path) {
if (!ALLOWED_PROPERTIES.has(prop)) {
throw new Error(`Forbidden property: ${prop}`);
}
obj = obj[prop];
}// ============================================================
// AFTER PATCH - SECURE CODE
// ============================================================
function resolveReference(response, value) {
const parts = value.substring(1).split(':');
const chunkId = parseInt(parts[0], 16);
const path = parts.slice(1);
let obj = getChunk(response, chunkId);
// β
PATCH 1: Only own properties!
// Don't traverse the prototype chain!
for (let prop of path) {
// Check if property exists on object itself, NOT on prototype
if (!Object.prototype.hasOwnProperty.call(obj, prop)) {
// Property doesn't exist β ERROR
throw new Error(`Property not found: ${prop}`);
}
obj = obj[prop];
}
return obj;
}
// β
PATCH 2: Whitelist allowed properties
const ALLOWED_PROPERTIES = new Set([
'then',
'catch',
'finally',
'value',
'status',
'error'
]);
for (let prop of path) {
if (!ALLOWED_PROPERTIES.has(prop)) {
throw new Error(`Forbidden property: ${prop}`);
}
obj = obj[prop];
}Conclusion
Thank you for taking the time to read this article.
My goal was to achieve technical understanding of this vulnerability β to answer the question "what is the root cause of this vulnerability?" rather than simply saying "apply the patch."
References & Resources
GitHub - msanft/CVE-2025-55182: Explanation and full RCE PoC for CVE-2025-55182 Explanation and full RCE PoC for CVE-2025-55182. Contribute to msanft/CVE-2025-55182 development by creating an accountβ¦
NVD A pre-authentication remote code execution vulnerability exists in React Server Components versions 19.0.0, 19.1.0β¦
Understanding React2Shell (CVE-2025β55182) The Critical RCE in React Server Components
The Asymmetry Achilles: How React2Shell (CVE-2025β55182) Turns Global Routing Against Itself The most concerning vulnerabilities are those that exploit your infrastructure. Five months after the identification ofβ¦