Critical React Server Components Vulnerability ‘React2Shell’ Exposes Deep Deserialization Risks in Flight Protocol

Critical React Server Components Vulnerability ‘React2Shell’ Exposes Deep Deserialization Risks in Flight Protocol

A critical remote code execution (RCE) vulnerability, dubbed "React2Shell" and assigned a CVSS score of 10.0, has exposed profound structural risks within React Server Components (RSC) and their custom communication protocol, Flight. Discovered and detailed by security researcher Durgesh Pawar, this unauthenticated flaw allowed attackers to achieve full shell access to affected Node.js servers running React applications, often with a single crafted HTTP request. The vulnerability, identified as CVE-2025-55182, sent immediate ripples through the cybersecurity community, highlighting the often-overlooked attack surface introduced by novel, framework-specific data streaming mechanisms.

Understanding React Server Components and the Flight Protocol

React Server Components represent a significant architectural shift in web development, aiming to enhance performance and user experience by allowing developers to render components entirely on the server. This approach enables faster initial page loads, reduced client-side JavaScript bundles, and more efficient data fetching. Unlike traditional server-side rendering, which typically sends HTML to the client, RSCs leverage a unique, proprietary streaming protocol known as Flight.

Flight is not a conventional data format like JSON or HTML. Instead, it’s a line-delimited stream designed to reconstruct interactive user interfaces on the client dynamically. This protocol includes its own type system, reference resolution, and rules for assembling executable behavior. Most React developers, focused on the declarative UI paradigm, rarely inspect Flight payloads in their browser’s network tab, implicitly trusting the framework to handle the intricate process of marshaling and unmarshaling component trees, module references, and even server-callable functions. This trust, as the React2Shell vulnerability painfully demonstrated, harbored a potent deserialization sink.

Chronology of a Critical Discovery

The investigation into Flight’s vulnerabilities began in earnest following the public disclosure of CVE-2025-55182 in December 2025. Durgesh Pawar, driven by the severity of the initial report, embarked on a deep dive into the Flight protocol’s inner workings. His research quickly revealed that React2Shell was not merely an isolated parsing error but a symptom of a deeper systemic issue: Flight’s fundamental nature as a deserialization system.

Pawar meticulously analyzed the core files responsible for Flight’s client-side parsing (ReactFlightClient.js, specifically parseModelString, getChunk, reviveModel, and getOutlinedModel) and server-side serialization (ReactFlightServer.js, ReactFlightReplyServer.js). His findings confirmed that Flight reconstructs complex executable references, lazy-loaded components, server-side Remote Procedure Call (RPC) endpoints, and asynchronous state from a stream of plain text. This process, while innovative for UI streaming, mirrors the architecture of deserialization systems historically plagued by critical security flaws across various programming languages.

Anatomy of a Deserialization Sink: The $ Prefix System

The true complexity and attack surface within Flight reside in its $ prefix system. When the client-side parser encounters a string value prefixed with a dollar sign, it deviates from treating it as literal text. Instead, it intercepts the string, evaluates the prefix, and routes it through a type-specific resolution path within the parseModelString function. This mechanism allows Flight to differentiate between simple model references ($), property accesses ($:), JavaScript Symbols ($S), callable Server Actions ($F), lazy components ($L), internal Chunk objects ($@), and binary data ($B).

The $: prefix, in particular, proved to be a critical vector. It enables the protocol to specify deep property paths, such as $1:user:name, instructing the parser to resolve a chunk (identified by 1), then access its user property, and subsequently its name property. This arbitrary property traversal, driven by attacker-controlled data within the stream, is a classic pattern exploited in JavaScript prototype pollution attacks. Furthermore, the $@ prefix, which exposes the raw internal Chunk wrapper object rather than its resolved value, provides a mutable handle that can be manipulated, especially in conjunction with JavaScript’s "Thenable" duck-typing mechanism, where any object with a callable .then property is treated as a Promise.

These features transform Flight from a mere data format into a system that dictates behavior. It instructs the client runtime on what code to load, which functions to call, and what internal state to trust. When an attacker can influence the stream’s content, they gain control over the parser’s control flow, dictating object construction and exposure of internal framework state, a hallmark of severe deserialization vulnerabilities.

