Web Development

Weaponizing and Defending the React Flight Protocol: A Deep Dive into Deserialization Vulnerabilities and Supply Chain Risks

The digital landscape was significantly shaken in December 2025 with the disclosure of CVE-2025-55182, dubbed "React2Shell," an unauthenticated remote code execution (RCE) vulnerability within React Server Components. This flaw, assigned a critical CVSS score of 10.0, exposed a fundamental security weakness in the custom streaming protocol known as Flight, which underpins the interactive user interfaces streamed by React Server Components. The vulnerability stemmed from powerful deserialization sinks that, when exploited, allowed attackers to achieve shell access with a single, crafted HTTP request to a Server Function endpoint, bypassing all authentication mechanisms. The incident underscored a critical need for developers to understand the intricate mechanics of Flight and implement robust defenses, ranging from strict schema validation on Server Actions to enhanced Cross-Site Request Forgery (CSRF) hardening.

Understanding React Server Components and the Flight Protocol

React Server Components (RSCs) represent a paradigm shift in web development, designed to optimize performance, reduce client-side bundle sizes, and enhance server-side data fetching capabilities. Unlike traditional React components that render entirely on the client or transmit HTML, RSCs leverage a unique, custom streaming protocol called Flight to communicate between the server and the browser. This protocol does not transmit raw HTML or JSON; instead, it’s a line-delimited format with its own type system, reference resolution, and rules for reconstructing executable behavior on the client.

Most React developers, accustomed to the framework’s abstraction, rarely inspect the Flight payload directly. When observed in a browser’s Network tab, it appears as a complex mix of JSON fragments, dollar-sign-prefixed references, and module pointers. The React runtime silently reassembles these into a live component tree, managing the intricacies behind the scenes. This seamless operation, while beneficial for developer experience, inadvertently fostered a sense of implicit trust in the protocol’s security, a trust that the React2Shell vulnerability would profoundly challenge. The core concept of Flight is to send "instructions" for building a UI, including executable references, lazy-loaded components, and server-side RPC endpoints, rather than just static data. This capability, while powerful, inherently creates a deserialization system, a class of components historically prone to severe security vulnerabilities across various programming languages and frameworks.

The Unveiling of React2Shell (CVE-2025-55182)

The React2Shell vulnerability, tracked as CVE-2025-55182, emerged from a critical flaw within the Flight deserialization layer. The core problem lay in how the protocol handled $ prefixes, particularly the $: property access mechanism. This prefix allows the protocol to specify deep property paths, such as $1:user:name, instructing the client-side parser to resolve a chunk (e.g., chunk 1), then access its .user property, and subsequently .name on the result.

The vulnerability resided specifically within the getOutlinedModel function in ReactFlightReplyServer.js, responsible for resolving these deep property paths. The critical oversight was a lack of a hasOwnProperty check during property traversal. The vulnerable loop for (key = 1; key < reference.length; key++) parentObject = parentObject[reference[key]]; blindly accessed properties without validating if they belonged directly to the object or were inherited from its prototype chain.

This omission allowed attackers to exploit a classic prototype pollution technique. By supplying a crafted reference like $1:__proto__:constructor:constructor, an attacker could traverse from a plain JSON object up through Object.prototype, 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 turning property traversal into a remote code execution vector.

The full exploit chain for React2Shell involved several steps, each leveraging a legitimate Flight protocol feature in an unintended way:

  1. Manipulating Internal Objects: An attacker would first send a Flight payload to trigger the creation of an internal Chunk wrapper object.
  2. Gaining a Mutable Handle: Using the $@ prefix, which exposes the raw internal Chunk object itself rather than its resolved value, the attacker obtained a mutable handle to this object. This was a critical design flaw, exposing framework plumbing that should have been opaque.
  3. Prototype Pollution via Property Traversal: The attacker then used the $: prefix with the crafted __proto__:constructor:constructor path on the mutable Chunk object. This allowed them to modify the Function.prototype, enabling arbitrary code execution.
  4. Injecting a Server Reference: A $F (Server Reference) tag was then used to create a callable Server Action that, when invoked, would execute the attacker-controlled code injected into Function.prototype.
  5. Triggering Execution: Finally, the attacker would trigger the newly crafted Server Action, leading to unauthenticated remote code execution on the server.

This intricate gadget chain demonstrated the profound power the Flight protocol handed to an attacker capable of controlling the stream, transforming seemingly benign data instructions into a full-blown RCE.

