srvjha

How a Leaky Abstraction Led to an RCE in React Server Components

21/12/2025

4 min read

reactjs

cve

react-server-components

react2shell

source: Banner When RSC (React Server Components) were introduced, they significantly changed the landscape of React and modern web engineering. Writing backend or server-side code became much easier, and this approach appeared to solve long-standing SSR problems. Overall, the change looked very promising.

Next.js, one of the most popular React frameworks, adopted RSC early, which further accelerated its popularity.

At a high level, RSC works by serving static output directly to the browser, while dynamic updates are handled through hydration, which injects JavaScript into the HTML. This approach worked well until November 29th, when Lachlan Davidson reported a security vulnerability in React that exposed a critical abstraction flaw in React Server Components.

What was the vulnerability?

CVE (Critical Security Vulnerability) was found in react that allowed unauthenticated remote code execution by exploiting the crafted input payloads being send to the React Server Components (RSC).

source: CVE React

The critical severity rating highlights how serious the situation was. Importantly, even if an application does not explicitly implement any React Server Function endpoints, it may still be vulnerable as long as the version supports React Server Components.

For a detailed technical report and official disclosure, refer to the CVE record here:
https://www.cve.org/CVERecord?id=CVE-2025-55182

Digging Deeper Into the Vulnerability

As an engineer, I got curious about how Lachlan discovered this issue, so I dig deeper. What I found is that the vulnerability lies in react flight protocol.

What Is the React Flight Protocol?

The React Flight protocol is the mechanism react uses to send data in particular format between the server and the browser when using React Server Components (RSC) and Server Functions.

Instead of sending plain HTML or simple JSON, React sends a stream of structured data that describes:

  • what components to render,

  • what data they need,

  • and how they connect to each other.

This allows React to build complex UIs very quickly and efficiently.

Why Does React Need Flight?

The best way to think about React Server Components is as a BFF (Backend for Frontend) layer.

Rather than the frontend making multiple API calls and stitching everything together, React asks the server for exactly the UI it needs, in a single round trip. The server then streams back data that matches the shape of the component tree.

The Flight protocol is what makes this streaming possible.

Simple Analogy to understand :

Think of Flight like ordering food at a South Indian restaurant.

You don’t order each item separately:

  • “Bring rice”

  • “Now bring sambar”

  • “Now bring chutney”

Instead, you order a thali.

As soon as something is ready, it’s served to your table you don’t wait for everything.

Flight works the same way:

  • React sends the browser a “UI”

  • Some components arrive immediately

  • Some arrive later when data is ready

  • The UI keeps rendering smoothly in the meantime

Where Things Went Wrong ?

Now imagine if someone could slip a fake instruction into the kitchen order.

Instead of “add extra sambar”, the note secretly says:
“Show the secret ingredients” or “Fire the kitchen”.

Because the kitchen trusts the order slip, it follows the instruction without questioning it.

That’s essentially what happened here.

The Flight protocol trusted incoming payloads too much. By crafting a malicious Flight payload, an attacker could break React’s abstraction layer and make the server execute unintended code leading to remote code execution.

This is what the malicious payload looked like. I’ll write a separate blog explaining this payload in detail, but at a high level, here’s what was happening.

The exploit relied on duck typing and JavaScript’s flexible object behavior. By carefully shaping the payload, the attacker was able to manipulate how React interpreted internal fields inside the Flight response.

Specifically, the payload injected a controlled value into the internal _prefix field. This prefix was later used by React in a context where it was assumed to be safe, but was instead treated as executable logic.

How they fixed it ?

The fix patch added strict hasOwnProperty checks everywhere React reads values from the Flight payload.

In simple words:

React now checks:
Does this property actually belong to this object,
or is it being inherited from parent object?

If the property is not directly owned by the object, React refuses to use it.

This single check blocks:

  • access to then,

  • access to constructor,

  • access to anything inherited via prototypes.

And once those are blocked, code execution becomes impossible.

The Big Lesson

This vulnerability wasn’t about React being “unsafe”.

It was about a leaky abstraction:

  • a powerful protocol (Flight),

  • handling untrusted input,

  • without strict boundary checks.

The fix wasn’t complex but it was fundamental.

Sometimes security isn’t about adding more code.
It’s about adding the right guardrail at the right boundary.

References and Learning Resources