Web Development

React2Shell: Unpacking the CVSS 10.0 Remote Code Execution Vulnerability in React Server Components’ Flight Protocol

The landscape of modern web development was significantly shaken in December 2025 with the disclosure of CVE-2025-55182, a critical unauthenticated remote code execution (RCE) vulnerability dubbed "React2Shell." This flaw, rated a maximum CVSS 10.0, resides within the custom Flight protocol used by React Server Components (RSCs) to stream interactive user interfaces. The vulnerability exposed a profound deserialization sink, allowing attackers to manipulate the protocol and gain shell access to affected servers with a single, unauthenticated HTTP request. Its immediate and widespread exploitation by sophisticated threat actors, including North Korean state-sponsored groups, swiftly elevated it to a global cybersecurity concern, prompting warnings from agencies like the federal Cybersecurity & Infrastructure Agency (CISA).

Understanding React Server Components and the Flight Protocol

React Server Components represent a significant architectural shift in web development, allowing developers to render components entirely on the server and stream them to the client. This approach aims to improve performance, reduce client-side bundle sizes, and enhance developer experience by allowing direct server-side data access. Unlike traditional server-side rendering (SSR) that sends HTML, or client-side rendering (CSR) that relies on JSON APIs, RSCs employ a unique, custom streaming protocol known as Flight.

Flight is neither HTML nor standard JSON. It’s a line-delimited format designed to efficiently transmit a dynamic component tree, including executable references, lazy-loaded components, and server-side RPC endpoints, from the server to the client. Each line in a Flight payload serves as a "row," processed by the client-side React runtime as it arrives. These rows can contain JSON fragments, dollar-sign-prefixed references, and module pointers, all silently reassembled into a live, interactive UI. While innovative, this custom protocol introduces a complex deserialization system, a historically fertile ground for security vulnerabilities across various programming languages and frameworks.

The protocol’s elegance lies in its ability to reconstruct not just data, but behavior. This includes $I for importing client-side modules, $J for JSON tree structures, $M for module metadata, $D for server context, and crucially, $F for callable Server Actions (RPC endpoints) and $@ for exposing internal Chunk objects that track asynchronous resolution states. The $: prefix system, which allows for property traversal (e.g., $1:user:name), proved to be the Achilles’ heel, as it facilitates deep access into objects, including their prototypes.

The Anatomy of a CVSS 10.0: React2Shell Explained

The React2Shell vulnerability, specifically CVE-2025-55182, originated in the getOutlinedModel function within the server-side reply handling code (ReactFlightReplyServer.js). This function is responsible for resolving deep property paths specified by the $:reference system. The core vulnerability lay in a critical omission: the absence of hasOwnProperty checks during property access.

The vulnerable loop, for (key = 1; key < reference.length; key++) parentObject = parentObject[reference[key]];, directly accessed properties without verifying if they belonged to the object itself or its prototype chain. This allowed an attacker to inject __proto__ or constructor.prototype into the reference path. By constructing a Flight payload with a reference like $1:__proto__:constructor:constructor, an attacker could traverse the JavaScript prototype chain: from a benign object, up to Object.prototype, then to the Object constructor, and finally to the Function constructor. In JavaScript, the Function constructor can execute arbitrary code (e.g., Function("arbitrary code")()), effectively providing a remote code execution primitive.

The full gadget chain, as detailed by security researchers like Resecurity, demonstrated how several legitimate Flight protocol features could be chained:

  1. Arbitrary Prototype Pollution: The $: prefix system, combined with the missing hasOwnProperty check, allowed attackers to inject properties into Object.prototype, affecting all objects in the Node.js runtime.
  2. Function Constructor Access: Through prototype pollution, attackers could gain access to the Function constructor, enabling arbitrary code execution.
  3. Thenable Manipulation: Leveraging JavaScript’s duck-typing, where any object with a callable .then property is treated as a promise, attackers could inject a malicious then function into the prototype. When the React runtime awaited a manipulated object during normal asynchronous chunk resolution, the attacker’s code would execute.
  4. Server Action Invocation: Attackers could forge $F (Server Reference) tags to invoke server actions with manipulated arguments, potentially triggering the malicious then function or other polluted prototypes.

