Atrás

React2Shell Explained (CVE-2025-55182): From Vulnerability Discovery to Exploitation

Vulnerability Assessment and Penetration Testing (VAPT)

React Server Components, React Flight protocol, deserialization, remote code execution, exploit chain, VAPT

React2Shell Explained (CVE-2025-55182): From Vulnerability Discovery to Exploitation
React2Shell Explained (CVE-2025-55182): From Vulnerability Discovery to Exploitation

Executive Summary

This technical analysis examines a critical remote code execution (RCE) vulnerability scenario in the React Server Components (RSC) architecture that could theoretically enable unauthenticated remote code execution. The hypothetical exploit chain abuses weaknesses in the React Flight protocol's deserialization process, where insufficient validation of client-controlled payloads could allow attackers to manipulate server-side execution. With React and Next.js powering a significant portion of modern web infrastructure, such a vulnerability would affect enterprise applications, e-commerce platforms, and government services globally. Successful exploitation would theoretically require only a single crafted HTTP request and bypass traditional security defenses.

React Server Components Communication Flow

This diagram illustrates how React Server Components (RSC) communicate with the client using the React Flight protocol. The client and server exchange serialized component data in incremental chunks, which are deserialized and reconstructed on the server. React2Shell abuses weaknesses in this serialization and deserialization process to achieve remote code execution.

Figure: React Server Components Communication Flow(This diagram would illustrate the bidirectional flow: React Client ↔ Serialized Data Chunks ↔ React Flight Protocol ↔ React Server Components)

1-React Server Components (RSC) Architecture

React Server Components represent a paradigm shift in React architecture by executing components exclusively on the server rather than in the browser. Unlike traditional React components that bundle JavaScript to the client, RSCs operate within server runtimes (Node.js or Edge environments) and possess direct access to privileged resources:

  • Database connections and internal APIs
  • File system operations
  • Environment variables and configuration secrets
  • Internal service communications

Instead of transmitting HTML or JavaScript bundles to clients, RSCs generate a serialized data model that describes the UI structure. This model contains component references, props, state information, and module identifiers—essentially instructions for the client-side React runtime rather than executable code.

2 React Flight Protocol: The Communication Layer

React Flight serves as the transport mechanism between client and server in the RSC architecture. This binary-like protocol handles:

  • Streaming component data in incremental chunks
  • Synchronization of UI updates
  • Transmission of serialized component trees

The protocol's design assumes that Flight payloads originate from trusted React clients. However, RSC endpoints are often directly exposed via HTTP endpoints (commonly /_rsc or similar paths in Next.js applications), creating an attack surface where malicious actors can craft and send arbitrary Flight payloads.

Security Implication: The critical trust boundary exists where Flight payloads are deserialized on the server. React2Shell exploits precisely this boundary by injecting malicious serialized data that the server incorrectly trusts during reconstruction.

3. Serialization and Deserialization

Serialization is the process of converting in-memory server-side objects (such as components, props, and references) into a format suitable for network transmission.

Deserialization is the reverse process:

  • Incoming Flight payloads are parsed
  • Objects are reconstructed in memory
  • Execution continues based on the reconstructed structures

In the context of React Server Components, deserialization is complex because it may involve:

  • Rebuilding object graphs
  • Resolving module references
  • Linking internal execution logic

Security implication (React2Shell):
React2Shell abuses unsafe deserialization, where attacker-controlled data is trusted and reconstructed without strict validation. This allows malicious payloads to influence execution flow and ultimately achieve remote code execution.

4. Data Chunks

React Flight does not transmit all data in a single response. Instead, serialized data is divided into small chunks that are streamed incrementally between the client and the server.

Each chunk:

  • Contains part of the serialized component tree
  • Is parsed and processed independently
  • Contributes to reconstructing the final UI state

This chunk-based approach improves:

  • Performance
  • Progressive rendering
  • User experience

What is React2Shell?

React2Shell is the common name for CVE-2025-55182, a critical remote code execution (RCE) vulnerability in React Server Components. It's a zero-day exploit that allows unauthenticated attackers to execute arbitrary code on vulnerable servers through a single crafted HTTP request, giving them full control over the server environment.

