Unpacking the React2Shell Vulnerability: How the Flight Protocol Exposes React Server Components to Remote Code Execution

While React Server Components rely on the custom Flight protocol to stream interactive user interfaces, this same mechanism introduces powerful deserialization sinks that attackers can exploit. Security researcher Durgesh Pawar has broken down the mechanics behind the CVSS 10.0 “React2Shell” vulnerability, demonstrating how protocol manipulation can lead directly to remote code execution. The analysis also outlines a practical, ranked set of defenses—ranging from strict schema validation to cross-site request forgery hardening—designed to secure modern React applications against these structural risks.

React Server Components do not send traditional HTML to the browser, nor do they send standard JSON. When a server component renders, what actually travels over the wire is a custom streaming protocol known as Flight. It is a line-delimited format featuring its own distinct type system, reference resolution rules, and mechanisms for reconstructing executable behavior directly on the client side. Most React developers have never opened their browser’s network tab to look closely at a Flight payload, which typically resembles a mix of JSON fragments, dollar-sign-prefixed references, and module pointers that the React runtime silently reassembles into a live component tree. Because the framework handles this complexity automatically, few teams have paused to consider what that underlying level of trust actually implies.

The security community gained a stark reminder of these implications following the disclosure of CVE-2025-55182 in December 2025. Quickly dubbed "React2Shell," the vulnerability represented a critical CVSS 10.0, unauthenticated remote code execution flaw sitting squarely within the Flight deserialization layer. A single crafted HTTP request directed at a Server Function endpoint granted an attacker full shell access without requiring any credentials. The Cybersecurity and Infrastructure Security Agency promptly added the flaw to its Known Exploited Vulnerabilities catalog, while security firms such as Sysdig tied in-the-wild exploitation to North Korean state-sponsored actors deploying file-less implants via the Ethereum blockchain.

A deeper examination of the source code—particularly within the chunk resolution logic of getOutlinedModel and getChunk—reveals that React2Shell was never a mere one-off parsing bug. Instead, it exposed a fundamental characteristic of the architecture: Flight reconstructs executable references, lazy-loaded components, server remote procedure call endpoints, and asynchronous state directly from a stream of text. That functionality constitutes a fully fledged deserialization system, meaning the total attack surface extends far beyond a single missing property check.

Flight On The Wire

Inspecting network traffic on modern application router pages reveals responses bearing the text/x-component content type, confirming the use of Flight. Rather than arriving as a single monolithic JSON blob, Flight employs a streaming, line-delimited format where each individual line functions as a self-contained row processed by the client-side React runtime as soon as it crosses the connection.

Every row adheres to a strict syntax combining a numeric row identifier, a tag indicating the data type, and the payload itself. Import directives instruct the client to load specific modules from the bundler chunk map, while JSON tree tags construct virtual DOM nodes, component props, and HTML elements. Cross-chunk pointers and environment definitions weave these elements together, resulting in a rich mix of structural data and module references that sets Flight apart from plain text formats.

Beneath the row tags lies a complex prefix system. When the client-side parser encounters a string value starting with a dollar sign, it intercepts the text, checks the prefix, and routes it through a specialized resolution path. Model references resolve to other chunks in the stream, property access prefixes traverse properties on resolved chunks, and server references represent callable server actions acting as remote procedure call endpoints. Other prefixes handle native symbols, lazy-loaded components, raw chunk wrappers, and binary data blobs.

This prefix system highlights a crucial reality: Flight is not simply JSON with extra steps. JSON provides static data, whereas Flight delivers executable behavior. It orchestrates code loading, establishes remote procedure call endpoints, sets up promise chains, and builds lazy-loaded component boundaries that execute dynamically on demand.

Why Flight Is A Deserialization Sink

The history of software security is replete with deserialization vulnerabilities, from Java object streams and Python pickle files to PHP unserialize chains and .NET binary formatters. The common pattern involves deserializing attacker-controlled input, invoking behavior during the reconstruction phase, and ultimately losing control of execution.