This complex interaction of protocol features, combined with a fundamental deserialization flaw, demonstrated that Flight was not merely a data format but a system capable of orchestrating behavior, making it a powerful attack surface when input is untrusted.

Immediate Fallout and Real-World Exploitation

The impact of React2Shell was immediate and severe. The CVSS 10.0 rating signifies maximum severity, indicating unauthenticated exploitation, complete compromise of confidentiality, integrity, and availability, and trivial exploit complexity.

The vulnerability was swiftly weaponized in the wild. Sysdig, a prominent cloud security firm, published research linking early exploitation to North Korean state-sponsored advanced persistent threat (APT) actors. These groups deployed EtherRAT, a novel file-less implant that utilized the Ethereum blockchain for command-and-control (C2) communications, a technique dubbed "EtherHiding." This innovative C2 channel made takedown efforts exceptionally challenging, as the blockchain itself cannot be seized.

Concurrently, Palo Alto Networks’ Unit 42 documented another backdoor, KSwapDoor, which masqueraded as a legitimate kernel process ([kswapd1]) on infected Linux systems. KSwapDoor employed RC4 encryption for internal strings and configuration, while C2 communications relied on AES-256-CFB with Diffie-Hellman key exchange over a peer-to-peer mesh network. The speed and sophistication of these state-sponsored campaigns underscored the urgent need for patching and the critical nature of deserialization vulnerabilities in widely adopted frameworks. CISA’s addition of CVE-2025-55182 to its Known Exploited Vulnerabilities Catalog further cemented its status as a top-tier threat, requiring immediate attention from all federal agencies and critical infrastructure organizations.

The Official Response and Remediation

The React team’s response to React2Shell was swift and targeted. The core fix involved caching the genuine hasOwnProperty method at module load time (var hasOwnProperty = Object.prototype.hasOwnProperty;) and then invoking this cached reference for every property check in the deserialization path (hasOwnProperty.call(value, i);). This mechanism effectively blocked the prototype chain traversal that fueled the gadget chain, preventing attackers from injecting malicious properties into shared prototypes.

This critical RCE fix was shipped in React versions 19.0.1, 19.1.2, and 19.2.1. Developers were urged to update their react, react-dom, and react-server-dom-webpack packages immediately. While the patch correctly neutralized the known exploit, security researchers noted that it addressed the symptom rather than fundamentally redesigning the protocol’s arbitrary property traversal mechanism. The $:prefix still allows colon-separated path walking, albeit now with proper ownership validation at each step. This observation raised concerns about potential future vulnerabilities emerging from the same architectural area.

Beyond React2Shell: A Cascade of Related Vulnerabilities