At its core, React2Shell abuses a flaw in how the React Flight Protocol deserializes data, allowing attackers to manipulate prototype chains and inject malicious code that gets executed during normal server-side rendering operations.

Affected Software

The table above outlines the components and software versions impacted by CVE-2025-55182 (React2Shell). The vulnerability primarily affects environments that use React Server Components (RSC) and rely on the React Flight protocol for server-to-client communication.

At the core of the issue are React versions 19.0.0 through 19.2.0, where unsafe deserialization logic in the Flight protocol allows attacker-controlled payloads to influence server-side execution. This directly exposes any application running RSC on these versions to unauthenticated remote code execution.

Next.js applications using the App Router are particularly exposed. Versions 16.0.0 through 16.0.6, as well as 15.x and early 16.x releases, include vulnerable RSC integrations that expose React Flight endpoints by default. Canary builds (14.x) are also affected due to experimental RSC features, although patches have been applied in later canary releases.

In addition to framework-level exposure, the vulnerability extends to React Server DOM serialization libraries, including:

  • react-server-dom-webpack
  • react-server-dom-parcel
  • react-server-dom-turbopack

These libraries implement the underlying serialization and deserialization logic used by React Flight. All versions released prior to vendor patches should be considered vulnerable, regardless of framework version, until explicitly updated.

Component Product / Library Vulnerable Versions Fixed Versions
React Server Components React 19.0.0 – 19.2.0 19.2.1+
App Router Next.js 16.0.0 – 16.0.6 16.0.7+
Canary Builds Next.js 14.x Canary Patched in later canary releases
Stable Releases Next.js 15.x, early 16.x 16.0.7+
Serialization Layer react-server-dom-webpack All versions prior to patched release Vendor patch required
Serialization Layer react-server-dom-parcel All versions prior to patched release Vendor patch required
Serialization Layer react-server-dom-turbopack All versions prior to patched release Vendor patch required


Before diving into the React2Shell exploit, we must first understand a fundamental JavaScript concept: how functions are defined and executed. This knowledge is crucial because React2Shell's attack chain exploits the specific behaviors of different function definition methods, particularly the dangerous Function() constructor.

JavaScript Function Definitions

JavaScript provides multiple ways to define functions, each with different characteristics. The core distinction is between declarations (parsed at compile time) and expressions/constructors (evaluated at runtime). The Function constructor is particularly dangerous as it evaluates strings as executable code.

1. Function Declaration

Standard function declaration with name and code block. Safest method, compiled during parsing phase.

function func1() { console.log("Resecurity");}
2. Function Constructor

Uses global Function constructor with string argument. Dynamically evaluates code at runtime

var func2=Function("console.log(`Resecurity`)")
3. Prototype Constructor Access

Accesses Function constructor through alert function's prototype chain. Shows how any object can reach code execution.

var func3=alert.__proto__.constructor("console.log('Resecurity')")
4. Double Constructor Chain

Double .constructor access still reaches same Function constructor. Demonstrates prototype chain consistency.

var func4=alert.__proto__.constructor.constructor("console.log('Resecurity')")
5. Method Constructor

Reaches Function constructor through method's prototype. Any function property leads to same execution endpoint.

5var func5=alert.__proto__.toString.constructor("console.log('Resecurity')")

React2Shell PoC –Technical Walkthrough

1: Normal Flight Chunk Resolution

Understanding how React Flight normally resolves references between chunks is essential to see how attackers subvert this process.

Example payload
files = {
  "0": (None, '["$1"]'),
  "1": (None, '{"object":" company ","name":"$2: companyName "}'),
  "2": (None, '{"companyName":"Resecurity"}'),
}
  • $<id> instructs React to resolve another chunk by ID.
  • $<id>:key retrieves a specific property from the referenced chunk.
  • Resolution occurs during model revival, where React reconstructs the component tree from serialized data
steps

1. Server receives three chunks (0, 1, 2)
2. Chunk 0 references chunk 1: ["$1"]
3. Chunk 1 contains: {object:"company", name:"$2:companyName"}
4. $2:companyName means: get chunk 2, then access its "companyName" property
5. Chunk 2 has: {"companyName":"Resecurity"}
6. Final deserialized result: {object:"company", name:"Resecurity"}