Immediate and Widespread Exploitation

The impact of React2Shell was immediate and severe. Within hours of its disclosure, the vulnerability was actively exploited in the wild. The federal Cybersecurity & Infrastructure Agency (CISA) promptly added CVE-2025-55182 to its Known Exploited Vulnerabilities (KEV) catalog, signaling its critical nature and the urgent need for patching across all affected systems. CISA’s directive emphasized that federal agencies must apply patches for KEVs within strict deadlines, highlighting the significant threat posed by this vulnerability.

Security research firm Sysdig published findings linking in-the-wild exploitation of React2Shell to North Korean state-sponsored threat actors. These actors deployed sophisticated file-less implants, notably EtherRAT, which leveraged the Ethereum blockchain for command-and-control (C2) communications. This novel technique, termed "EtherHiding," made traditional takedown efforts nearly impossible, as the C2 infrastructure was distributed across an immutable blockchain.

Separately, Palo Alto Networks’ Unit 42 documented another backdoor, KSwapDoor, also exploiting CVE-2025-55182. KSwapDoor was observed masquerading as [kswapd1] on compromised Linux systems, artfully blending into process lists alongside the legitimate kswapd0 kernel swap daemon. Unit 42’s analysis revealed that KSwapDoor employed RC4 encryption for its internal strings and configuration data, while its C2 communications were secured with AES-256-CFB encryption and Diffie-Hellman key exchange over a peer-to-peer mesh network. The rapid weaponization by state-sponsored actors, deploying advanced implants through a single unauthenticated HTTP request, underscored the gravity of a CVSS 10.0 deserialization vulnerability and the imperative for immediate remediation.

The Framework’s Response: Patches and Subsequent Discoveries

The React team responded swiftly to React2Shell, releasing targeted patches to address the root cause. The primary fix involved caching the genuine hasOwnProperty method at module load time: var hasOwnProperty = Object.prototype.hasOwnProperty;. Subsequently, every property check in the deserialization path was updated to use .call() to invoke this cached reference (e.g., hasOwnProperty.call(value, i)). This ensured that even if an attacker managed to shadow hasOwnProperty on a malicious object, the original, untampered prototype method would be used, effectively blocking the prototype chain traversal that fueled the gadget chain. This crucial fix was shipped in React versions 19.0.1, 19.1.2, and 19.2.1.

However, React2Shell was not an isolated incident. The intense security audits following the December 2025 disclosure revealed a series of related vulnerabilities within the same deserialization surface, none as critical as the RCE but significant nonetheless. These included:

Weaponizing And Defending The React Flight Protocol: Deserialization Sinks In RSCs — Smashing Magazine
  • CVE-2025-55184 (CVSS 7.5, DoS): An infinite recursion of nested Promises during Server Function deserialization, capable of hanging the Node.js event loop. Fixed in React 19.0.2, 19.1.3, 19.2.2.
  • CVE-2025-67779 (CVSS 7.5, DoS): An incomplete fix for CVE-2025-55184, where specific edge cases still allowed the same loop to be exploited. This required a second round of patching in React 19.0.4, 19.1.5, 19.2.4. This highlights the inherent difficulty in fully securing complex deserialization parsers.
  • CVE-2026-23864 (CVSS 7.5, DoS/OOM): Disclosed in January 2026, this vulnerability involved unbounded request body buffering and zipbomb-style decompression, leading to memory exhaustion and denial of service. Fixed in React 19.0.4+, 19.1.5+, 19.2.4+.
  • CVE-2025-55183 (CVSS 5.3, Information Disclosure): A sneaky vulnerability where crafted requests could reflect Server Function source code back to the attacker if the function implicitly or explicitly stringified an attacker-controlled argument (e.g., via JSON.stringify or logging). This could expose business logic, database queries, and hardcoded secrets. Fixed in React 19.0.1, 19.1.2, 19.2.1.
  • CVE-2026-27978 (CVSS 5.3, CSRF Bypass): Specific to Next.js, this bug occurred because Next.js’s Server Action handling treated Origin: null (sent by sandboxed iframes) as a "missing" origin rather than an explicit "cross-origin" indicator. This allowed attackers to embed forms in sandboxed iframes and invoke Server Actions using a victim’s authenticated session cookies. Fixed in Next.js 16.1.7.

