← Back to PullLight
CVE-2025-55182 CVSS 10.0 Critical ⚠ Actively Exploited

React2Shell — Remote Code Execution via React Server Components Deserialization

React 19.0.0–19.2.0 allows arbitrary server-side code execution through prototype chain traversal in the RSC "Flight" protocol deserializer. No Server Actions required.

CVSS 10.0 / 10.0
CWE CWE-502 (Deserialization of Untrusted Data)
Discovered Nov 29, 2025
Disclosed Dec 3, 2025
KEV Added Dec 5, 2025
Discovery Lachlan Davidson (Meta bug bounty)
// what happened
  • Affected: React 19.0.0–19.2.0, Next.js 14.3.0-canary.77+ through 16.x (App Router), Parcel, Turbopack with RSC enabled
  • Attack vector: Attacker-supplied RSC "Flight" payload traverses prototype chain via constructor key to reach global Function constructor
  • Impact: Full server compromise — arbitrary code execution as Node.js process user, cloud credential theft, cryptomining, C2 backdoors
  • Fix: React 19.0.1/19.1.2/19.2.1, Next.js 15.0.5+/16.0.7+. Patch adds hasOwnProperty checks on all module exports and chunk initializations.
  • Active exploitation: Confirmed within hours of disclosure by Earth Lamia and Jackpot Panda (China-nexus APT), cryptomining observed post-patch

Base Score
10.0 Critical
Attack Vector (AV)
Network — exploitable remotely over HTTP
Attack Complexity (AC)
Low — no special conditions needed
Privileges Required (PR)
None — unauthenticated attacker
User Interaction (UI)
None — no victim action required
Scope (S)
Changed — affects resources beyond the vulnerable component
Confidentiality (C)
High — full server access, env vars, cloud credentials
Integrity (I)
High — arbitrary code execution, file writes, system modification
Availability (A)
High — full system compromise, denial of service possible
Vector String
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H