This shows the intended use - chunks can reference other chunks' properties using $id:propertyPath syntax. The system trusts that all references point to legitimate data properties, not JavaScript internals.

{ object: " company ", name: "Resecurity" }


2: Prototype Traversal During Deserialization

The core vulnerability - React didn't validate that referenced properties actually exist on the target object before accessing them.

Payload
files = {
  "0": (None, '["$1:__proto__:constructor:constructor"]'),
  "1": (None, '{"x":1}'),
}


Steps
  1. 1: $1 resolves to chunk 1 → {x: 1}
  2. 2: :__proto__ accesses JavaScript's built-in prototype property

→ Returns Object.prototype (the parent of all objects)

  1. Step 3: :constructor accesses the constructor property of Object.prototype

→ Returns the Object() constructor function

  1. Step 4: :constructor again accesses constructor of Object() function

→ Returns the global Function() constructor

  1. Result: Attacker obtains Function constructor [Function: Function]

Why This Works: JavaScript's prototype chain is always accessible. When you access obj.property, JavaScript:

  1. Checks if obj has its own property
  2. If not, checks obj.__proto__ (the prototype)
  3. Continues up the chain until found or returns undefined

3: Thenables – Turning Data into Execution

A "thenable" is any JavaScript object that has a .then method—it doesn't need to be an actual Promise. When JavaScript encounters await obj, if obj.then exists, the language automatically invokes .then() with two internal callback functions (resolve and reject). This built-in behavior allows attackers to trigger function execution without any explicit function call appearing in the vulnerable code.

Example payload demonstrating the primitive

files = {
  "0": (None, '{"then":"$1:__proto__:constructor:constructor"}'),
  "1": (None, '{"x":1}'),
}


Leading to this error:

steps

1: Deserialization produces: {then: Function}
2: Application code does: await decodedReply
3: JavaScript runtime sees .then property
4: Automatically invokes: Function(resolve, reject)
5: Function() tries to execute arguments as code

