Skip to content
08 · Object & ACL SecurityPart 1 of 4Foundation

Auditing Active Directory ACLs before an attacker does

How to find dangerous AD ACLs like GenericAll and DCSync rights, clean up stale adminCount flags, and use BloodHound defensively.

Florian Amette5 min read

Group Policy hardening and patching get most of the attention, but a huge share of real-world Active Directory compromises never touch a CVE at all — they abuse permissions that were granted, inherited, or forgotten. An over-permissioned service account, a stale delegation from a decommissioned helpdesk tool, or a DCSync-capable service principal can hand an attacker domain dominance without a single exploit. This guide covers how to find and fix those paths before someone else does.

The ACLs that matter most

Active Directory objects carry discretionary access control lists (DACLs) just like files do, but the rights that matter for security are a small, well-known set:

RightWhat it allowsWhy it's dangerous
GenericAllFull control of the objectCan reset passwords, modify group membership, edit any attribute
GenericWriteWrite any non-protected attributeCan set scriptPath for logon-script execution, alter group membership attributes
WriteDACLModify the object's own ACLGrantee can add themselves GenericAll at will, bypassing future review
WriteOwnerTake ownership of the objectNew owner implicitly gains the ability to grant themselves any right
DS-Replication-Get-Changes + DS-Replication-Get-Changes-AllRequest directory replication dataTogether, these enable DCSync — pulling password hashes for any account, including krbtgt, without touching a DC's disk

Any of the first four rights held on a group like Domain Admins, or on the AdminSDHolder object itself, is equivalent to full domain compromise. The replication rights are dangerous specifically because they are rarely audited — most admins only think to check group membership, not replication permissions on the domain object.

Auditing ACLs with PowerShell

Start with the domain root object itself, since that's where DCSync rights live:

PowerShell
$domainDN = (Get-ADDomain).DistinguishedName
$acl = Get-Acl -Path "AD:\$domainDN"

$acl.Access | Where-Object {
    $_.ActiveDirectoryRights -match "ExtendedRight" -and
    ($_.ObjectType -eq "1131f6aa-9c07-11d1-f79f-00c04fc2dcd2" -or  # DS-Replication-Get-Changes
     $_.ObjectType -eq "1131f6ad-9c07-11d1-f79f-00c04fc2dcd2")     # DS-Replication-Get-Changes-All
} | Select-Object IdentityReference, ActiveDirectoryRights, ObjectType

These two GUIDs are not the only path to DCSync: GenericAll, AllExtendedRights (an extended-right ACE with an empty ObjectType) and WriteDACL on the domain root grant or let the holder grant the same rights. The DCSync rights audit covers the full query and the expected default holders.

Then sweep for dangerous rights across high-value OUs (Domain Controllers, Tier 0 admin OU, privileged groups):

PowerShell
$targets = @("OU=Domain Controllers,DC=corp,DC=example,DC=com",
             "CN=Domain Admins,CN=Users,DC=corp,DC=example,DC=com")

foreach ($dn in $targets) {
    Write-Host "== $dn ==" -ForegroundColor Cyan
    (Get-Acl -Path "AD:\$dn").Access |
        Where-Object { $_.ActiveDirectoryRights -match "GenericAll|GenericWrite|WriteDacl|WriteOwner" } |
        Select-Object IdentityReference, ActiveDirectoryRights, AccessControlType
}

Cross-reference every IdentityReference returned against a known-good list of built-in principals (SYSTEM, Enterprise Admins, Domain Admins, Administrators). Anything else needs a documented business reason.

AdminSDHolder, SDProp, and adminCount

Active Directory protects a fixed set of privileged groups (Domain Admins, Enterprise Admins, Schema Admins, Administrators, Account Operators, Backup Operators, and a few others) using a mechanism called SDProp (SD Propagation). Every 60 minutes, the process:

  1. Reads the ACL on the special AdminSDHolder object.
  2. Applies that ACL to every current member of a protected group, overwriting any ACL customizations made directly on those accounts.
  3. Sets adminCount=1 on each protected member, and does not automatically clear it when the account leaves the protected group.