packages/react-server/src/ReactFlightReplyServer.js — requireModule() React 19.0.0–19.2.0 — No hasOwnProperty check on module export access function requireModule(moduleExports, id) { // VULNERABLE: direct property access, no ownership check // An attacker supplying "constructor" traverses to global Function return moduleExports[id]; // ← attacker uses "constructor" → global Function }
packages/react-server/src/ReactFlightReplyServer.js — reviveModel() React 19.0.0–19.2.0 — No prototype check before model assignment function reviveModel(parent, key, model) { if (model !== null && typeof model === 'object') { // VULNERABLE: no hasOwnProperty check before assignment // Attacker pollutes chunk prototype → thenable trigger on deserialization parent[key] = model; // triggers chunk.then(resolve, reject) if polluted } }
packages/react-server/src/ReactFlightReplyServer.js — initializeModelChunk() React 19.0.0–19.2.0 — Prototype-polluted chunks invoke arbitrary code function initializeModelChunk(chunk) { // If chunk has .then property (via prototype pollution), it's treated as a thenable // Attacker pollutes Chunk.prototype.then → arbitrary code runs on deserialization return chunk.value; // triggers chunk.then(resolve, reject) if it exists }
packages/react-server/src/ReactFlightReplyServer.js — requireModule() React 19.0.1 / 19.1.2 / 19.2.1 — hasOwnProperty guard added function requireModule(moduleExports, id) { // FIX: validate property ownership before access if (!Object.hasOwn(moduleExports, id)) { throw new Error('Invalid module export: ' + id); } return moduleExports[id]; }
packages/react-server/src/ReactFlightReplyServer.js — reviveModel() React 19.0.1 / 19.1.2 / 19.2.1 — prototype chain guard function reviveModel(parent, key, model) { if (model !== null && typeof model === 'object') { // FIX: reject objects with polluted prototypes if (Object.getPrototypeOf(model) !== null && Object.getPrototypeOf(model) !== Object.prototype) { throw new Error('Invalid model: prototype pollution detected'); } parent[key] = model; } }
packages/react-server/src/ReactFlightReplyServer.js — initializeModelChunk() React 19.0.1 / 19.1.2 / 19.2.1 — own property check on thenable trigger function initializeModelChunk(chunk) { // FIX: only treat .then as thenable if it's an own property, not inherited if (Object.hasOwn(chunk, 'then') && typeof chunk.then === 'function') { // This is a thenable — resolve it return chunk.value; } return chunk.value; }
// root cause
The RSC "Flight" protocol deserializes client-supplied payloads on the server without validating property ownership. In requireModule(), the code does moduleExports[metadata[2]] — direct bracket access — without checking if the key is an own property. This allows prototype chain traversal via keys like constructor, enabling access to the global Function constructor. The attack chains $@ (self-reference creating thenable/promise-like objects) with _prefix (holds attacker code string) — new Function("_prefix")() runs with full server privileges. The vulnerability exists in ANY app with RSC enabled; explicit Server Actions are NOT required.

How an attacker reaches arbitrary code execution
1
Attacker sends a crafted RSC "Flight" payload to a server with React 19.x + RSC enabled. The payload contains a module export key of "constructor".
2
requireModule(moduleExports, "constructor") is called. Since "constructor" is not an own property of moduleExports, JavaScript looks up the prototype chain and returns the global Function constructor.
3
The attacker also includes $@ (self-reference marker) and _prefix (malicious code string). The deserializer recognizes $@ as a thenable trigger.
4
new Function("_prefix")() is constructed and called — the code in _prefix runs with the Node.js process's full permissions on the server.
5
Attacker harvests cloud credentials from process.env (AWS/GCP/Azure keys), accesses IMDS at 169.254.169.254 for IAM role credentials, deploys cryptomining (XMRig), or installs C2 backdoors (Sliver framework).
Full Server Compromise
RCE with Node.js process privileges. Attacker can read/write any file, spawn processes, exfiltrate data.
Cloud Credential Harvesting
AWS/GCP/Azure keys from process.env. IAM role credentials via IMDS at 169.254.169.254.
Cryptomining
XMRig deployed on compromised servers within hours of disclosure. Observed post-patch in unpatched deployments.
C2 Backdoors
Sliver framework installed for persistent access. China-nexus APT (Earth Lamia, Jackpot Panda) confirmed within 12 hours.
500M+ Deployments at Risk
React's weekly npm downloads exceeded 25M. An estimated 500M+ RSC-enabled deployments when including Next.js and other frameworks.
39% Cloud Exposure
39% of cloud environments had at least one vulnerable RSC instance at time of disclosure.

app/page.jsx — Next.js App Router with RSC server component // Server component that processes RSC Flight payload import { createFromFlight } from 'react-server-dom-webpack/server'; export async function POST(request) { const flightPayload = await request.text(); // Payload deserialized without ownership validation // This is what the attacker exploits const data = createFromFlight(flightPayload); // Malicious payload uses "constructor" → global Function // and _prefix → attacker-controlled code string // Result: new Function("_prefix")() runs on the server return Response.json({ ok: true }); }
PullLight synthetic fix — add explicit server action validation // PullLight would flag: payload deserialization without input validation // Fix: validate that the payload comes from an authenticated action context export async function POST(request) { const flightPayload = await request.text(); // Verify the payload is from a legitimate server action context // before deserializing const sessionToken = request.headers.get('x-action-session'); if (!sessionToken || !validateSession(sessionToken)) { return Response.json({ error: 'Unauthorized' }, { status: 401 }); } // After React patch, this is also protected server-side const data = createFromFlight(flightPayload); return Response.json({ ok: true }); }
🔴 PullLight — Critical Finding
[CRITICAL] RCE via Deserialization — packages/react-server/src/ReactFlightReplyServer.js

Unvalidated property access on untrusted input. moduleExports[id] in requireModule() is called without a hasOwnProperty check, allowing prototype chain traversal. An attacker supplying "constructor" reaches the global Function constructor, enabling arbitrary code execution (RCE). The attack chains $@ (thenable self-reference) and _prefix (code string) — new Function("_prefix")() runs on the server with full Node.js process privileges.

This is a CWE-502 (Deserialization of Untrusted Data) vulnerability with a CVSS 10.0 rating. It affects ANY app with React Server Components enabled — explicit Server Actions are NOT required.
→ Fix: add if (!Object.hasOwn(moduleExports, id)) throw new Error('Invalid export') to requireModule(). Also guard reviveModel() against prototype pollution and initializeModelChunk() against inherited .then properties. Patch: React 19.0.1/19.1.2/19.2.1, Next.js 15.0.5+/16.0.7+.

Five reasons this vulnerability hides from line-by-line review

  • Deserialization looks like framework internals. The RSC Flight protocol code is deep in React's internals — reviewers trust the framework's own deserialization logic implicitly.
  • moduleExports[id] is idiomatic. Direct bracket access on an object is normal JavaScript. It looks correct in isolation — the vulnerability only emerges when you know the input is attacker-controlled.
  • The attack chain spans multiple files. requireModulereviveModelinitializeModelChunk, with protocol-specific mechanics ($@ self-ref, Flight chunk format) — too complex for a linear diff review.
  • No obvious "user input" in the code. The input is a structured Flight payload arriving via HTTP headers and body — invisible at the code level. The deserializer looks like it reads internal data structures.
  • Prototype pollution on the server is counterintuitive. Most developers associate prototype pollution with frontend XSS — server-side deserialization chain traversal to Function constructor is a niche, severe attack class most reviewers don't model.

What makes this a PullLight-specialized catch

  • Cross-file context: The vulnerability spans requireModule(), reviveModel(), and initializeModelChunk(). PullLight models the full deserialization pipeline, not just the changed function.
  • Semantic type: CWE-502. "Deserialization of Untrusted Data" is a recognized vulnerability class that requires semantic understanding of what data flows where and how it's processed — not just syntax checking.
  • Prototype chain traversal: Recognizing that moduleExports["constructor"] resolves to the global Function requires understanding JavaScript's prototype chain in a server-side context — a subtle but critical distinction.
  • Framework internals trust: PullLight treats all code equally — it doesn't give framework code a pass. Most human reviewers (and AI reviewers) trust React's code more than their own application's code.

CVSS 10.0 is not a typo. This is the maximum possible severity in the CVSS scoring system — reserved for vulnerabilities that are trivially exploitable and cause total system compromise. React2Shell meets every criteria: network-accessible, no authentication required, arbitrary code execution, scope change confirmed.

The window between disclosure and active exploitation was measured in hours. The China-nexus APT groups Earth Lamia and Jackpot Panda had working exploits within 12 hours of public disclosure. Cryptomining payloads were observed on unpatched servers within 24 hours. This is what a real-world zero-day looks like — and it started with a React server component.

React Server Components are now the default architecture for Next.js App Router applications. That's millions of production deployments that suddenly needed emergency patching. PullLight's analysis catches this class of vulnerability — prototype chain traversal in deserialization — before it reaches production, because it models the data flow, not just the code syntax.

The fix is simple (hasOwnProperty checks), but the discovery required understanding the full RSC Flight protocol, the JavaScript prototype chain, and the specific attack chain (constructor + $@ + _prefixnew Function()). That's the kind of bug that hides in framework internals and only becomes visible when you model the entire system — not just the changed lines.


Nov 29, 2025 Lachlan Davidson discovers CVE-2025-55182, reports via Meta bug bounty program
Dec 3, 2025 Public disclosure. React 19.0.1 / 19.1.2 / 19.2.1 and Next.js 15.0.5+ / 16.0.7+ released
Dec 3–4, 2025 Proof-of-concept exploits published on social media and GitHub
Dec 4, 2025 Earth Lamia (China-nexus APT) observed exploiting in-the-wild
Dec 5, 2025 CISA KEV catalog added CVE-2025-55182. Jackpot Panda also confirmed exploiting.
Dec 5–10, 2025 Cryptomining payloads (XMRig) observed on unpatched servers post-disclosure
Dec 6–12, 2025 Sliver C2 framework deployed on compromised servers by China-nexus actors
Jun 2026 PullLight documents React2Shell as 7th CVE case study — helping engineers identify prototype-pollution deserialization patterns

Don't let this happen to your PRs

More CVE Case Studies