React2Shell was not an isolated incident but the first in a series of related vulnerabilities uncovered in the Flight deserialization surface following intensive security audits. While none reached the severity of the original RCE, they highlighted the inherent complexity and risk of custom deserialization.

  • Denial of Service (DoS) Vulnerabilities:

    • CVE-2025-55184 (CVSS 7.5): Disclosed shortly after React2Shell, this bug allowed attackers to trigger an infinite recursion of nested Promises during Server Function deserialization, hanging the Node.js event loop and leading to a DoS. Fixed in 19.0.2, 19.1.3, 19.2.2.
    • CVE-2025-67779 (CVSS 7.5): An incomplete fix for CVE-2025-55184, this vulnerability demonstrated that edge cases in complex deserialization logic are notoriously difficult to patch comprehensively. It allowed the same infinite loop via new vectors. Fixed in 19.0.4, 19.1.5, 19.2.4.
    • CVE-2026-23864 (CVSS 7.5): Disclosed in January 2026, this DoS/Out-of-Memory (OOM) vulnerability involved unbounded request body buffering and zipbomb-style decompression, leading to memory exhaustion. Fixed in 19.0.4+, 19.1.5+, 19.2.4+.
  • Information Disclosure:

    Weaponizing And Defending The React Flight Protocol: Deserialization Sinks In RSCs — Smashing Magazine
    • CVE-2025-55183 (CVSS 5.3): A "sneaky" vulnerability, this flaw allowed crafted requests to reflect Server Function source code back to the client. If a Server Function implicitly or explicitly stringified a malicious argument (e.g., for logging or debugging), the deserialization parser would expose the function’s internal code, potentially revealing business logic, database queries, or hardcoded secrets. Fixed in 19.0.1, 19.1.2, 19.2.1.
  • CSRF Bypass:

    • CVE-2026-27978 (CVSS 5.3): This vulnerability, specific to Next.js’s Server Action handling, was a Cross-Site Request Forgery (CSRF) bypass. Next.js’s built-in CSRF protection relied on validating that the Origin header matched the Host. However, when requests originated from a sandboxed <iframe>, browsers send Origin: null. The Next.js parser incorrectly treated Origin: null as a missing origin rather than an explicit cross-origin indicator, allowing attackers to invoke Server Actions with a victim’s authenticated session cookies. Fixed in Next.js 16.1.7.

This succession of vulnerabilities underscored the need for developers to remain vigilant and apply all relevant patches, not just the initial RCE fix.

Proactive Defenses for React Server Components

