React's Server-Killing Flaw Explained
A critical CVSS 10.0 vulnerability in React Server Components allows attackers to execute any code on your server with one request. Here's how the exploit works and why you must patch your applications immediately.
The Alert That Shook the Web
Worst-case scenario arrived for React Server Components with a maximum-severity, CVSS 10.0 remote code execution bug that security researchers quickly labeled “catastrophic.” At stake: any app using the vulnerable React server package, from hobby Next.js projects to high-traffic production sites, suddenly exposed to unauthenticated attackers. One crafted HTTP request could run arbitrary code on the server, no login required.
React maintainers responded with unusually blunt language for a framework release, urging developers to “upgrade immediately” across all affected versions. Hosting platforms followed suit: Vercel, Cloudflare, and others rushed out emergency WAF rules, while warning that these filters were only partial shields. Security teams treated the advisory like a zero-day fire drill, pushing patches into production within hours.
Security mailing lists and Discord channels filled with red banners and “drop everything and patch React” messages. DevOps teams froze feature deployments to prioritize dependency upgrades, dependency pinning, and redeploys. CI pipelines that usually take days to change suddenly shipped new React builds in minutes.
Name for the bug arrived just as fast. Researchers and the React community converged on React2Shell, a deliberate echo of Log4Shell that signaled the same class of nightmare: a ubiquitous component, a serialization flaw, and trivial remote code execution. The dedicated site react2shell.com and a growing GitHub ecosystem of proof-of-concept exploits cemented the branding.
Comparisons to Log4Shell were not just marketing. Like Log4j in 2021, React Server Components sit deep in modern web stacks, hidden behind frameworks, boilerplates, and templates. Many teams needed hours just to answer a basic question: “Are we running React Server Components anywhere, and on which versions?”
Reports of active exploitation arrived almost immediately. AWS security teams warned of “cyber threat groups rapidly trying to exploit this,” with scanners sweeping the internet for vulnerable Next.js servers. Bug bounty hunters and criminal operators alike began trading payloads that turned a single POST request into a shell prompt on someone else’s infrastructure.
React2Shell: A CVSS 10.0 Nightmare
React2Shell sits in the nightmare tier of bugs: unauthenticated remote code execution on internet-facing servers. No login, no CSRF token, no tricking a user into clicking anything. A single crafted HTTP request hits a React Server Components endpoint and the attacker runs arbitrary commands with whatever privileges your Node process has.
Security people fear RCE because it collapses every other defense. Once an attacker can execute code, they can read environment variables, dump databases, drop webshells, install cryptominers, or pivot deeper into your cloud. At that point, you are not “leaking data”; you are hosting someone else’s operations center.
React2Shell does not stop at Next.js. The bug lives in the React server package and its React Flight protocol implementation, so any framework that wires into that stack inherits the blast radius. That includes projects like Waku, React Router’s server-side features, and any custom backend that decided to speak Flight directly.
CVSS 10.0 is not marketing spin; it is the top of the standardized risk scale. A score that high implies three things at once: trivial exploitability, no authentication barrier, and complete compromise of confidentiality, integrity, and availability. You do not need chained bugs, race conditions, or local access; internet reachability plus a vulnerable version is enough.
Under CVSS, React2Shell hits maximum values across the board: Attack Vector: Network, Attack Complexity: Low, Privileges Required: None, User Interaction: None. Impact metrics also max out, because arbitrary code execution means full read/write control over application data and the ability to crash or repurpose the service. That is why the advisory tags this as “Critical” rather than “High.”
Most chilling detail: you do not have to turn on exotic features to get owned. The vulnerable code sits in the core serialization path that React Server Components use by default, so a brand-new “npx create-next-app” using affected versions ships exploitable logic on day one. No server actions, no custom deserializers, no experimental flags required.
React2Shell turns boilerplate into an attack surface. If your stack speaks Flight through the vulnerable React server package and it is reachable over HTTP, you are in scope.
Your App Is Vulnerable (Even If You Don't Use Server Actions)
React2Shell does not live in Server Actions. It lives in the React Flight protocol itself—the low-level serializer/deserializer that powers all React Server Components. If your stack parses Flight payloads with a vulnerable `react-server` version, you inherit unauthenticated RCE whether or not you ever wrote `use server` in your codebase.
Meta’s advisory calls out the `react-server` package as vulnerable in specific 19.x builds. Any framework that bundles those versions and exposes a Flight endpoint is in scope. That list includes Next.js, plus other Flight adopters like React Router’s data APIs and emerging RSC frameworks.
Next.js App Router makes this especially dangerous because its default architecture wires up Flight for you. A fresh `create-next-app` using the App Router and a vulnerable Next.js release (for example, 14.x paired with React 19.0.0–19.2.0) spins up endpoints that accept Flight form-data without any extra configuration. Build, deploy, and you have an RCE surface exposed to the internet.
Every HTTP handler that consumes a Server Component payload becomes a potential entry point. That includes: - Built‑in App Router data endpoints - Custom route handlers that call `renderToReadableStream` or similar Flight APIs - Any proxy or edge function that forwards Flight payloads to a Node server
Authentication does not save you by default. If an attacker can hit a Flight endpoint before auth, they get pre‑auth RCE. If your Flight parsing sits behind a login but you expose it to browsers, any cross‑site request forgery or token theft instantly upgrades to full server compromise.
Meta’s own write‑up, Critical Security Vulnerability in React Server Components (CVE-2025-55182), explicitly warns that hosting providers’ WAF rules are only a stopgap. Patch `react-server` and your framework, or assume any exposed Flight endpoint is already being probed.
Anatomy of the Attack: One Request to Pwn a Server
One HTTP request is all an attacker needs. React2Shell turns a boring POST endpoint in a React Server Components app into a remote shell by abusing how the React Flight protocol deserializes streamed component data. No authentication, no CSRF token, no user session required.
Attackers start by crafting a malicious POST request that looks like a normal browser submission. The body uses `multipart/form-data`, the same format React Flight uses to stream Server Components payloads and Server Actions. Inside that form data, the attacker hides a series of “chunks” that mimic Flight’s internal `$`-prefixed reference syntax.
Each chunk pretends to be part of a legitimate component tree, but the structure is weaponized. One field points at another chunk using `$0`, `$1`, `$2`, and so on, while a specially crafted path string uses `:` separators to traverse into nested properties. Buried in that path is the real payload: an attempt to access `__proto__` and climb into JavaScript’s object internals.
When the vulnerable server receives the request, React’s Flight deserializer kicks in automatically. It parses the form data, reconstructs the chunk graph, and starts resolving those `$` references and colon-delimited paths. Crucially, older React server versions never checked whether a requested key was an “own” property, so `__proto__` sailed straight through.
That oversight turns a simple lookup into full-blown prototype pollution. By resolving a path like `$1:__proto__:execSync`, the deserializer can hand the attacker a live reference to `child_process.execSync` (or similar primitives) attached to the polluted prototype. From there, the payload instructs the server to run arbitrary shell commands as part of the deserialization process itself.
Proof-of-concept code on React2Shell and the Original and Detailed POCs weaponizes this into a one-shot exploit. The attacker’s crafted POST makes the server execute a command that echoes “you have been pawned” and then runs `cat secret.txt`, reading a file placed on the server’s desktop. In the demo, the contents stream straight into the Next.js server logs, but the same primitive could exfiltrate secrets via `curl`, drop a reverse shell, or install persistent malware with a single request.
Inside the Flaw: The React Flight Protocol
React Flight exists to solve a real problem: how do you stream a tree of React Server Components from the backend to the browser without shipping half your server runtime as JavaScript? Instead of JSON, React’s team designed a custom wire format that can describe components, props, and references as a sequence of small “chunks” that trickle over the network and hydrate progressively on the client.
Each chunk in the React Flight protocol carries a tiny piece of the UI: maybe a component type, maybe props, maybe a promise placeholder. To avoid repeating data, Flight uses special string tokens starting with "$" to point at other chunks. A payload might say `"$1"` to mean “go look at chunk 1,” or `"$1:name"` to say “go to chunk 1, then follow the `name` path on whatever object lives there.”
Those `$` references form a compact graph of objects that the deserializer stitches back together. Chunk 0 can be just `["$1"]`, chunk 1 can be an object that itself points at chunk 2, and so on. The result on the server or client side is a clean JavaScript object like `{ name: "cherry" }`, even though the wire format looked like a weird mix of indices and path strings.
Root cause of React2Shell hides inside that path-following logic. The Flight deserializer happily accepted attacker-controlled path traversal instructions such as `"$1:__proto__:toString"` and walked them without asking whether those keys should be accessible. No guardrails, no property checks, just raw access into whatever the path string described.
Instead of stopping at user data, those traversal steps reached straight into JavaScript internals. By resolving paths like `__proto__` on plain objects, the deserializer opened the door to classic prototype pollution. From there, attackers chained into powerful built-ins, eventually steering execution toward `child_process` and achieving remote code execution with a single malicious request.
JSON would have been boring here—and safer. Standard JSON lacks references, paths, or special `$` semantics; it only describes data, not how to walk or reconstruct it. Flight’s bespoke format bolted on cross-chunk references and traversal syntax, but without the hardened validation layers that mature serializers and JSON parsers have accumulated over years of security review.
Custom protocols always trade safety for flexibility. By inventing an ad hoc mini-language inside strings like `"$1:foo:bar"`, React’s Flight format created a new attack surface that generic tools—JSON validators, schema checkers, WAF rules—did not understand. Once that mini-language could touch `__proto__`, every server using React Server Components inherited the same critical flaw.
From Path Traversal to Prototype Pollution
Prototype pollution sounds abstract, but in JavaScript it is one of the most dangerous bug classes you can ship. Instead of corrupting a single object, prototype pollution lets an attacker tamper with `Object.prototype` itself, the shared ancestor of almost every object in a Node or browser process. Once that root gets compromised, the entire object graph starts inheriting attacker-controlled properties.
React Server Components fell into this trap via its React Flight Protocol path traversal logic. The deserializer supports a colon-based syntax for walking nested structures, so a reference like `:$1:fruit:name` means “follow chunk 1, then `fruit`, then `name`.” Researchers realized nothing stopped them from swapping a normal key for JavaScript internals and asking for `:__proto__` instead.
That single change turned a convenience feature into a weapon. When the vulnerable code saw a path segment like `__proto__`, it treated it as just another property, blindly walking into `value['__proto__']`. In JavaScript, that jump hands you the live `Object.prototype` of the target, not a harmless field on a data bag.
Once the exploit path hit `__proto__`, attackers could start writing to it. By sending crafted Flight chunks, they could do things like `payload: { ":$1:__proto__:pwned": "yes" }`, which effectively sets `Object.prototype.pwned = "yes"`. From that moment on, every plain object in the process suddenly has a `pwned` property, even ones created long after the exploit request finishes.
Pollution does not stop at string flags. Attackers can inject entire functions into the prototype, such as `Object.prototype.toJSON` or custom hooks that downstream code trusts. If some later library does `JSON.stringify(user)` or calls `options.onSuccess?.()`, it might actually run attacker-supplied code grafted onto the prototype minutes earlier.
React2Shell chains this into remote code execution by targeting powerful, rarely-audited spots in the stack. For example, polluted properties can influence how a framework builds shell commands, file paths, or dynamic `require` calls. Once a polluted value flows into `child_process.exec`, a template engine, or a dynamic import, the jump from “weird extra field” to “arbitrary command on the server” happens in a single line.
Full technical details in the NVD Entry for CVE-2025-55182 and the Detailed proof-of-concept show how a single HTTP request both pollutes `Object.prototype` and then rides those corrupted properties into Node’s most dangerous APIs. That combination is what justifies the CVSS 10.0 score: one unauthenticated request, whole-process compromise.
The Final Chain: Hijacking Promises to Run Code
Prototype pollution alone does not give you a shell; attackers still need a way to turn a corrupted object graph into running system commands. React2Shell’s final trick chained that polluted prototype into a fully weaponized remote code execution path, using nothing more exotic than JavaScript’s own reflection features and async semantics.
Once the attacker gained control over the base object prototype, they could walk the prototype chain to reach the global Function constructor. In JavaScript, every function ultimately inherits from `Function.prototype`, and `Function` itself is accessible via constructors like `({}).constructor.constructor`.
With that access, the payload simply does what every “no eval” bypass does: `new Function("require('child_process').execSync('id > /tmp/pwned');")`. That string can hold any shell command: `curl` to exfiltrate data, `rm -rf` to wipe disks, or a one-liner to drop a reverse shell. On a Next.js server, those commands run with the same privileges as the Node process, often giving direct access to environment variables, API keys, and private files.
Attackers still faced one problem: they could create a malicious function, but they did not control any obvious call site. They needed the server’s own control flow to invoke their function automatically, without an explicit `malicious()` call in application code.
JavaScript’s `await` keyword supplied the missing link. Under the hood, `await value` checks whether `value` is “thenable” and, if so, calls `value.then(resolve, reject)`. That quiet behavior is exactly what powers Promises—and exactly what React2Shell abuses.
By polluting the shared prototype to define a `.then` property pointing to the attacker’s Function instance, any awaited object suddenly became a trigger. When React Server Components code hit an `await` on a deserialized value, the JS engine dutifully invoked `.then`, which now executed arbitrary shell commands. No extra hooks, no weird gadgets—just normal async code paths transformed into a universal RCE trampoline.
The Simple Fix That Saved React
React’s fix for React2Shell looks almost insultingly simple. After a chain that runs from path traversal to prototype pollution to hijacked Promises and `child_process.execSync`, the official patch from the React team adds essentially one gatekeeper: a single `if` that calls `Object.prototype.hasOwnProperty` before reading a property during Flight deserialization.
Instead of blindly doing `value = value[path]` for each segment of the traversal, the patched code first checks `if (!Object.prototype.hasOwnProperty.call(value, path))` and bails with an error when the key is missing. That tiny condition flips the behavior from “walk the entire JavaScript object model” to “only trust fields this object actually owns.” Malicious `__proto__` references, which only live on the prototype chain, suddenly hit a hard wall.
This works because `hasOwnProperty` ignores the inherited prototype chain completely. In JavaScript, every plain object inherits from `Object.prototype`, which is where dangerous meta-properties like `__proto__` and `constructor` live. By insisting that every dereferenced key be an “own property,” React’s Flight protocol stops attackers from ever touching those internals, so the polluted prototype never materializes.
Security researchers love to say that most catastrophic bugs come down to a missing check, and React2Shell proves it. Before the patch, the deserializer implicitly trusted any path the client sent, even if it pointed straight into JavaScript’s guts. Afterward, that one `if` statement transforms a CVSS 10.0 RCE chain into a boring “invalid reference” error.
Viewed against the backdrop of unauthenticated RCE across thousands of React Server Components deployments, the elegance is almost unsettling. A sprawling ecosystem built on React Server Components and Next.js hinged on a single guardrail that should have been there from day one. One line of defensive code now separates a routine server render from a full server takeover.
The Fallout: Active Exploits and WAF Bypasses
React2Shell moved from theoretical nightmare to practical attack kit in days. Proof-of-concept repos, exploit writeups, and ready-to-run payloads now live on GitHub, lowering the bar from nation-state to weekend tinkerer. Any internet-facing app running vulnerable React Server Components effectively advertises “unauthenticated shell available on port 443.”
Security teams already see the ripple effects. AWS reports “cyber threat groups rapidly trying to exploit this,” with scanners sweeping for telltale React Flight endpoints across huge IP ranges. Independent researchers and MSSPs describe surging probes that send malformed Flight payloads and watch for subtle timing and error differences to map vulnerable targets.
Attackers do not need pixel-perfect copies of the original exploit. With public PoCs and detailed writeups like CVE-2025-55182 (React2Shell): Remote code execution in React Server Components, they can mutate payloads endlessly. Change field names, reorder chunks, or hide the polluted prototype chain behind extra levels of indirection, and you already diverge from signatures most WAF rules expect.
Hosting providers rushed out emergency filters. Vercel, Cloudflare, and AWS pushed WAF updates that block obvious React Flight abuse patterns, such as `__proto__` references in multipart form data or suspicious `$`-prefixed chunk references. Those rules help against copy-paste attacks, but they only target known shapes of a bug that fundamentally lives in how the server deserializes input.
History says WAF-only defenses age badly. Once a CVSS 10.0 RCE goes public, exploit authors iterate faster than vendors can ship new signatures. Obfuscation tricks—Unicode homoglyphs, alternative access paths like `constructor.prototype`, nested arrays, or compressed payloads—routinely bypass generic “block this string” filters.
Only one control changes the rules of engagement: patching. Upgrading React, Next.js, and any framework that embeds the vulnerable React server package removes the unsafe Flight deserialization entirely. No amount of clever WAF config can match the certainty of not running the bug in the first place.
Is This React's 'Log4Shell' Moment?
React2Shell invites immediate Log4Shell comparisons for one simple reason: blast radius. A single unauthenticated POST can trigger remote code execution on almost any app using vulnerable React Server Components, from hobby Next.js projects to high‑traffic SaaS dashboards. Like Log4Shell, the bug lives in infrastructure code that thousands of teams adopted implicitly, not in an obvious “security-sensitive” feature they consciously turned on.
Log4Shell rode on Log4j’s ubiquity in Java stacks; React2Shell rides on React’s dominance in modern web frontends. Any framework that wired in the React Flight protocol—Next.js, React Router’s data APIs, emerging RSC frameworks—suddenly inherited a CVSS 10.0 liability. That kind of ecosystem-level dependency risk is exactly what made Log4Shell a once-in-a-decade incident.
Where Log4Shell shattered assumptions about logging libraries, React2Shell shreds the mental model of “frontend” frameworks. React Server Components blur the line between client and server, but this bug proves the security boundary blurred too. A component tree now doubles as a protocol surface, and a serialization format quietly became a remote code execution primitive.
Security responsibility used to split cleanly: backend teams guarded APIs and databases; frontend teams worried about XSS and CSRF. RSC-era stacks erase that divide. When a JSX component can trigger server logic via a custom wire format, a “frontend” change can ship a backend vulnerability, and neither side may feel fully accountable.
Developers now have to ask an uncomfortable question: do Server Components have a security model they can actually reason about? React2Shell exposed how few people understood Flight’s internals before this week, including many who deployed it to production. If you cannot explain which inputs hit the deserializer, you cannot meaningfully threat-model your app.
Framework authors face a harsher mandate. Protocols like Flight, Remix’s data loaders, or tRPC’s custom encodings all deserve the same scrutiny as ORM query builders or TLS stacks. That means formal threat models, fuzzing, red‑team reviews, and third‑party audits focused on serialization, reference resolution, and cross‑boundary data flows.
React’s quick patch prevented a full Log4Shell‑scale meltdown, but the warning shot is loud. Any tool that moves logic across the network—no matter how “frontend” it looks—now belongs squarely in the category of critical attack surface.
Frequently Asked Questions
What is the React2Shell vulnerability (CVE-2025-55182)?
React2Shell is a critical unauthenticated Remote Code Execution (RCE) vulnerability in React Server Components. It allows an attacker to run arbitrary code on a server using a single malicious request, and it has the highest possible severity score of CVSS 10.0.
Which versions of React and Next.js are affected?
The vulnerability affects React Server versions 19.0.0 through 19.2.0. This includes frameworks that use it, such as Next.js. Any Next.js version using an affected React Server version, like the 16.0.6 version shown in demos, is vulnerable. Users should upgrade to the latest patched versions immediately.
How does the React2Shell exploit work?
The exploit abuses an unsafe deserialization flaw in the React Flight protocol. By sending a crafted payload, an attacker can cause a 'prototype pollution' vulnerability, modifying core JavaScript object behaviors to ultimately chain commands and execute code on the server.
Is a WAF enough to protect my application from React2Shell?
No. While Web Application Firewalls (WAFs) from providers like Vercel and Cloudflare can block known attack patterns, they are not a complete solution. Attackers can often obfuscate payloads to bypass WAF rules. The only reliable fix is to upgrade your React and Next.js dependencies to patched versions.