This is why permission changes you make directly on a Domain Admin's user object keep reverting — SDProp stomps them on its next cycle. It's also why adminCount=1 becomes a durable, if imperfect, marker of "this account was privileged at some point."

To change effective permissions on protected accounts, edit the ACL on AdminSDHolder itself (carefully — it propagates to everyone), not on individual accounts.

PowerShell
$adminSDHolderDN = "CN=AdminSDHolder,CN=System," + (Get-ADDomain).DistinguishedName
(Get-Acl -Path "AD:\$adminSDHolderDN").Access |
    Where-Object { $_.ActiveDirectoryRights -match "GenericAll|WriteDacl|WriteOwner" } |
    Select-Object IdentityReference, ActiveDirectoryRights

Finding and cleaning stale adminCount=1 objects

Accounts removed from privileged groups keep adminCount=1 and, critically, keep inheritance disabled on their ACL — meaning they no longer receive permission changes made at the OU level, and their last-known privileged ACL sticks around indefinitely. This is a common "shadow admin" path: an account that looks ordinary in group membership but still carries residual privileged ACL entries.

PowerShell
# Find users flagged adminCount=1 that are NOT current members of protected groups
$protectedMembers = @()
foreach ($g in "Domain Admins","Enterprise Admins","Schema Admins","Administrators","Account Operators","Backup Operators") {
    $protectedMembers += (Get-ADGroupMember -Identity $g -Recursive -ErrorAction SilentlyContinue).SamAccountName
}

Get-ADUser -Filter "adminCount -eq 1" -Properties adminCount, whenChanged |
    Where-Object { $_.SamAccountName -notin $protectedMembers } |
    Select-Object SamAccountName, DistinguishedName, whenChanged

For each stale hit: confirm the account genuinely no longer needs elevated rights, then re-enable ACL inheritance and let it inherit from its OU again:

PowerShell
$obj = Get-ADUser -Identity "svc_legacybackup"
$acl = Get-Acl -Path "AD:\$($obj.DistinguishedName)"
$acl.SetAccessRuleProtection($false, $true)  # unprotect, inherit from parent
Set-Acl -Path "AD:\$($obj.DistinguishedName)" -AclObject $acl
Set-ADUser -Identity "svc_legacybackup" -Clear adminCount

Do this in batches with change review — clearing adminCount on an account that's still legitimately privileged (just via nested group membership SDProp didn't catch cleanly) will strip protections it still needs.

Using BloodHound and PingCastle defensively

Manual ACL review scales poorly past a few thousand objects. Assessment tools built for attackers work just as well pointed inward:

  • BloodHound ingests AD ACLs, group membership, session data, and GPO links, then graphs attack paths ("shortest path to Domain Admins"). Run it read-only, against a dedicated collection account with no write rights, and treat the output as an internal audit artifact, not something to leave lying around — the graph itself is a target map.
  • PingCastle produces a scored health report covering stale objects, ACL misconfigurations, trust risks, and AdminSDHolder anomalies, with a trend score you can track release over release.

Run these on a recurring schedule (monthly is reasonable for most environments), store historical reports, and track the count of "critical" findings as a metric — not just a point-in-time cleanup exercise.

Verification

After remediating a finding, re-run the exact query that found it and confirm a clean result:

