Inside React2Shell: How the Flight Protocol Enabled RCE

A CVSS 10.0 flaw in React's Flight protocol let attackers chain property traversal into remote code execution with no login required.

MiHiR SEN
MiHiR SEN
·4 min read

React Server Components don't send plain HTML or JSON to the browser. What actually travels over the wire is a custom streaming format called Flight, a line-delimited protocol with its own type system and reference resolution rules for reconstructing executable behavior on the client. A recent technical breakdown argues that this design, while solving a genuinely hard streaming problem, also opened a serious deserialization attack surface, one that produced a CVSS 10.0 vulnerability nicknamed React2Shell.

Not JSON With Extra Steps

Each line of a Flight payload follows a row ID, a type tag, and a payload structure, where certain string values prefixed with a dollar sign get intercepted and routed through type-specific resolution logic rather than treated as literal text. One prefix hands back a raw internal chunk object instead of its resolved value, exposing framework-internal state; another performs arbitrary property-path traversal across a resolved object, walking colon-separated segments one at a time. Unlike plain JSON, Flight reconstructs module references that trigger code loading, RPC-callable server functions, and lazily resolved async state, behavior rather than static data.

Why JavaScript Isn't Actually Immune to Deserialization Bugs

Raw JSON.parse produces only plain data objects with no executable side effects, but that safety disappears once a framework layers custom resolution logic on top. JavaScript's prototype-based inheritance means an attacker who can inject a __proto__ key during reconstruction can modify shared base prototypes that every object inherits from, and any object carrying a callable then property gets automatically invoked by the language's own async machinery, no class check required.

How the Actual Exploit Chain Worked

The vulnerability lived in a path-resolution function that walked colon-separated property paths without checking whether a given property actually belonged to the object itself, as opposed to somewhere up its prototype chain. That gap let an attacker's supplied path walk from any plain object up through its prototype chain to the Function constructor, effectively JavaScript's equivalent of a code-eval primitive. Chaining several legitimate Flight protocol features together, resolving a raw internal chunk reference, manipulating its status field, and overwriting its resolution path during a second deserialization pass, gave attackers a mutable handle they could use to trigger arbitrary shell command execution. Crucially, the flaw required no authentication and no credentials, since deserialization happened before any application-level auth checks ran.

Exploited Within Hours by State-Linked Actors

The vulnerability was added to the federal Cybersecurity and Infrastructure Security Agency's known-exploited-vulnerabilities catalog, with research tying in-the-wild exploitation to North Korean state-linked actors deploying file-less implants that used the Ethereum blockchain for command-and-control communication, a technique making takedown difficult since a blockchain can't simply be seized. A separate backdoor observed by researchers masqueraded as a legitimate Linux kernel process, using layered encryption across a peer-to-peer mesh network for its own command-and-control traffic.

A Targeted, Correct Patch

React's fix caches the genuine object-ownership check at module load time, so even if an attacker shadows that property on a malicious object, the validation still uses the original, trusted method rather than the attacker's version, closing the specific prototype-chain traversal that powered the exploit chain. The fix shipped across React 19.0.1, 19.1.2, and 19.2.1.

Practical Defenses Beyond the Framework Patch

The most impactful step for developers, according to the breakdown, is placing strict schema validation at the very start of every server action, before any logging or business logic runs, since even logging an unvalidated argument can leak internal details through a related stringification bug. Keeping sensitive server-only code explicitly marked and out of shared module barrels, layering custom CSRF protections beyond a framework's defaults, and staying current on patched React and Next.js versions round out the recommended defenses; taint-tracking APIs and web application firewalls are described as useful but reference-based or bypassable safety nets rather than primary security boundaries.

Part of a Recurring Pattern

The piece situates React2Shell within a long history of frameworks inventing custom wire formats to move stateful, sometimes executable data between server and client, then discovering years later that the format itself is exploitable, pointing to earlier precedents in Google Web Toolkit's RPC protocol and ASP.NET's ViewState mechanism as similar cases that took years to fully resolve.