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
}
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
-
Reconnaissance — identify a Next.js app running React 19.0.0–19.2.0 with App Router. Any endpoint processing
multipart/form-datawith aNext-Actionheader is a valid target. No specific route or prior knowledge of the app structure needed. -
Payload construction — multipart body where
__proto__:thenpoisonsObject.prototype,_formData.getis redirected to$1:constructor:constructor, and_prefixcarries the JavaScript to execute. -
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. - Server-side evaluation — the Flight deserializer encounters an object with .then (inherited from the poisoned prototype). Calls new Function(_prefix). Executes attacker code.
-
Exfiltration —
execSync()output is interpolated into aNEXT_REDIRECTerror digest. Next.js converts it into a307withX-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.
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];
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')"
🔴 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)