The Mechanics of React2Shell: From Input to RCE

CVE-2025-55182, or React2Shell, materialized precisely from this structural weakness. The root cause lay within getOutlinedModel, a server-side function (ReactFlightReplyServer.js) responsible for resolving deep property paths specified by the $: reference system. The vulnerable code iterated through path segments without validating property ownership:

for (key = 1; key < reference.length; key++)
    parentObject = parentObject[reference[key]];

This two-line loop, critically missing a hasOwnProperty check, allowed an attacker to inject __proto__ or constructor into the path segments. By crafting a reference like $1:__proto__:constructor:constructor, the attacker could traverse the JavaScript prototype chain from a benign JSON object, through Object.prototype, to the Object constructor, and ultimately to the Function constructor. In JavaScript, Function("arbitrary code")() acts as an eval() equivalent, enabling the execution of arbitrary code.

The full gadget chain, as detailed in comprehensive security write-ups by firms like Resecurity, leveraged several Flight protocol features. This sequence of actions allowed the attacker to:

  1. Inject a malicious payload: A crafted Flight payload containing the prototype pollution path.
  2. Trigger deserialization: The server, processing an incoming request to a Server Function endpoint, would attempt to deserialize this payload.
  3. Achieve prototype pollution: The getOutlinedModel function, lacking proper checks, would traverse the prototype chain via __proto__ or constructor, modifying shared prototypes.
  4. Execute arbitrary code: Subsequent operations, relying on the now-polluted prototypes, would invoke the Function constructor with attacker-supplied code, leading to remote code execution.

This exploit required no authentication, no special privileges, and could be triggered with a single HTTP request, making it a dream scenario for attackers and a nightmare for defenders.

Real-World Fallout: State-Sponsored Exploitation

The impact of React2Shell was immediate and severe. Its CVSS 10.0 score underscored the maximum severity: complete compromise, no authentication, high confidentiality, integrity, and availability impact. The U.S. federal Cybersecurity & Infrastructure Security Agency (CISA) promptly added CVE-2025-55182 to its Known Exploited Vulnerabilities (KEV) catalog, mandating federal agencies to patch the flaw without delay. This move signaled the vulnerability’s critical nature and its active exploitation in the wild.

Within hours of the disclosure, evidence of exploitation emerged. Cybersecurity firm Sysdig published research linking "EtherRAT" deployments to North Korean state-sponsored actors. These sophisticated attackers weaponized React2Shell to deploy file-less implants that utilized the Ethereum blockchain for command-and-control (C2) communications—a technique dubbed "EtherHiding." This innovative C2 channel made takedown attempts exceedingly difficult, as the blockchain infrastructure is inherently resilient to seizure.

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

Separately, Palo Alto Networks’ Unit 42 documented the use of a backdoor named "KSwapDoor," which masqueraded as a legitimate Linux kernel process ([kswapd1]) to blend into system process lists. Their analysis confirmed KSwapDoor leveraged RC4 encryption for internal string protection and AES-256-CFB with Diffie-Hellman key exchange for secure C2 communications across a peer-to-peer mesh network. The rapid, sophisticated deployment of such implants by state-sponsored groups through an unauthenticated RCE highlighted the urgent need for patching and robust security measures.

The Patch and Persistent Concerns

The React team responded swiftly and decisively to the crisis. The fix implemented for CVE-2025-55182 was targeted and effective: it involved caching the genuine hasOwnProperty method at module load time (var hasOwnProperty = Object.prototype.hasOwnProperty;) and then invoking it using .call() for every property check in the deserialization path (hasOwnProperty.call(value, i);). This approach ensures that even if an attacker attempts to shadow or pollute hasOwnProperty on a malicious object, the original, untampered prototype method is always used, effectively blocking the prototype chain traversal that powered the React2Shell gadget chain.