Securing applications built with React Server Components requires a multi-layered approach, acknowledging that the Flight protocol deserializes "behavior" before application-level logic can intervene. Durgesh Pawar outlines a ranked set of defenses:

  1. Input Validation on Server Actions (Zod, Valibot): This is the most impactful application-level defense. Every Server Action must begin with strict schema validation, ideally using libraries like Zod or Valibot. Validation should occur before any business logic, logging, or destructuring of the input. Using .safeParse() instead of .parse() prevents internal error details from being exposed. This practice ensures that business logic only ever operates on trusted, validated data shapes.

  2. The server-only Package: This simple yet effective package prevents server-side code (e.g., database credentials, internal APIs) from being accidentally bundled into client components. By importing "server-only" at the top of sensitive files, the build process fails if a client component attempts to import them, directly or indirectly. Developers must be cautious with "barrel files" that re-export both server-only and client-safe utilities, as this can circumvent the protection. It’s crucial to remember that server-only protects code from crossing the boundary, not data returned by server functions. Return values must be explicitly filtered to avoid leaking sensitive information.

  3. CSRF Protections: Beyond Next.js’s default Origin vs. Host header check, robust CSRF protection is essential for state-changing Server Actions. This includes:

    • Cookie Configuration: Explicitly setting SameSite=Strict or SameSite=Lax on session cookies.
    • Explicit CSRF Tokens: For high-value operations, generating and validating per-session CSRF tokens embedded in hidden form fields or custom headers.
    • allowedOrigins Caution: Never add 'null' to experimental.serverActions.allowedOrigins in Next.js configuration, as this reopens the CVE-2026-27978 bypass.
  4. Verify hasOwnProperty Patch and Other Fixes: Developers must ensure their react, react-dom, and react-server-dom-webpack packages are running patched versions (19.0.1+, 19.1.2+, 19.2.1+ for RCE; 19.0.4+, 19.1.5+, 19.2.4+ for DoS fixes). Regularly checking npm ls or yarn why is critical.

  5. The Taint API: React’s experimental taintObjectReference and taintUniqueValue functions serve as development-time guardrails. They register objects or strings with the runtime, causing an error if tainted data attempts to pass through the Flight serializer to the client. While useful for catching accidental data leaks (e.g., passing a full user object to a client component), taint tracks object references, not data content. Transformations like spreading (...user), accessing individual properties, or serialization/deserialization round-trips will break the taint tracking. It’s a useful defense-in-depth layer but not a primary security boundary against malicious input.

  6. Web Application Firewalls (WAFs): WAFs can provide an additional detection layer for known attack patterns. Rules can be configured to block requests containing __proto__ or constructor:constructor in POST bodies with Next-Action headers, flag responses leaking error digests (E{"digest"), or block excessively large Server Action payloads to mitigate DoS vectors. However, WAFs are easily bypassed by motivated attackers using padding or chunked transfer encoding to exceed inspection buffer limits. They are best treated as a noise-reduction and detection layer rather than a primary security control.

Enduring Structural Risks and Future Challenges

Despite the patches, some risks are inherent to Flight’s design and the nature of custom deserialization protocols.

  • Man-In-The-Middle (MITM) on the Flight Stream: If an attacker can intercept and modify the Flight stream (e.g., via CDN compromise or rogue proxies), they can inject malicious instructions. Since Flight is plain text, an attacker could alter $I (Import) rows to redirect component loading, inject $F (Server Reference) tags to embed hidden RPC triggers, or modify $D (Data) rows to change component props, potentially leading to XSS if the target component uses dangerouslySetInnerHTML. The protocol’s internal escaping mechanisms do not protect against a direct MITM attack.

  • Server Action Enumeration: Server Action IDs are obfuscated hashes, but if the server-reference-manifest.json (which maps IDs to source implementations) is exposed due to misconfigured hosting or path traversal, an attacker gains a complete API map. This enables standard IDOR (Insecure Direct Object Reference) and parameter tampering attacks, as developers might blindly trust inputs originating from React’s internal machinery.

  • Encrypted Closure Tampering: Server Actions often capture variables from their surrounding scope (closures). Next.js encrypts these using NEXT_SERVER_ACTIONS_ENCRYPTION_KEY. If this key, especially when static in multi-server setups, is compromised (e.g., via file read access or SSRF), an attacker can decrypt, modify (e.g., alter userId or role), and re-encrypt the closure state. The server would then accept this forged, malicious closure as legitimate.

  • Supply Chain Activation via Module IDs: A theoretical but plausible risk involves injecting $I import references into the Flight stream. A compromised npm package sitting in node_modules might be bundled into a chunk but remain dormant because no component imports it. A MITM attacker, however, could potentially inject an $I reference for this dormant module, causing the client-side runtime to load and execute the malicious code, even if it’s not explicitly referenced in the application’s source.

This phenomenon is not new. Historically, frameworks like Google Web Toolkit (GWT), Java Server Faces (JSF), and ASP.NET with its ViewState have all faced severe deserialization vulnerabilities due to custom wire formats that moved rich, stateful, or executable data between server and client. The consistent pattern is frameworks assuming a trusted server-to-client channel, only for attackers to demonstrate manipulation of the wire format or tricking the server into deserializing untrusted input. React Flight is the latest iteration of this recurring challenge.

Implications for Framework Design and Web Security

React Flight successfully addresses the complex problem of streaming interactive component trees to enable progressive hydration, async data loading, and server-driven code splitting. However, its reliance on serializing executable references, async state, module pointers, and RPC endpoints over a streaming text protocol, with implicit trust in the stream’s structure, proved to be a critical design flaw. The powerful internal primitives like $:, $@, and $B, when exposed through a parser that initially lacked robust ownership validation, led directly to severe vulnerabilities.

The React team’s commitment to patching known gadget chains is commendable, but the fundamental challenge remains. As more frameworks embrace server-driven UI patterns, the industry must move beyond the assumption of "the server is trusted." Future developments will necessitate stronger security primitives: cryptographic validation of serialized payloads, signed component trees, and comprehensive content integrity checks on the Flight stream itself. Relying solely on the framework to find and fix every edge case in a complex deserialization parser has historically proven insufficient. Developers must actively understand the underlying mechanisms of their frameworks and implement robust, application-level security measures to protect against these structural risks. The code in react-client/src/ReactFlightClient.js serves as a stark reminder of what frameworks trust on our behalf, and why continuous vigilance is paramount.

Related Articles

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button