React2Shell: When Your Framework Lets Attackers Run Code
December 13, 2025
Subscribe to the Effie Labs NewsletterIn early December 2025, a vulnerability was disclosed that affected React Server Components—the modern architecture that powers Next.js and many React applications. CVE-2025-55182, also called React2Shell, is a CVSS 10.0 pre-authentication remote code execution vulnerability. That's the worst possible rating. And it's being actively exploited in the wild.
What makes this interesting isn't just the severity—it's how the exploit works. It's a masterclass in JavaScript's quirks, prototype chains, and how asking the wrong question can break everything.
What are React Server Components? (quick primer)
React Server Components (RSC) are a relatively new architecture where UI components run on the server instead of the browser. The idea is to reduce the JavaScript sent to clients—components execute on the server, and only the rendered HTML (or minimal data) gets sent over the wire.
RSC uses a serialization protocol called "React Flight" to communicate between client and server. Think of it like RPC over HTTP: clients send numbered "chunks" of serialized data, and the server reassembles them to understand what the user requested.
When you submit a form, your browser packages the data into chunks that reference each other. The server processes these chunks to figure out what action to take. This is where the vulnerability lives.
The vulnerability: asking the wrong question
The bug is in how React deserializes data from clients. When RSC receives chunks, it needs to check: "Is this property actually on this object, or is it inherited from JavaScript's prototype chain?"
The vulnerable code did this check by asking the untrusted object itself: value.hasOwnProperty(i). That's like asking a burglar if they're supposed to be in your house.
In JavaScript, every object inherits from Object.prototype, which includes methods like hasOwnProperty, constructor, and toString. Normally these are safe, but an attacker who controls the object can replace hasOwnProperty with something malicious, bypassing the security check entirely.
The core issue
The code trusted untrusted data to tell the truth about itself. When you call
value.hasOwnProperty(), you're doing a method lookup on attacker-controlled
data. They can shadow that property with anything they want.
How the exploit works (it's like a Rube Goldberg machine, but evil)
The exploit is clever. It doesn't just break things—it carefully constructs a chain that tricks JavaScript into executing attacker code. Here's how it works:
Stage 1: Make chunks reference themselves (the inception part)
Flight uses special prefixes like $@ to encode different data types. The $@ prefix returns the raw chunk object instead of its parsed value. If chunk 1 says "give me chunk 0's raw object" and chunk 0 references chunk 1, you create a loop that exposes JavaScript's internal objects.
Stage 2: Climb the prototype chain (like climbing a ladder to get Function)
By traversing $1:__proto__:constructor:constructor, the attacker walks up JavaScript's prototype chain: chunk → Object.prototype → Object constructor → Function constructor. Now they can create arbitrary functions.
Stage 3: Trick JavaScript's await (the "then" property hack)
JavaScript's await keyword automatically looks for objects with a .then() method and calls it. By setting then to point to React's internal Chunk.prototype.then, the attacker hijacks this mechanism. React's own code then calls initializeModelChunk(), feeding the attacker's payload into React's initialization process.
Stage 4: Run whatever code you want (the fun part)
The $B prefix triggers React's blob handler, which calls a .get() method on a controlled object. By pointing _formData.get to the Function constructor, the attacker crafts their payload so the call becomes Function("require('child_process').execSync('id');//0")(). The server executes the attacker's shell command with full Node.js privileges.
Why this is really, really bad
This vulnerability is particularly severe because exploitation happens during deserialization, before any authentication or validation. Setting any Next-Action header value (even Next-Action: foo) triggers the vulnerable code path.
- Pre-authentication RCE: No credentials required
- Full Node.js access: Can execute arbitrary commands, read files, access environment variables
- Environment variable theft: Database credentials, API keys, secrets
- Lateral movement: Can access cloud metadata endpoints, internal networks
What attackers are actually doing with this (it's not pretty)
Within 24 hours of public disclosure, multiple distinct campaigns emerged. Security researchers observed a surge in exploitation attempts, including:
- Botnet deployments: Mirai variants and custom bots targeting cloud infrastructure
- Cobalt Strike beacons: Advanced persistent threat actors deploying command and control infrastructure
- Cryptocurrency miners: Opportunistic attackers using compromised servers for mining
- Secret harvesting: Automated tools scanning for API keys, credentials, and cloud metadata
One particularly interesting payload we've seen is called "Secret-Hunter"—it automatically installs legitimate security tools like TruffleHog and Gitleaks to scan compromised servers for buried secrets in Git repositories, Docker images, and cloud configurations. The attacker then exfiltrates everything they find.
How they fixed it (surprisingly elegant)
The fix is elegant. Instead of calling value.hasOwnProperty(i), the patched code captures a reference to the original hasOwnProperty method at module load time and uses .call() syntax:
hasOwnProperty.call(value, i)
This works because the attacker can't modify module-level variables from the serialized payload. The hasOwnProperty variable stores a reference to the original method before any untrusted data is processed, and .call() explicitly specifies which function to invoke, bypassing property lookup entirely.
Common misconceptions (let's clear this up)
There's been a lot of confusion about this vulnerability. Let me clear up some common misconceptions:
- "It's prototype pollution": Not exactly. The attack works without
__proto__. The minimum viable exploit uses$1:then:constructorwithout any prototype manipulation. - "Only Next.js is affected": No—any framework using React Server Components is potentially affected, including custom implementations.
- "WAF blocking proto protects us": No—the exploit doesn't require
__proto__. Effective WAF rules must block$@chunk references,resolved_model, andconstructor:constructorpatterns. - "Edge Runtime is vulnerable": No—Edge Runtime (Cloudflare Workers, Vercel Edge) uses V8 isolates without access to Node.js APIs. Applications deployed exclusively to Edge Runtime are not exploitable.
What you should do (upgrade, like, now)
If you're running React Server Components or Next.js, upgrade immediately:
- Next.js: >= 15.1.3, >= 14.2.22, or >= 13.5.8
- React: >= 19.0.0, >= 18.3.1, or >= 18.2.0
Beyond patching, implement defense-in-depth: audit Server Action exposure, add rate limiting, review logs for historical exploitation, and consider Edge Runtime for sensitive endpoints.
This vulnerability is a reminder that serialization is dangerous. When you deserialize untrusted data, you're essentially executing code—just in a different form. The React2Shell exploit shows how JavaScript's dynamic nature can be weaponized when you trust data to validate itself.