PowerShell
# Re-check DCSync-equivalent rights (replication GUIDs, AllExtendedRights, GenericAll, WriteDACL) after remediation
$domain   = Get-ADDomain
$rootSid  = (Get-ADDomain (Get-ADForest).RootDomain).DomainSID.Value
$expected = @(
    'S-1-5-18',                        # SYSTEM
    'S-1-5-9',                         # Enterprise Domain Controllers
    'S-1-5-32-544',                    # Administrators (built-in)
    "$($domain.DomainSID.Value)-516",  # Domain Controllers
    "$($domain.DomainSID.Value)-512",  # Domain Admins (default WriteDACL)
    "$rootSid-519",                    # Enterprise Admins (default GenericAll)
    "$rootSid-498"                     # Enterprise Read-only Domain Controllers (Get-Changes only)
)
$repl = @([guid]'1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', [guid]'1131f6ad-9c07-11d1-f79f-00c04fc2dcd2')
$acl  = Get-Acl -Path "AD:\$($domain.DistinguishedName)"
$acl.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier]) | Where-Object {
    $r = $_.ActiveDirectoryRights
    $_.AccessControlType -eq 'Allow' -and
    -not $_.PropagationFlags.HasFlag([System.Security.AccessControl.PropagationFlags]::InheritOnly) -and
    $_.IdentityReference.Value -notin $expected -and (
        $r.HasFlag([System.DirectoryServices.ActiveDirectoryRights]::GenericAll) -or
        $r.HasFlag([System.DirectoryServices.ActiveDirectoryRights]::WriteDacl) -or
        ($r.HasFlag([System.DirectoryServices.ActiveDirectoryRights]::ExtendedRight) -and
         ($_.ObjectType -in $repl -or $_.ObjectType -eq [guid]::Empty))
    )
}
# Expect no output, apart from documented exceptions such as the Entra Connect AD DS connector account

What it breaks

  • Legitimate delegated administration. Helpdesk groups scoped to reset passwords in a specific OU, application teams that manage their own service accounts, and backup software that needs replication-adjacent rights can all show up as "findings." Removing these without review breaks real workflows — validate against a change owner before revoking.
  • Third-party AD-integrated tools (backup, identity governance, PAM solutions) sometimes require broad rights by design; confirm vendor documentation before stripping their service account's permissions.
  • Automation that relies on adminCount clearing — if a script or provisioning tool expects adminCount to reflect current group membership in real time, it will need to account for SDProp's one-hour cycle and non-automatic clearing.

Pair this audit with the identity boundaries described in Tier 0 & privileged access, and revisit it any time you decommission a delegated admin tool or helpdesk workflow — those are the most common sources of orphaned ACL grants.

Frequently asked questions

What is the fastest way to find who can DCSync in my domain?

Run Get-Acl against the domain naming context object and filter for principals holding both DS-Replication-Get-Changes and DS-Replication-Get-Changes-All, or GenericAll, AllExtendedRights or WriteDACL, which include or can grant them. By default, domain controllers hold both rights through the Domain Controllers and Enterprise Domain Controllers groups, the built-in Administrators group holds both (covering Domain Admins and Enterprise Admins), and Enterprise Read-only Domain Controllers hold Get-Changes only. Anyone else holding both rights, or one of the broader rights, can perform DCSync.

Why do permissions I remove from a Domain Admin's account keep coming back?

This is AdminSDHolder and the SDProp process. Every 60 minutes, SDProp resets the ACL on every member of a protected group (Domain Admins, Enterprise Admins, etc.) to match the AdminSDHolder object's ACL, overwriting any custom permissions you added directly on that user.

Is it safe to just strip GenericAll and WriteDACL from every non-admin account?

Not without review. Some of those grants are legitimate delegated administration, such as a helpdesk OU-admin group that needs GenericAll scoped to a specific OU. Audit each finding, map it to an intended business purpose, and remove only what has no justification.

Auditing Active Directory ACLs before an attacker does

Related guides

Object & ACL Security

AdminSDHolder and SDProp: cleanup and monitoring

Baseline the AdminSDHolder ACL, find orphaned adminCount=1 accounts, reset their ACLs safely, trigger SDProp on demand, and alert on AdminSDHolder changes.

Intermediate
Group Policy & SYSVOL

Auditing GPO permissions and gPLink rights

Find who can edit, create and link GPOs in Active Directory: GPO ACLs, gPLink rights on OUs and sites, Group Policy Creator Owners and WMI filters.

Intermediate