This crucial patch was shipped in React versions 19.0.1, 19.1.2, and 19.2.1. While the fix successfully neutralizes the known RCE vector, Durgesh Pawar and other security researchers noted a significant caveat: the patch addresses the symptom (the missing hasOwnProperty check) rather than the underlying design choice. The Flight protocol’s $: prefix still allows for arbitrary property traversal, albeit now with proper ownership validation at each step. This structural aspect, where internal framework primitives and execution flow are exposed and driven by network-facing protocol data, remains a potential source of future vulnerabilities.

A Cascade of Vulnerabilities: Beyond React2Shell

React2Shell was just the first domino. The intense security audits that followed its disclosure uncovered a series of related vulnerabilities within the same Flight deserialization surface, further underscoring its complexity:

  • Denial-of-Service (DoS) Vulnerabilities:
    • CVE-2025-55184 (CVSS 7.5): Discovered shortly after the RCE, this flaw involved infinite recursion of nested Promises during Server Function deserialization, capable of hanging the Node.js event loop. Fixed in 19.0.2, 19.1.3, 19.2.2.
    • CVE-2025-67779 (CVSS 7.5): An incomplete fix for CVE-2025-55184, demonstrating how challenging it is to fully patch complex deserialization issues. It addressed edge cases missed by the first patch. 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 exploited unbounded request body buffering and zipbomb-style decompression, leading to server memory exhaustion. Fixed in 19.0.4+, 19.1.5+, 19.2.4+.
  • Information Disclosure:
    • CVE-2025-55183 (CVSS 5.3): A "sneaky" source code exposure bug. Crafted requests could trick a Server Function into reflecting its own source code back in the response if it implicitly or explicitly stringified one of its arguments (e.g., for logging). This could expose sensitive business logic, database queries, and hardcoded secrets. Fixed in 19.0.1, 19.1.2, 19.2.1.
  • CSRF Bypass:
    • CVE-2026-27978 (CVSS 5.3): A cross-site request forgery (CSRF) bypass in Next.js’s Server Action handling. Next.js typically validates that the Origin header matches the Host header. However, when a request originated from a sandboxed <iframe>, browsers send Origin: null. Next.js’s action-handler.ts 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 rapid succession of vulnerabilities underscored that the initial RCE was indeed a symptom of deeper, systemic risks inherent in Flight’s design and its integration within the broader React/Next.js ecosystem.

Proactive Defenses for React Applications