While these patches addressed known gadget chains and vulnerabilities, a critical observation remained: the React team hardened ownership checks but left the fundamental property traversal model ($:) intact. This reactive approach, while necessary, addressed the symptoms rather than the underlying design choice of exposing arbitrary property traversal through a network protocol. The architectural implications of this decision continue to be a subject of debate within the security community.

Beyond the Patch: Structural Risks and Proactive Defenses

The framework patches are essential, but relying solely on them to protect Server Components is insufficient. The Flight protocol continues to reconstruct behavior—executable references, module imports, RPC endpoints, async state—from a stream of text. This reconstruction occurs before application code runs, before validation logic fires, and before authentication middleware sees the request. Therefore, application-level defenses are paramount.

1. Input Validation on Server Actions (Zod, Valibot)
This is the single most impactful application-level defense. Every Server Action must begin with strict schema validation using libraries like Zod or Valibot. This validation must occur before any business logic executes or any properties are accessed on the incoming data. For instance, destructuring an object before validation can expose unvalidated properties to potential exploits. Developers should use safeParse() to avoid leaking internal error details and consider custom lint rules to enforce validation on all "use server" exports.

"use server"
import  z  from "zod"

const UpdateProfileSchema = z.object(
  name: z.string().min(1).max(100),
  email: z.string().email(),
  role: z.enum(["user", "editor"]),
)

export async function updateProfile(formData: FormData) 
  const parsed = UpdateProfileSchema.safeParse(
    name: formData.get("name"),
    email: formData.get("email"),
    role: formData.get("role"),
  )
  if (!parsed.success) return  error: "Invalid input" 
  // Only proceed with parsed.data

2. The server-only Package
The server-only package serves as a crucial build-time guardrail. Importing it at the top of files containing sensitive server-side logic (database credentials, API keys, internal business logic) ensures that these files cannot be accidentally imported by client components. If such an import occurs, the build process will fail, preventing server-only code from leaking to the client bundle. Developers should be cautious of "barrel files" that re-export both server-only and client-safe utilities, as this can inadvertently bypass the protection. It’s vital to remember that server-only protects code, not data. Sensitive data returned by server components must be explicitly filtered before being passed as props to client components.

3. CSRF Protections
Given the Origin: null bypass (CVE-2026-27978), relying solely on framework defaults for CSRF protection is insufficient. For state-changing Server Actions, developers should implement layered defenses:

  • Cookie Configuration: Explicitly set SameSite=Strict or SameSite=Lax on session cookies.
  • Explicit CSRF Tokens: For high-value operations, generate a per-session CSRF token on the server, embed it in a hidden form field or custom header, and validate it within the Server Action.
  • allowedOrigins Caution: Never add 'null' to experimental.serverActions.allowedOrigins in Next.js configuration, as this reopens the CVE-2026-27978 bypass.

4. The hasOwnProperty Patch Verification
Developers must verify that their React and Next.js installations are running patched versions. The RCE fix landed in React 19.0.1, 19.1.2, and 19.2.1. Additionally, the subsequent DoS fixes (CVE-2025-55184, CVE-2025-67779, CVE-2026-23864) require React 19.0.4+, 19.1.5+, or 19.2.4+. Regular dependency audits and updates are non-negotiable.

5. The Taint API
React’s experimental taintObjectReference and taintUniqueValue functions allow developers to mark sensitive objects or strings. If tainted data attempts to pass through the Flight serializer to the client, React throws an error. This is a useful development-time guardrail for preventing accidental data leakage (e.g., passing a full user object with password hashes to a client component). However, taint tracks object references, not data content. Any derivation (e.g., spreading an object, accessing individual properties, or serialization round-trips) will break the taint tracking. It should be considered a defense-in-depth measure, not a primary security boundary against a motivated attacker.

