DEV Community

Cover image for CVE-2025-55182 · React2Shell: RCE in React Server Components via Prototype Pollution
Annais Molina Fuentes
Annais Molina Fuentes

Posted on • Originally published at blog.deviannt.com

CVE-2025-55182 · React2Shell: RCE in React Server Components via Prototype Pollution

This is a summary. The full analysis — root cause walkthrough, complete payload, exploitation framework, forensic artifacts, and patch diffing — lives at blog.deviannt.com.

TL;DR: React's Flight deserializer evaluates any object with a .then method as a Promise, regardless of its actual type. An attacker poisons Object.prototype.then through a crafted multipart POST, forcing the server to execute arbitrary JavaScript via the Function constructor. The result is exfiltrated through the X-Action-Redirect HTTP header. No authentication required. Deterministic. CVSS v3.1: 10.0 (Critical).

The attack surface

React Server Components (RSC) stabilized in React 19 alongside Server Actions — a model where UI components execute directly on the server and communicate with the client through a custom serialization layer called the Flight protocol. When a client invokes a Server Action, it sends a multipart POST with a serialized payload. The server deserializes it, executes the action, and streams the result back.

The Flight protocol is not JSON. It is a streaming format with typed chunks. Its core mechanism: if a deserialized object has a .then function, the runtime resolves it as a Promise.

That assumption is the root of this vulnerability.

⚠️ Any Next.js application using the App Router with React Server Components is affected — the default since Next.js 14. Explicitly defined Server Actions are not required. The presence of the affected RSC packages is sufficient

Root cause

// VULNERABLE — React 19.0.0 / 19.1.0 / 19.1.1 / 19.2.0
if (obj && typeof obj.then === 'function') {
  // behavioral check — bypassable via prototype chain
}
Enter fullscreen mode Exit fullscreen mode

If an attacker writes a function to Object.prototype.then, every plain object in the runtime inherits it. The deserializer can no longer distinguish a real Promise from a poisoned plain object — and calls new Function(_prefix) on attacker-controlled content.

The exploit chain

  1. Reconnaissance — identify a Next.js app running React 19.0.0–19.2.0 with App Router. Any endpoint processing multipart/form-data with a Next-Action header is a valid target. No specific route or prior knowledge of the app structure needed.
  2. Payload construction — multipart body where __proto__:then poisons Object.prototype, _formData.get is redirected to $1:constructor:constructor, and _prefix carries the JavaScript to execute.
  3. Request delivery — single POST to root with Next-Action: x. WAFs see a well-formed multipart request and forward without inspection. No standard injection signatures triggered.
  4. Server-side evaluation — the Flight deserializer encounters an object with .then (inherited from the poisoned prototype). Calls new Function(_prefix). Executes attacker code.
  5. ExfiltrationexecSync() output is interpolated into a NEXT_REDIRECT error digest. Next.js converts it into a 307 with X-Action-Redirect: /login?a=<output>. Decode the parameter.

No shell injection. No file upload. No authentication. One POST request.

Basic RCE: whoami, id and uname -a executed on the vulnerable Node.js server. Basic RCE: whoami, id and uname -a executed on the vulnerable Node.js server.

The complete payload structure, the minimal curl one-liner, and the full exploitation framework react2shell.py — with modules for persistent interactive shell, environment variable exfiltration, defacement, and selective denial of service — are documented at blog.deviannt.com.

The patch

Released simultaneously across three React 19 branches: 19.0.1, 19.1.2, 19.2.1 (December 3, 2025).

// VULNERABLE
- resolvedValue = resolvedValue[key];

// PATCHED
+ if (!resolvedValue.hasOwnProperty(key)) break;
+ resolvedValue = resolvedValue[key];
Enter fullscreen mode Exit fullscreen mode

hasOwnProperty guards prevent prototype chain traversal. The attacker can no longer reach the Function constructor through $1:constructor:constructor. Chain broken at the first link.

Verify your installation:

node -e "const r = require('react'); const [maj,min,pat] = r.version.split('.').map(Number); \
  console.log('React:', r.version, (maj===19 && (min<2||(min===2&&pat<1))) ? '❌ VULNERABLE' : '✓ Patched')"
Enter fullscreen mode Exit fullscreen mode

🔴 Post-patch advisory: The initial patch versions (19.0.1, 19.1.2, 19.2.1) also contain two follow-on CVEs: CVE-2025-55184 (DoS, CVSS 7.5) and CVE-2025-55183 (Source Code Exposure, CVSS 5.3). Update to 19.0.2, 19.1.3, or 19.2.2.

One structural lesson

Behavioral trust is weaker than identity trust. The typeof obj.then === 'function' check was designed to be flexible and work with any thenable. That flexibility is exactly what made it exploitable. When a security boundary depends on an object's behavior rather than its identity, prototype pollution becomes a master key.


Full analysis → blog.deviannt.com · CVE-2025-55182 · React2Shell
— devianntsec // security research & beyond

Top comments (0)