← Back to Blogs
HN Story

Deconstructing React2Shell: A Deep Dive into a Critical RCE in React Flight

May 10, 2026

Deconstructing React2Shell: A Deep Dive into a Critical RCE in React Flight

The discovery of React2Shell (CVE-2025-55182) serves as a masterclass in curiosity-driven security research. What began as a simple desire to understand an undocumented protocol evolved into a critical remote code execution (RCE) vulnerability affecting millions of websites using React Server Components (RSC) and Server Functions.

This article breaks down the technical journey from initial curiosity to the final exploit chain, highlighting the dangers of undocumented protocols and the illusion of type safety in modern JavaScript frameworks.

The Mystery of the Flight Protocol

To understand React2Shell, one must first understand "Flight," the proprietary protocol used by Next.js and React to facilitate communication between the client and server for Server Components and Server Functions.

Unlike standard JSON, Flight allows for the transmission of complex JavaScript objects, including Date, BigInt, Map, and circular references. It achieves this by breaking messages into "chunks" and using a special $ syntax to denote types and references. For example, $D indicates a date, while $x refers to another chunk.

For security researchers, the primary red flag was the lack of documentation. The protocol was essentially a "black box," making it difficult for developers and security auditors to understand the attack surface. As one Hacker News commenter noted, this lack of specification made identifying indicators of compromise (IoC) significantly harder after the vulnerability was disclosed.

The First Crack: Prototype Pollution and Type Coercion

The vulnerability began with a "glaring omission of a safety check" regarding how Flight handles property references. The researcher discovered that Flight allows referencing properties not just on an object, but also on its prototype.

This opens the door to type coercion attacks. In many Next.js applications, developers use TypeScript to define the types of their Server Functions. However, TypeScript is a build-time tool; it does not enforce types at runtime.

Consider a function like this:

async function sayHello(name: string): string {
    'use server'
    return 'Hello, ' + name + '!'
}

While the developer expects a string, an attacker can use Flight to send a custom object. If the attacker places a malicious function on the toString property of that object, the server will implicitly call that function during string concatenation, leading to unexpected code execution.

The Path to RCE: Abusing "Thenables"

While type coercion is a potent tool, achieving full RCE requires a more complex chain. The breakthrough came from the discovery of how React handles "thenables"—objects that implement a .then() method, mimicking the behavior of a Promise.

In JavaScript, the await keyword is lenient; if it awaits a thenable, it will automatically invoke the .then() method. The researcher found that by sending a crafted Flight payload containing a thenable, they could force the server to execute an attacker-supplied function during the await decodeReply(...) call.

This capability allowed the researcher to chain multiple function calls, effectively creating a primitive for executing arbitrary functions within the server's memory.

The Final Exploit: Spoofing React Internals

The final leap to RCE involved targeting React's internal Chunk object. By using the $@x syntax, which creates a promise of a chunk, the researcher could coerce React into running Chunk.prototype.then against an attacker-controlled object rather than a legitimate React chunk.

This allowed the researcher to spoof the internal state of a Chunk, including the server manifest—the map that tells React which Server Function IDs correspond to which modules and functions.

Initially, the researcher attempted to override the server manifest to map an ID to Node.js's child_process.exec, but encountered obstacles with Webpack's module system. The final, more elegant PoC involved finding a location in the Chunk code where React calls a function with a controllable argument (specifically within the $Bx code for uploaded file blobs) and planting a malicious function there to be executed.

Lessons Learned and Industry Impact

React2Shell was a critical vulnerability because it blurred the lines between client and server code in a way that was fundamentally insecure. As one critic on Hacker News pointed out, creating a brand new protocol to serialize complex objects between trusted and untrusted actors is a high-risk design choice.

Key Takeaways for Developers:

  • Never trust runtime types: TypeScript annotations are not runtime validations. Always explicitly validate user input using libraries like Zod or Joi.
  • Avoid undocumented protocols: Proprietary, "security through obscurity" protocols often hide critical flaws that are easier for attackers to find than for defenders to fix.
  • Implement Bug Bounty Programs: The rapid triage and resolution of this vulnerability (confirmed in 17 hours) demonstrates the value of professional security researchers. As noted by the community, having a bounty program ensures that researchers call you before attackers do.

Despite the complexity of the exploit, the impact was devastating. The Meta and Vercel teams worked around the clock to coordinate with WAF providers like Cloudflare to implement defenses before the public advisory (CVE-2025-55182) was released, preventing a widespread catastrophe.

References

HN Stories