6. Web Application Firewalls (WAFs)
WAFs can act as a detection and secondary blocking layer for known attack patterns. They can inspect POST requests with the Next-Action header for suspicious patterns like __proto__ or constructor:constructor and flag responses indicating internal error leakage (E{"digest"). WAFs can also mitigate DoS attacks by blocking excessively large Server Action payloads. However, sophisticated attackers can often bypass WAF inspection buffers using techniques like padding or chunked transfer encoding. WAFs are valuable for reducing noise and blocking low-effort attacks but should not be relied upon as the sole or primary security control against advanced threats.

Enduring Vulnerabilities and Future Attack Vectors

Despite the patches, several structural risks inherent to the Flight protocol remain, representing potential future attack vectors:

  • Man-In-The-Middle (MITM) on the Flight Stream: If an attacker can intercept and modify the Flight stream in transit (e.g., via CDN compromise, cache poisoning, or a rogue proxy), they can directly inject malicious protocol instructions. The plain-text, predictable format makes this feasible. 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 framework’s escaping of $ prefixes in user-supplied data does not protect against a MITM attacker directly writing raw protocol.
  • Server Action Enumeration: Server Action IDs are obfuscated hashes, but the server-reference-manifest.json file maps these IDs to their source implementations. If this manifest is exposed (e.g., due to misconfigured hosting, an exposed .next directory, or path traversal), an attacker gains a complete API map. This allows for standard IDOR (Insecure Direct Object Reference) and parameter tampering attacks, as attackers can forge direct requests with manipulated arguments, often against inputs that developers implicitly trust due to their origin from React’s internal machinery.
  • Encrypted Closure Tampering: Server Actions that capture variables from their surrounding scope (closures) have these variables encrypted by Next.js before being sent to the client. The encryption key, NEXT_SERVER_ACTIONS_ENCRYPTION_KEY, is typically regenerated per build. However, multi-server setups often use a static key. If an attacker gains file read access, they can extract this key, decrypt the closure state, modify sensitive values (e.g., userId, role, query parameters), and re-encrypt it. The server would then accept this forged closure as legitimate, leading to privilege escalation or data manipulation.
  • Supply Chain Activation via Module IDs: Flight references client components by module ID (e.g., ["360","static/chunks/app/page-7f3480.js"]). These IDs are assigned at build time. A compromised npm package, even if not directly imported by application code, could be bundled into a chunk. If an attacker can inject $I import references into the Flight stream (via MITM or server-side injection), the parser might load this dormant, malicious module. This attack vector does not require the package to be explicitly imported; it only needs to exist within the bundle output and have a valid module ID.

Historical Precedent: A Recurring Security Pattern

The challenges faced by React Flight are not unique; they echo a recurring pattern in software security. Frameworks that introduce custom serialization formats for server-client communication have historically become significant attack surfaces.

  • Google Web Toolkit (GWT): GWT utilized a custom RPC protocol to synchronize Java objects between the browser and server. Security researchers, notably BishopFox, demonstrated that manipulating its binary wire format could lead to arbitrary deserialization, eventually prompting GWT to disable binary serialization entirely after years of iterative patches.
  • Java Server Faces (JSF) and ASP.NET ViewState: Both frameworks serialized ViewState to the client, often as a hidden form field. When cryptographic signing was weak or absent, attackers could tamper with the serialized state to achieve remote code execution. Microsoft and Oracle released numerous patches, but the underlying pattern of trusting client-provided serialized state continued to resurface as a vulnerability.

The pattern is consistent: a framework designs a custom format to transfer rich, stateful, and often executable data, assuming the server is the sole producer and the client a trusted consumer. Attackers then demonstrate that this format can be manipulated in transit or that the server can be tricked into deserializing attacker-controlled input. React Flight is the latest, but likely not the last, entry in this long history of deserialization vulnerabilities.

Broader Implications for Server-Driven UI

The React Flight protocol is an innovative solution to genuinely complex problems in modern web development, 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 an inherent trust in the stream’s structure on both ends, introduces significant security challenges. While the React team has commendably patched known gadget chains and vulnerabilities, the core design choices, such as exposing arbitrary property traversal and executable Thenable reconstruction through a network-facing protocol, represent a fundamental risk. The initial omission of a simple hasOwnProperty check, leading to a CVSS 10.0 RCE, underscores this.

As the industry increasingly adopts server-driven UI patterns, the prevailing security primitives—often based on the assumption of a "trusted server"—are proving insufficient. The future of secure server-driven UIs will necessitate stronger, more explicit security mechanisms: cryptographic validation of serialized payloads, digitally signed component trees to ensure integrity and authenticity, and robust content integrity checks applied directly to the Flight stream itself. Developers working with React Server Components must move beyond implicit trust, actively understand the underlying mechanics of the Flight protocol, and proactively implement comprehensive application-level defenses. The lessons from React2Shell and its aftermath serve as a stark reminder that innovation, without rigorous security considerations from the ground up, can inadvertently open critical new attack surfaces.

Related Articles

Leave a Reply

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

Back to top button