Given the persistent risks, developers and organizations must implement robust, multi-layered defenses. Relying solely on framework-level patches is insufficient when dealing with complex deserialization attack surfaces.

  1. Strict Input Validation on Server Actions: This is paramount. Every Server Action must begin with comprehensive schema validation (using libraries like Zod or Valibot) for all incoming arguments, before any business logic executes or properties are destructured. This ensures that only trusted, well-formed data interacts with application logic.
  2. The server-only Package: Crucial for preventing sensitive server-side code (database credentials, API keys, internal logic) from accidentally being bundled or imported into client components. It acts as a build-time guardrail, though developers must be wary of "barrel files" that can inadvertently re-export server-only modules.
  3. Robust CSRF Protections: Beyond framework defaults, high-value Server Actions require explicit CSRF tokens and careful cookie configurations (SameSite=Strict or SameSite=Lax). Developers must strictly avoid adding 'null' to experimental.serverActions.allowedOrigins in Next.js configurations, as this reopens the CVE-2026-27978 bypass.
  4. Prompt Patching and Version Management: Regularly audit and update react, react-dom, and react-server-dom-webpack packages. The RCE fix requires React 19.0.1+, 19.1.2+, or 19.2.1+. Subsequent DoS fixes require 19.0.4+, 19.1.5+, or 19.2.4+. Staying current is non-negotiable.
  5. Leveraging the Taint API (with caveats): React’s taintObjectReference and taintUniqueValue can serve as useful development-time guardrails, throwing errors if sensitive object references or string values are accidentally serialized to the client. However, it tracks references, not data content, and is easily bypassed by routine data transformations (e.g., spreading an object). It’s a useful defense-in-depth, but not a primary security boundary.
  6. Strategic Use of Web Application Firewalls (WAFs): WAFs can provide a detection and blocking layer for known attack patterns (e.g., __proto__, constructor:constructor in Next-Action headers, E{"digest" error leakage). While bypassable by sophisticated attackers using padding or encoding tricks, WAFs are valuable for mitigating automated scans and low-effort attacks.

Unaddressed Structural Exposures

Despite the patches, several structural risks inherent to Flight’s design remain:

  • Man-In-The-Middle (MITM) on the Flight Stream: If an attacker can intercept and modify the plain-text Flight stream (e.g., via CDN compromise or rogue proxy), they could inject malicious I (Import) rows to redirect component loading, embed hidden RPC triggers with F (Server Reference) tags, or manipulate D (Data) rows to introduce XSS if client components use dangerouslySetInnerHTML.
  • Server Action Enumeration: The server-reference-manifest.json file, which maps obfuscated action IDs to their source implementations, can be exposed due to misconfigured hosting or path traversal. This provides attackers with a complete API map, enabling direct IDOR and parameter tampering attacks against Server Actions.
  • Encrypted Closure Tampering: Server Actions can capture variables from their surrounding scope, which Next.js encrypts using NEXT_SERVER_ACTIONS_ENCRYPTION_KEY before sending to the client. If this key is static and falls into attacker hands (e.g., via file read access), they can decrypt, modify, and re-encrypt closure state, forging legitimate-looking requests with altered user IDs, roles, or query parameters.
  • Supply Chain Activation via Module IDs: An attacker could potentially inject I import references into the Flight stream to load dormant, compromised modules from the node_modules directory that were bundled but not explicitly referenced by legitimate components. This would activate malicious code present in the bundle without requiring any code changes in the application itself.

Historical Precedent: A Recurring Pattern

The challenges faced by React Flight are not unprecedented. History is replete with examples of frameworks that invented custom serialization formats for server-client communication, only to discover they introduced severe attack surfaces.

  • Google Web Toolkit (GWT): Utilized a custom RPC protocol to synchronize Java objects. BishopFox famously demonstrated that manipulating its binary wire format could lead to arbitrary deserialization and RCE. GWT eventually had to disable binary serialization entirely after years of patching efforts.
  • Java Server Faces (JSF) and ASP.NET ViewState: Both frameworks serialized rich, stateful data to the client, often as hidden form fields. When cryptographic signing was weak or absent, attackers could tamper with this serialized state to achieve RCE. Microsoft and Oracle battled these issues through multiple patch cycles.

The underlying pattern is clear: frameworks aiming for seamless, rich server-client interaction often devise proprietary wire formats that deserialize complex, sometimes executable, data. When designers assume the server is the sole trusted producer and the client a trusted consumer, they create blind spots. Attackers inevitably find ways to manipulate these formats in transit or trick the server into deserializing malicious input. React Flight is the latest, but certainly not the last, entry in this recurring security narrative.

The Path Forward: Securing Server-Driven UIs

React Flight addresses a genuinely complex problem: streaming interactive component trees efficiently, enabling 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, has proven to be a dangerous design choice.

The hasOwnProperty fix, along with subsequent patches for DoS and information disclosure, are essential and commendable efforts by the React team. Yet, the architectural decision to expose powerful internal primitives like arbitrary property traversal ($:) and executable Thenable reconstruction ($@) through a network-facing parser, without initially robust validation, represents a fundamental design flaw.

As more frameworks embrace server-driven UI patterns, the industry must evolve beyond the simplistic assumption of "the server is trusted." Future security primitives will need to include:

  • Cryptographic validation of serialized payloads: Ensuring that the server can verify the integrity and authenticity of incoming data streams.
  • Signed component trees: Attaching cryptographic signatures to streamed UI components to prevent tampering.
  • Content integrity checks: Implementing mechanisms to detect unauthorized modifications to the Flight stream itself.

Hoping that complex deserialization parsers will catch every edge case has historically failed. The React2Shell vulnerability serves as a stark reminder that developers must understand the underlying mechanisms their frameworks employ, particularly when those mechanisms cross security boundaries and process untrusted network input. The code in react-client/src/ReactFlightClient.js is not just implementation detail; it is a critical security surface that every developer shipping React Server Components should comprehend.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

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