Result: SyntaxError (resolve/reject functions aren't valid code strings)

4: $@chunkId – Breaking the Object Boundary

Using React Flight's special syntax to reference raw chunk objects instead of resolved values. $@<id> → return raw chunk object, not resolved value

Payload
files = {
  "0": (None, '{"then": "$1:__proto__:then"}'),
  "1": (None, '"$@0"'),
}


$@ Syntax Explained

React Flight provides a special reference syntax that allows a chunk to be accessed without being resolved.

Normal Reference Resolution

$1

  • Resolves chunk 1
  • Returns the final deserialized value
  • All references inside the chunk are already processed

Example: $1 → { x: 1 }

This is the safe, intended behavior for component reconstruction.

Special Raw Reference Syntax

$@1

  • Returns the raw chunk object itself
  • Bypasses normal resolution
  • Internal metadata and methods remain intact
Why $@ Exists

React uses $@ internally to:

  • Support streaming and incremental rendering
  • Allow unresolved chunks to be passed around
  • Treat chunks as thenables for async coordination

In legitimate usage, this is never exposed to untrusted input.

Why $@ Is Dangerous in React2Shell

When an attacker controls Flight payloads:

  • $@ breaks the abstraction boundary between data and runtime internals
  • Attackers gain access to:
    • Chunk prototypes
    • .then() implementation
    • Internal state fields such as status and _response

5: Forcing initializeModelChunk()

React has special internal logic for processing "resolved model" chunks through the initializeModelChunk() function. Attackers discovered they could force React to execute this critical function by setting a specific status field in their crafted chunk, unlocking a second deserialization pass with different execution context.

files = {
  "0": (None, '{"then":"$1:__proto__:then","status":"resolved_model"}'),
  "1": (None, '"$@0"'),
}


Internal logic

React sees:

chunk.status === "resolved_model"

if (chunk.status === "resolved_model") {
  initializeModelChunk(chunk);  // <-- Triggered!
  // Inside initializeModelChunk:
  var parsed = JSON.parse(chunk.value);  // Parse chunk.value
  reviveModel(chunk._response, parsed);  // Second deserialization!
}


steps

1: Chunk has status: "resolved_model"
2: React calls initializeModelChunk(chunk)
3: initializeModelChunk parses chunk.value as JSON
4: Calls reviveModel() with chunk._response as context

Result: We get a SECOND deserialization pass with different context

The status field is client-controlled with no validation. React assumes only its own code would set status: "resolved_model".

6: Context Confusion via _response

The _response object serves as the execution context for React Flight's deserialization process. By providing a malicious _response object, attackers can completely control the environment in which chunk references are resolved, enabling them to redirect normal data operations into code execution.

Key Components Explained:
  1. reason: -1: Bypasses an error check in initializeModelChunk:
var rootReference = chunk.reason.toString(16); // -1.toString() errors


  1. value: '{"then": "$B0"}': JSON that will be parsed during second pass. $B0 references a "blob" (special Flight type).
  2. _response: The execution context for the second pass. Fully attacker-controlled.
  3. _response._formData.get: Points to Function() constructor via prototype chain.
  4. _response._prefix: Will become the argument passed to Function().

This is a "Confused Deputy" Attack: The reviveModel() function (the deputy) uses attacker-controlled _response object but executes with server privileges.

7: Blob Resolution → Function Execution

React Flight has special handling for binary data through "blob references" (prefixed with $B). Attackers discovered that by hijacking the blob resolution mechanism and controlling the _response context, they could transform a benign data-fetching operation into a direct Function() constructor call with attacker-controlled code as its argument.

// Simplified blob handler logic (vulnerable version)
case "B":  // Handle blob references like "$B0"
  // BUG: response._formData.get is attacker-controlled
  return response._formData.get(response._prefix + blobId);


steps

1: Second pass processes value: {"then": "$B0"}
2: $B0 triggers blob handler
3: response._formData.get → resolves to Function() constructor
4: response._prefix + "0""RCE_CODE_HERE" + "0"
5: Function("RCE_CODE_HERE0") called
6: Creates function with attacker code
7: Function returned as .then property
8: await automatically calls .then()
9: Attacker code executes

8: Real RCE Payload Example

This final step combines prototype pollution, thenable behavior, chunk manipulation, and blob resolution into a single, weaponized payload that executes arbitrary Node.js code on the server. The crafted payload abuses every stage of React Flight's deserialization process to achieve full remote code execution (RCE).

Each Component's Role

1. "then": "$1:__proto__:then"

Hijacks the .then property to point to React's internal Chunk.prototype.then method instead of the dangerous global Function() constructor.

2. "status": "resolved_model"

Forces React to treat our malicious chunk as a fully resolved model chunk, triggering the special initializeModelChunk() function.

as a simple data object. By setting this status, we essentially tell React: "This chunk is ready for advanced processing," unlocking the ability to parse chunk.value as JSON and process nested references a second time.

3. "reason": -1

A subtle but essential bypass that prevents an early error in initializeModelChunk().

4. "value": '{"then": "$B0"}'

Contains a nested JSON structure with a blob reference ($B0) that will be processed during the second deserialization pass.

5. "_response"

Purpose: Provides a completely attacker-controlled execution context for the second deserialization pass.

6. "_formData.get"

Points to the global Function() constructor, turning a data-fetching operation into a code execution gadget.

7. "_prefix"

Contains the actual remote code execution payload that gets passed to the Function() constructor.

8. "$@0"

Creates a self-referential loop that allows chunk 0 to reference itself during the resolution process.

Each component exploits a different trust assumption in React's architecture, and together they create a weaponized deserialization chain that turns innocent-looking data into remote code execution. The brilliance of this exploit is how it repurposes React's own legitimate features—chunk processing, thenable handling, blob resolution—against itself, without any buffer overflows, memory corruption, or traditional exploit techniques.

React2Shell Lab POC: Command Execution, Demo

React2Shell Live Attack: Production Server Compromise

Result: Executes id command on server

What Makes CVE-2025-55182 Business-Critical?

1. Massive Global Exposure

React and Next.js underpin a substantial portion of modern web infrastructure, including enterprise applications, e-commerce platforms, SaaS products, and government services.

Because React2Shell targets React Server Components (RSC), a successful exploit often results in full compromise of the application’s backend runtime, not just a single feature or endpoint.

For many organizations, an RSC compromise is equivalent to total application ownership.

2. Low-Friction, High-Impact Exploitation

React2Shell requires:

  • No memory corruption
  • No authentication
  • No elevated privileges
  • A single crafted HTTP request exploiting a logic flaw in React Flight deserialization is sufficient to achieve remote code execution.

Public proof-of-concepts are already available, significantly lowering the barrier to exploitation and increasing the likelihood of opportunistic and automated attacks.

3. Traditional Defenses Are Ineffective

The exploit executes during deserialization, before application-level validation, routing logic, or authorization checks occur.

As a result:

  • Many WAFs do not detect the attack by default
  • Signature-based rules are easily bypassed through payload obfuscation
  • Application logging may miss the initial execution event entirely

Without explicit awareness of React Flight internals,most defensive layers provide little to no protection.

4. Impact Across Every Layer of the Stack

A successful React2Shell exploit enables attackers to:

  • Execute arbitrary code on the server
  • Access environment variables and cloud credentials
  • Read and modify databases
  • Deploy ransomware or persistent webshells
  • Establish a foothold for lateral movement within the environment

How to Protect Your Environment Right Now

1. Apply Official Patches Immediately

The React and Next.js teams have released fixes. Ensure all environments are upgraded to:

  • React: 19.2.1 or later
  • Next.js: 16.0.7 or later

If your environment remains unpatched, assume compromise is possible until proven otherwise.

2. Regenerate Secrets and Credentials

Because exploitation allows access to server memory and environment variables, credential exposure must be assumed.

Immediately rotate:

  • API keys
  • Environment variables
  • Database credentials
  • Cloud access tokens (AWS, GCP, Azure, etc.)

Invalidate old credentials wherever possible.

3. Implement WAF and API Gateway Protections

Deploy temporary detection and blocking rules focused on:

  • Suspicious or malformed React Flight chunk structures
  • Requests containing __proto__ or prototype-related references
  • Abnormal usage of next-action, rsc-action-id, or related headers

These controls will not stop all variants, but they can disrupt mass exploitation and scanning activity.

4. Harden RSC and Next.js Deployments

Reduce blast radius by enforcing runtime hardening:

  • Run RSC servers with minimal OS and cloud privileges
  • Enforce strong isolation between application, database, and cloud control planes
  • Use read-only file systems where feasible
5. Actively Hunt for Indicators of Compromise

Monitor logs and telemetry for:

  • Unexpected or anomalous .then() behavior
  • Suspicious POST requests targeting RSC endpoints
  • Shell command execution from Node.js processes
  • Unusual outbound network traffic from application servers

Conclusion

React2Shell demonstrates how modern frameworks inherit classic vulnerabilities despite advanced architectures. This hypothetical RCE flaw stems from implicit trust in client data and missing prototype validation, allowing attackers to manipulate execution through deserialization. The exploit chain reveals that sophisticated features expand attack surfaces while traditional defenses fail against protocol-specific attacks. Organizations must adopt zero-trust deserialization, prioritize dependency vigilance, and implement protocol-aware security controls. Ultimately, security must be foundational—not incidental—in our complex web ecosystem, with deserialization treated as a critical threat vector requiring continuous scrutiny and defense-in-depth protection.

Boletín informativo

Mantente al día con las últimas noticias y desarrollos en ciberseguridad.

Al suscribirme, entiendo y acepto que mis datos personales serán recopilados y procesados de acuerdo con la Privacidad y las Política de Cookies

Arquitectura en la nube
Arquitectura en la nube
445 S. Figueroa Street
Los Angeles, CA 90071
Google Maps
Contáctenos completando el formulario
Prueba los productos de Resecurity hoy con prueba gratuita
Resecurity
Cerrar
¡Hola! Estoy aquí para responder tus preguntas y ayudarte.
Antes de empezar, ¿podrías indicarnos tu nombre y correo electrónico?