While raw JavaScript code parsed via standard JSON methods remains immune to such execution chains because plain data objects lack constructors or magic methods, introducing custom framework deserialization layers changes the equation entirely. Flight relies on prototype-based inheritance and property traversal to reconstruct its object trees. When the parser walks colon-separated property paths, it iterates through each segment and accesses it on the parent object. If those path segments include prototype or constructor references, the traversal walks straight up the prototype chain, opening the door to prototype pollution and arbitrary execution vectors.

Weaponizing And Defending The React Flight Protocol: Deserialization Sinks In RSCs — Smashing Magazine

Furthermore, the JavaScript runtime treats any object containing a .then property as a thenable, automatically invoking it whenever an operation awaits resolution. If an attacker manages to inject a manipulated thenable into the chunk resolution pipeline, the runtime executes the attacker’s function during normal asynchronous processing. Because the protocol dictates its own control flow based on stream contents, an attacker who influences the stream effectively controls which functions the parser calls, which objects it constructs, and which internal state it exposes.

The Mechanics Of React2Shell

CVE-2025-55182 provided a concrete demonstration of these risks. The vulnerability resided in getOutlinedModel, a function tasked with resolving deep property paths from the reference system during server-side reply handling. When encountering a path containing colons, the parser split the string and walked the path segment by segment inside a loop devoid of ownership validation.

By supplying a path traversing from a plain JSON object up through object prototypes to the function constructor, an attacker could leverage the constructor’s capability to evaluate arbitrary code. Constructing a complete exploit chain required combining several legitimate Flight protocol features, such as promise resolution handling and server reference forgery, demonstrating how complex protocol features can compose dangerously when exposed to untrusted input.

In the wild, exploitation was swift. Security researchers linked the vulnerability to state-sponsored campaigns utilizing file-less implants communicating via blockchain networks, as well as sophisticated backdoors designed to masquerade as kernel processes on compromised Linux systems. The speed and sophistication of these attacks underscored the urgency of patching critical deserialization flaws before they can be weaponized at scale.

The Fix and Ongoing Mitigations

The official patch provided by the React team targeted the root cause by caching the native hasOwnProperty method at module load time and utilizing it for every property check in the deserialization path. Even if an attacker attempts to shadow property check methods on malicious objects, the runtime falls back to the original prototype method, successfully blocking the prototype chain traversal used in the exploit gadget chain.

However, closing known gadget chains does not alter the fundamental dynamic of the architecture. The Flight protocol continues to reconstruct complex behavior from text streams before application code and authentication middleware can inspect the request. Consequently, developers must implement robust application-level defenses to limit their exposure.

Strict schema validation libraries such as Zod and Valibot represent the single most impactful application-level defense. Placing schema validation checks at the very beginning of every server action ensures that incoming arguments are thoroughly validated before any business logic or logging can execute. This prevents accidental exposure of sensitive internal data and rejects malformed payloads before the deserializer can process them in unintended ways.

Additionally, developers should leverage the server-only package to prevent sensitive modules containing database credentials or core business logic from being imported into client-side code. While this safeguards source code from crossing the network boundary, teams must still carefully filter return values to prevent sensitive data from leaking through component props.

Further hardening requires robust cross-site request forgery protections, including properly configured session cookies, explicit token validation for high-value operations, and strict adherence to allowed origin policies. Relying solely on framework defaults can leave applications vulnerable to edge cases, making defense-in-depth essential for securing server-driven architectures.

As modern web development continues to embrace server-driven user interface patterns, the security community faces the ongoing challenge of securing complex streaming protocols. While framework patches and application-level validations provide crucial protection, the evolution of these architectures highlights the continuous need for vigilance against structural risks in deserialization layers.

Share:

Muslim writes for Tech Maze.

Leave a comment