$ cat cve-2025-55182-react2shell.mdx
CVE-2025-55182: React2Shell — RCE in React Server Components
Authorization: this is an educational proof of concept for security research. Only run it against an app you own or are explicitly authorized to test. Unauthorized use against systems you don't control can be a crime.
Summary
React2Shell (CVE-2025-55182) is a CVSS 10.0 pre-authentication remote
code execution bug in React Server Components (RSC), disclosed on
2025-12-03. The flaw is not in react or react-dom — it lives in the three
RSC bundler packages that decode payloads sent to Server Function endpoints:
react-server-dom-webpack, react-server-dom-parcel, and
react-server-dom-turbopack.
NVD classifies it as CWE-502 — Deserialization of Untrusted Data. In React's own words, "the vulnerable code unsafely deserializes payloads from HTTP requests to Server Function endpoints." No authentication, no user interaction: an ordinary POST to an endpoint the framework mounts for you is enough.
This repository ships a minimal vulnerable app plus a Python exploit that crafts the payload, sends it, and executes commands on the host running the Node.js process.
How the vulnerability works
RSC accepts server-action arguments and form submissions as multipart
FormData and reconstructs the original JavaScript values on the server. That
reconstruction happens in ReactFlightReplyServer.js, and it is the trust
boundary — it turns bytes the attacker fully controls into live JavaScript
objects.
What the maintainers confirmed
The fix (facebook/react#35277) was authored by Sebastian Markbåge, who names the root cause directly:
"These
hasOwnPropertychecks were the ones that were missing. This is the critical fix. Without it, you can drill into objects not created by the parser itself."
That is the whole primitive. Without an own-property guard, the deserializer resolves keys along the prototype chain instead of staying inside the plain object it just built — so the attacker gets to reference properties the parser never created, and reach code the parser was never supposed to call. The rest of the PR, in the author's words, addresses "other gadgets and to slow down reverse engineering."
React deliberately withheld the exploitation details at disclosure time ("further details of the vulnerability will be provided after the rollout of the fix is complete"), which is why the advisory describes the class of bug and not the chain.
What this PoC does
The gadget chain below is this lab's observed path, not vendor-published detail — treat it as one working route through the primitive above, not as the canonical exploit:
- a
thenproperty, so the object is duck-typed as a thenable and its resolution path is hijacked; - a
statusfield that pushes the deserializer into an "already resolved" state; - a nested
_responseobject carrying the command to run; FormDataconstructor-reference manipulation to bridge from the parsed form into the gadget that invokes command execution.
Once the poisoned state is consumed, the command runs with the privileges of the Node.js process.
Affected versions
| Package | Vulnerable | Fixed |
|---|---|---|
react-server-dom-webpack | 19.0.0, 19.1.0, 19.1.1, 19.2.0 | 19.0.1, 19.1.2, 19.2.1 |
react-server-dom-parcel | 19.0.0, 19.1.0, 19.1.1, 19.2.0 | 19.0.1, 19.1.2, 19.2.1 |
react-server-dom-turbopack | 19.0.0, 19.1.0, 19.1.1, 19.2.0 | 19.0.1, 19.1.2, 19.2.1 |
NVD additionally lists Next.js 15.0.0 – 16.0.7 as affected, since it ships
the RSC runtime transitively. React's advisory names the exposed ecosystem:
Next.js, React Router, Waku, @parcel/rsc, @vitejs/plugin-rsc, and rwsdk
(Redwood SDK).
You are not affected if your React code does not use a server, or if you do not use a framework, bundler, or bundler plugin that supports RSC. React Native is unaffected unless a monorepo pulls in the vulnerable packages.
Note the shape of the fix: every patched release is a patch-level bump. There is no code of yours in the vulnerable path, and nothing of yours to rewrite.
Impact
Successful exploitation gives an unauthenticated attacker arbitrary OS command execution as the user running the Node.js server — enough to read secrets and environment variables, pivot within the network, or take over the host.
The CVSS v3.1 vector assigned by Facebook, Inc. is
AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H — 10.0. The detail worth reading there
is S:C (Scope: Changed): the compromised component is the RSC runtime, but
the impact lands on the whole host. That single metric is the difference between
the 9.8 this class of bug usually scores and a clean 10.0.
This was not theoretical for long:
| Date | Event |
|---|---|
| 2025-12-03 | Disclosure, patches published, advisory GHSA-fv66-9v8q-g76r |
| 2025-12-05 | Added to the CISA Known Exploited Vulnerabilities catalog |
| 2025-12-12 | CISA remediation deadline for federal agencies (BOD 22-01) |
CISA flags it with knownRansomwareCampaignUse: Known, and AWS reported
China-nexus threat groups exploiting it in the wild. CISA's own guidance is to
"check for signs of potential compromise on all internet accessible REACT
instances after applying mitigations" — patching alone does not answer
whether you were already hit.
Proof of concept
The exploit builds the payload, posts it as multipart FormData to the
vulnerable server action, and returns the command output. The end-to-end flow:
- Craft — assemble the JSON gadget (
then/status/_response+FormDataconstructor refs) with the operator-supplied command. - Send — POST it as multipart
FormDatato the vulnerable RSC endpoint. - Resolve — the deserializer walks past the missing own-property guard and reaches the execution gadget.
- Execute — the command runs on the server and its output comes back in the response.
# python3 exploit.py -c "<command>" run a single command
# python3 exploit.py -i interactive shell
# python3 exploit.py -c "whoami" -v verbose (show the raw payload/response)Run it
Prerequisites: Node.js with npm, and Python 3 with the requests library.
The lab pins Next.js 15.1.3 exactly — an unpinned install would resolve to
a patched RSC runtime and the exploit would simply stop working. Keep the pin.
1. Bring up the vulnerable app:
npm install
pip3 install requests
npm run dev2. Fire a single command from a second terminal:
python3 exploit.py -c "whoami"Useful commands to confirm execution: pwd, ls -la, cat <file>, and env
to enumerate environment variables (a common place for leaked secrets).
3. Drop into an interactive shell to run commands back-to-back:
python3 exploit.py -i4. Inspect the wire format with verbose mode when you want to see the raw payload and server response:
python3 exploit.py -c "whoami" -v