The BloodHound cheat sheet covers AD attack path analysis, data collection with SharpHound/BloodHound-Python, Cypher query builder and library, 80+ edge definitions (Core AD, ADCS, cross-domain, coercion, Azure), BloodHound CE API usage, and operational workflows.
# 1. Start BloodHound CE
curl -L https://ghst.ly/getbhce | docker compose -f - up
# 2. Collect data (from Windows, domain-joined)
SharpHound.exe -c All --zipfilename bh_$(hostname).zip
# 3. Collect from Linux (network access to DC)
bloodhound-python -u user -p 'P@ss' -d corp.local -ns 10.10.10.10 -c All --zip
# 4. Upload via UI or API
curl -s -X POST http://localhost:8080/api/v2/file-upload \
-H "Authorization: Bearer $TOKEN" -F "file=@bh_HOST.zip"
# 5. Mark owned principals, run pre-built path query
# UI: Search node -> right-click -> "Mark as Owned"
# Cypher: MATCH (n {name:"[email protected]"}) SET n.owned=true
# Docker Compose (recommended, pulls everything)
curl -L https://ghst.ly/getbhce | docker compose -f - up
# Retrieve the auto-generated admin password
docker compose logs | grep "Initial Password"
# Access: http://localhost:8080 (admin / <password>)
# Pin to a specific version
curl -L https://ghst.ly/getbhce -o docker-compose.yml
# Edit BLOODHOUND_VERSION in the file, then:
docker compose up -d
| Scenario | Access Level | Recommended Command |
|---|---|---|
| Domain-joined Windows | DA / local admin | SharpHound.exe -c All |
| Domain-joined Windows | Low-priv domain user | SharpHound.exe -c DCOnly,Group,ACL,Container,Trusts,ObjectProps |
| Non-joined Windows | Have domain creds | SharpHound.exe -c All -d corp.local --ldapusername user --ldappassword pass |
| Linux / external | Password creds | bloodhound-python -u user -p pass -d corp.local -ns DC_IP -c All |
| Linux / external | NTLM hash | bloodhound-python -u user --hashes :NThash -d corp.local -ns DC_IP |
| Linux / external | Kerberos ccache | KRB5CCNAME=user.ccache bloodhound-python -k -d corp.local -ns DC_IP -c All --no-pass |
| Linux / external | No creds (unauthenticated) | netexec smb 10.10.10.0/24 -u '' -p '' then bloodhound-python with guest/anonymous |
| Method | What It Collects | Noise Level | Notes |
|---|---|---|---|
Default |
Medium | Best starting point | |
All |
High | Triggers most detections | |
DCOnly |
No computer connections, stealth choice | ||
ComputerOnly |
Sessions, local groups, user rights from domain hosts | High | Hits every computer |
Group |
AD group membership | Low | LDAP only |
LocalAdmin |
Local Administrators group on all computers | Medium | Requires SMB |
RDP |
Remote Desktop Users group | Medium | Requires SMB |
DCOM |
Distributed COM Users group | Medium | Requires SMB |
PSRemote |
WinRM/PowerShell Remote users | Medium | Requires SMB |
Session |
Active user sessions (NetSessionEnum) | Very noisy, blocked by GPO | |
LoggedOn |
Privileged sessions via registry (needs local admin) | Requires admin on each host | |
Trusts |
Domain/forest trust relationships | Low | LDAP only |
ACL |
DACL/ACE entries for all AD objects | Low | LDAP only |
Container |
OUs, containers, GPO links | Low | LDAP only |
ObjectProps |
LastLogon, PwdLastSet, UAC flags | Low | LDAP only |
GPOLocalGroup |
Local group membership via GPO | Low | LDAP only |
CARegistry |
ADCS CA registry settings | Low | Requires CA access |
CertServices |
Certificate templates, enterprise CAs | Low | LDAP only |
UserRights |
User rights assignments on computers | Medium | Requires SMB |
| Flag | Default | Description |
|---|---|---|
-c, --collectionmethods |
Default |
|
-d, --domain |
Current | Target AD domain |
-s, --searchforest |
false | Collect all domains in forest |
--stealth |
false | |
--computerfile |
- | File with computer names/IPs to target |
--distinguishedname |
- | Limit search to base DN |
-f, --ldapfilter |
- | Filter collected principals by LDAP query |
--excludedcs |
false | Skip domain controllers |
--collectallproperties |
false | Collect all string LDAP properties |
--outputdirectory |
. |
Output folder for ZIP/JSON |
--outputprefix |
- | Prefix for output filenames |
--nozip |
false | Don't compress output files |
--zipfilename |
auto | Custom ZIP filename |
--zippassword |
- | |
--randomfilenames |
false | |
--loop |
false | Repeat computer collection |
--loopduration |
02:00:00 |
Total loop time (HH:MM:SS) |
--loopinterval |
00:05:00 |
Wait between loops (HH:MM:SS) |
--domaincontroller |
auto | Target specific DC (IP or hostname) |
--ldapport |
389 | Custom LDAP port |
--secureldap |
false | Use LDAPS (port 636) |
--ldapusername |
- | Alternate LDAP username |
--ldappassword |
- | Alternate LDAP password |
--disablesigning |
false | Disable Kerberos signing/sealing |
--disablecertverification |
false | Disable LDAPS cert check |
--dolocaladminsessionenum |
false | Use local admin creds for sessions |
--throttle |
0 | Millisecond delay per computer request |
--jitter |
0 | % variation added to throttle |
--threads |
50 | Enumeration thread count |
--portchecktimeout |
2000 | Port 445 check timeout (ms) |
--skipportcheck |
false | Skip port 445 pre-check |
--skipregistryloggedon |
false | Skip registry session enum |
--memcache |
false | |
--randomfilenames |
false | Randomize cache/output names |
--verbosity |
0 | Log verbosity (0-2) |
# Quietest: LDAP only, no computer connections, randomized filenames
SharpHound.exe -c DCOnly --memcache --randomfilenames --outputdirectory C:\Windows\Temp
# Low-noise: add local group and ACL, still mostly LDAP
SharpHound.exe -c DCOnly,LocalAdmin,ACL,Container,ObjectProps,Trusts
# Session loop (for catching DA logons), throttled, jittered
SharpHound.exe -c Session --loop --loopduration 04:00:00 --loopinterval 00:15:00 --throttle 2000 --jitter 25
# Target single OU with specific creds
SharpHound.exe -c All --distinguishedname "OU=Servers,DC=corp,DC=local" --ldapusername svc_scan --ldappassword 'P@ss!'
# Use encrypted ZIP for exfil
SharpHound.exe -c All --zipfilename results.zip --zippassword 'S3cur3!'
# Domain-joined context with alternate creds (runas scenario)
SharpHound.exe -c All -d otherdomain.local --ldapusername domuser --ldappassword pass --domaincontroller 10.10.20.5
# Enumerate specific computers from file
SharpHound.exe -c ComputerOnly --computerfile C:\hosts.txt --skipportcheck
# CE version (BloodHound 5.x+)
pip install bloodhound-ce
bloodhound-ce-python -u user -p 'pass' -d corp.local -ns 10.10.10.10 -c All
# Legacy version
pip install bloodhound
bloodhound-python -u user -p 'pass' -d corp.local -ns 10.10.10.10 -c All
# NTLM hash auth
bloodhound-python -u user --hashes 'aad3b435b51404eeaad3b435b51404ee:NThash' \
-d corp.local -ns 10.10.10.10 -c All
# Kerberos auth via ccache
KRB5CCNAME=/tmp/user.ccache bloodhound-python -u user -d corp.local \
-ns 10.10.10.10 -c All -k --no-pass
# AES key auth
bloodhound-python -u user --aeskey <aes256key> -d corp.local -ns 10.10.10.10 -c All -k
# DNS via TCP (useful when UDP is blocked)
bloodhound-python -u user -p pass -d corp.local -ns 10.10.10.10 -c All --dns-tcp
# Auth method selection (auto, ntlm, kerberos)
bloodhound-python -u user -p pass -d corp.local -ns 10.10.10.10 --auth-method ntlm -c All
# Global Catalog server for multi-domain
bloodhound-python -u user -p pass -d corp.local -ns 10.10.10.10 -gc gc.corp.local -c All
# Zip output, custom directory, workers
bloodhound-python -u user -p pass -d corp.local -ns 10.10.10.10 \
-c All --zip -o /tmp/bh/ -w 5
# Stealth: DCOnly equivalent
bloodhound-python -u user -p pass -d corp.local -ns 10.10.10.10 \
-c DCOnly,Group,ACL,Container,Trusts,ObjectProps
# Specific collection methods
bloodhound-python -u user -p pass -d corp.local -ns 10.10.10.10 \
-c Group,LocalAdmin,ACL,Session,Trusts,ObjectProps,Container
| Flag | Description |
|---|---|
-u |
Username (user or user@domain) |
-p |
Password |
--hashes |
NTLM hash (LM:NT or :NT) |
-k |
Use Kerberos authentication |
--no-pass |
Don't prompt for password (use with -k) |
--aeskey |
AES key for Kerberos |
-d |
|
-ns |
|
-dc |
Specific DC hostname override |
-gc |
Global Catalog server |
-c |
|
--auth-method |
Force auth: auto, ntlm, kerberos |
--dns-tcp |
|
--zip |
Compress output into ZIP |
-o |
Output directory |
-w |
Worker thread count (default 10) |
--exclude-dcs |
Skip domain controllers |
-v |
Verbosity |
# Download AzureHound
wget https://github.com/SpecterOps/azurehound/releases/latest/download/azurehound-linux-amd64.zip
unzip azurehound-linux-amd64.zip && chmod +x azurehound
# --- Authentication Methods ---
# Username + Password (MFA-less accounts only)
./azurehound list -u "[email protected]" -p 'P@ssw0rd' -t "TENANT_ID" -o azure.json
# Service Principal (App registration with secret)
./azurehound list --app "$APP_ID" --secret "$APP_SECRET" -t "$TENANT_ID" -o azure.json
# Service Principal with certificate
./azurehound list --app "$APP_ID" --cert "$CERT_PATH" --key "$KEY_PATH" -t "$TENANT_ID" -o azure.json
# JWT token (from az cli or manual acquisition)
JWT=$(az account get-access-token --resource https://graph.microsoft.com | jq -r .accessToken)
./azurehound list --jwt "$JWT" -t "$TENANT_ID" -o azure.json
# Refresh token
./azurehound list -r "$REFRESH_TOKEN" -t "$TENANT_ID" -o azure.json
# List specific object types
./azurehound list users -t "$TENANT_ID" -o users.json
./azurehound list groups -t "$TENANT_ID" -o groups.json
./azurehound list service-principals -t "$TENANT_ID" -o sps.json
./azurehound list subscriptions -t "$TENANT_ID" -o subs.json
# Via proxy
./azurehound list --proxy "http://127.0.0.1:8080" -t "$TENANT_ID" -o azure.json
# BloodHound Enterprise mode (streaming)
./azurehound configure # Set BHE URL + API key
./azurehound start # Stream to BHE continuously
| Flag | Description |
|---|---|
-t, --tenant |
|
-u |
Username (UPN) |
-p |
Password |
--app |
App/client ID |
--secret |
Client secret |
--cert |
Certificate file path |
--key |
Private key file path |
-j, --jwt |
Pre-acquired JWT access token |
-r, --refresh-token |
Refresh token |
-o |
Output file (JSON) |
--proxy |
Proxy URL |
--log-file |
Log output to file |
-v, --verbosity |
Verbosity (-1 to 2) |
// Shortest path to Domain Admins from owned principals
MATCH p=shortestPath((n {owned:true})-[*1..]->(g:Group))
WHERE g.objectid ENDS WITH '-512' AND n<>g
RETURN p
// Shortest path to Enterprise Admins (EA)
MATCH p=shortestPath((n {owned:true})-[*1..]->(g:Group))
WHERE g.objectid ENDS WITH '-519' AND n<>g
RETURN p
// Paths to Schema Admins
MATCH p=shortestPath((n {owned:true})-[*1..]->(g:Group))
WHERE g.objectid ENDS WITH '-518' AND n<>g
RETURN p
// Paths to Account Operators
MATCH p=shortestPath((n {owned:true})-[*1..]->(g:Group))
WHERE g.objectid ENDS WITH '-548' AND n<>g
RETURN p
// All users with ANY path to DA (show count, sorted)
MATCH (u:User), (g:Group) WHERE g.objectid ENDS WITH '-512'
MATCH p=shortestPath((u)-[*1..]->(g))
RETURN u.name, length(p) AS hops ORDER BY hops ASC LIMIT 50
// Non-admin users with path to DA (find weak links)
MATCH (u:User {admincount:false, enabled:true})
MATCH (g:Group) WHERE g.objectid ENDS WITH '-512'
MATCH p=shortestPath((u)-[*1..10]->(g))
RETURN u.name, length(p) AS hops ORDER BY hops ASC LIMIT 25
// All paths to DA, limit 3 hops (faster for large graphs)
MATCH (g:Group) WHERE g.objectid ENDS WITH '-512'
MATCH p=(n)-[*1..3]->(g) WHERE NOT n=g
RETURN p LIMIT 20
// Find owned principals with WriteDacl, WriteOwner, GenericAll, GenericWrite on any node
MATCH p=(n {owned:true})-[:WriteDacl|WriteOwner|GenericAll|GenericWrite|Owns]->(m)
RETURN p LIMIT 50
// Find users with GenericAll over Domain Admins group
MATCH p=(u)-[:GenericAll]->(g:Group) WHERE g.objectid ENDS WITH '-512' RETURN p
// Find principals with DCSync rights (three relevant edges)
MATCH p=(n)-[:DCSync|GetChangesAll|AllExtendedRights]->(d:Domain) RETURN p
// Find ForceChangePassword edges to enabled users
MATCH p=(n)-[:ForceChangePassword]->(u:User {enabled:true}) RETURN p
// AddKeyCredentialLink (Shadow Credentials)
MATCH p=(n)-[:AddKeyCredentialLink]->(m) RETURN p
// WriteSPN edges (set-SPN for targeted Kerberoasting)
MATCH p=(n)-[:WriteSPN]->(u:User) RETURN p
// WriteAccountRestrictions (for RBCD abuse)
MATCH p=(n)-[:WriteAccountRestrictions]->(m:Computer) RETURN p
// AllExtendedRights on user objects (can change password / read LAPS)
MATCH p=(n)-[:AllExtendedRights]->(u:User) WHERE n<>u RETURN p
// AddMember rights to high-value groups
MATCH p=(n)-[:AddMember|AddSelf]->(g:Group {highvalue:true}) RETURN p
// WriteDacl on domain objects (dangerous)
MATCH p=(n)-[:WriteDacl]->(d:Domain) RETURN p
// Full ACL abuse chain via any combination
MATCH p=shortestPath((n {owned:true})-[:MemberOf|GenericAll|GenericWrite|WriteDacl|WriteOwner|ForceChangePassword|Owns|AllExtendedRights|AddMember|AddKeyCredentialLink*1..]->(g:Group))
WHERE g.objectid ENDS WITH '-512' AND n<>g
RETURN p LIMIT 10
// All Kerberoastable users (exclude krbtgt)
MATCH (u:User {hasspn:true, enabled:true})
WHERE NOT u.name STARTS WITH 'KRBTGT'
RETURN u.name, u.serviceprincipalnames, u.pwdlastset ORDER BY u.pwdlastset ASC
// Kerberoastable users with path to DA
MATCH (u:User {hasspn:true})
MATCH (g:Group) WHERE g.objectid ENDS WITH '-512'
MATCH p=shortestPath((u)-[*1..]->(g))
RETURN u.name, length(p) AS hops ORDER BY hops ASC
// Kerberoastable users who are local admin somewhere
MATCH (u:User {hasspn:true})-[:AdminTo|MemberOf*1..]->(:Group)-[:AdminTo]->(c:Computer)
RETURN u.name, c.name
// AS-REP Roastable users
MATCH (u:User {dontreqpreauth:true, enabled:true}) RETURN u.name
// Unconstrained delegation (computers, excluding DCs)
MATCH (c:Computer {unconstraineddelegation:true})
WHERE NOT EXISTS {
MATCH (c)-[:MemberOf*1..]->(g:Group) WHERE g.objectid ENDS WITH '-516'
}
RETURN c.name, c.operatingsystem
// Constrained delegation (users and computers)
MATCH (n) WHERE n.allowedtodelegate IS NOT NULL AND n.allowedtodelegate <> []
RETURN labels(n)[0] AS type, n.name, n.allowedtodelegate
// Resource-based constrained delegation (RBCD)
MATCH p=(n)-[:AllowedToAct]->(m:Computer) RETURN p
// Shortest path to unconstrained delegation systems from owned
MATCH p=shortestPath((n {owned:true})-[*1..]->(c:Computer {unconstraineddelegation:true}))
WHERE NOT n=c RETURN p
// Find all ADCS ESC1 attack paths
MATCH p=()-[:ADCSESC1]->() RETURN p
// Find all ADCS ESC3 attack paths
MATCH p=()-[:ADCSESC3]->() RETURN p
// Find all ADCS ESC4 attack paths (template write control)
MATCH p=()-[:ADCSESC4]->() RETURN p
// Find all ADCS ESC6a/6b (EDITF_ATTRIBUTESUBJECTALTNAME2)
MATCH p=()-[:ADCSESC6a|ADCSESC6b]->() RETURN p
// Find all ADCS ESC9a/9b
MATCH p=()-[:ADCSESC9a|ADCSESC9b]->() RETURN p
// Find all ADCS ESC10a/10b
MATCH p=()-[:ADCSESC10a|ADCSESC10b]->() RETURN p
// Find all ADCS ESC13 paths
MATCH p=()-[:ADCSESC13]->() RETURN p
// Find all ESC paths combined
MATCH p=()-[r:ADCSESC1|ADCSESC3|ADCSESC4|ADCSESC6a|ADCSESC6b|ADCSESC9a|ADCSESC9b|ADCSESC10a|ADCSESC10b|ADCSESC13]->()
RETURN type(r) AS esc_type, startNode(p).name AS attacker, endNode(p).name AS target
// Who has enrollment rights on published templates?
MATCH p=(n)-[:Enroll|AllExtendedRights|GenericAll]->(t:CertTemplate)-[:PublishedTo]->(ca:EnterpriseCA)
RETURN n.name, t.name, ca.name
// Find templates with enrollee-supplied SAN (ESC1 indicator)
MATCH (t:CertTemplate) WHERE t.enrolleesuppliessubject = true
MATCH p=(t)-[:PublishedTo]->(:EnterpriseCA)
RETURN t.name, t.requiresmanagerapproval, t.authenticationenabled
// Find ManageCA / ManageCertificates rights (ESC7)
MATCH p=(n)-[:ManageCA|ManageCertificates]->(ca:EnterpriseCA) RETURN p
// GoldenCert paths
MATCH p=()-[:GoldenCert]->() RETURN p
// Coerce + relay to ADCS
MATCH p=()-[:CoerceAndRelayNTLMToADCS]->() RETURN p
// EnrollOnBehalfOf (delegation agent abuse)
MATCH p=(n)-[:EnrollOnBehalfOf|DelegatedEnrollmentAgent]->(t:CertTemplate) RETURN p
// ADCS infrastructure chain
MATCH p=(ca:EnterpriseCA)-[:IssuedSignedBy]->(root:RootCA)-[:RootCAFor]->(d:Domain) RETURN p
MATCH p=(ca:EnterpriseCA)-[:TrustedForNTAuth]->(store:NTAuthStore)-[:NTAuthStoreFor]->(d:Domain) RETURN p
// Map all domain trusts
MATCH p=(d1:Domain)-[:CrossForestTrust|SameForestTrust|TrustedBy|Trusts]->(d2:Domain) RETURN p
// Find foreign users in local groups
MATCH (u:User)-[:MemberOf]->(g:Group) WHERE u.domain <> g.domain RETURN u.name, g.name
// Find cross-domain admin paths
MATCH p=(n)-[r]->(m) WHERE n.domain <> m.domain RETURN p LIMIT 50
// Foreign group membership
MATCH (g1:Group)-[:MemberOf]->(g2:Group) WHERE g1.domain <> g2.domain RETURN g1.name, g2.name
// SID History abuse paths
MATCH p=(n)-[:HasSIDHistory]->(m) RETURN p
// SID spoofing paths (cross-forest)
MATCH p=()-[:SpoofSIDHistory]->() RETURN p
// TGT delegation abuse across trusts
MATCH p=()-[:AbuseTGTDelegation]->() RETURN p
// DCFor (which computers are DCs for which domains)
MATCH p=(c:Computer)-[:DCFor]->(d:Domain) RETURN c.name, d.name
// Ownership across domains
MATCH (n)-[r:Owns|GenericAll|WriteDacl]->(m) WHERE n.domain <> m.domain RETURN n.name, type(r), m.name
// GPO applied to high-value OUs
MATCH p=(gpo:GPO)-[:GPLink]->(ou:OU)-[:Contains*1..]->(n {highvalue:true}) RETURN p
// Who controls GPOs (write access)?
MATCH p=(n)-[:GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner]->(gpo:GPO) RETURN p
// GPO applies to computers (find scope)
MATCH p=()-[:GPOAppliesTo]->() RETURN p
// WriteGPLink (add GPO to OU)
MATCH p=(n)-[:WriteGPLink]->(ou:OU) RETURN p
// Container delegation (GenericAll on OU)
MATCH p=(n)-[:GenericAll]->(ou:OU) RETURN p
// OU contains DA (map scope of OU control)
MATCH (ou:OU)-[:Contains*1..]->(u:User)-[:MemberOf*1..]->(g:Group)
WHERE g.objectid ENDS WITH '-512'
RETURN ou.name, u.name
// GPO links to Domain Controllers OU
MATCH p=(gpo:GPO)-[:GPLink]->(ou:OU {name:"DOMAIN [email protected]"}) RETURN p
// Propagated ACEs via container inheritance
MATCH p=()-[:PropagatesACEsTo]->() RETURN p
// Computers with local admin on other computers (rare but high-impact)
MATCH p=(c1:Computer)-[:AdminTo]->(c2:Computer) RETURN p
// DCOM, RDP, PSRemote between computers
MATCH p=(c1:Computer)-[:ExecuteDCOM|CanRDP|CanPSRemote]->(c2:Computer) RETURN p
// Computers with sessions leading to DA
MATCH (u:User)-[:MemberOf*1..]->(g:Group) WHERE g.objectid ENDS WITH '-512'
MATCH p=(c:Computer)-[:HasSession]->(u)
RETURN c.name, u.name
// Shortest path: any owned computer to DA
MATCH (c:Computer {owned:true}), (g:Group) WHERE g.objectid ENDS WITH '-512'
MATCH p=shortestPath((c)-[*1..]->(g))
RETURN p LIMIT 10
// Computers allowed to delegate to DCs
MATCH (c:Computer)-[:AllowedToDelegate]->(dc:Computer)
WHERE EXISTS { MATCH (dc)-[:DCFor]->(:Domain) }
RETURN c.name, dc.name, c.allowedtodelegate
// MSSQL servers (SQL admin paths)
MATCH (c:Computer) WHERE ANY(spn IN c.serviceprincipalnames WHERE toUpper(spn) CONTAINS 'MSSQL')
RETURN c.name, c.serviceprincipalnames
// SQLAdmin paths
MATCH p=(u:User)-[:SQLAdmin]->(c:Computer) RETURN p
// Coerce + relay NTLM to SMB
MATCH p=()-[:CoerceAndRelayNTLMToSMB]->() RETURN p
// Coerce + relay NTLM to LDAP/LDAPS
MATCH p=()-[:CoerceAndRelayNTLMToLDAP|CoerceAndRelayNTLMToLDAPS]->() RETURN p
// Users with password stored in description (common misconfiguration)
MATCH (u:User) WHERE u.description =~ '(?i).*(pass|pwd|password|cred|secret|key|login).*'
RETURN u.name, u.description
// Computers without LAPS (lapsexpirationtime is null/absent)
MATCH (c:Computer) WHERE c.haslaps = false OR c.haslaps IS NULL
RETURN c.name, c.operatingsystem ORDER BY c.operatingsystem
// Computers with LAPS readable by non-admins
MATCH p=(n)-[:ReadLAPSPassword]->(c:Computer)
WHERE NOT n.objectid ENDS WITH '-512' AND NOT n.objectid ENDS WITH '-516'
RETURN n.name, c.name
// Stale user accounts (not logged in for 90 days, still enabled)
MATCH (u:User {enabled:true})
WHERE u.lastlogontimestamp < (datetime().epochseconds - (90 * 86400))
AND NOT u.lastlogontimestamp IN [-1.0, 0.0]
RETURN u.name, datetime({epochSeconds: toInteger(u.lastlogontimestamp)}) AS last_logon
ORDER BY u.lastlogontimestamp ASC
// Never-logged-in enabled accounts
MATCH (u:User {enabled:true}) WHERE u.lastlogontimestamp = -1.0 RETURN u.name
// Passwords not changed in 365+ days (enabled accounts)
MATCH (u:User {enabled:true})
WHERE u.pwdlastset < (datetime().epochseconds - (365 * 86400))
AND NOT u.pwdlastset IN [-1.0, 0.0]
RETURN u.name, datetime({epochSeconds: toInteger(u.pwdlastset)}) AS pwd_set
ORDER BY u.pwdlastset ASC
// Password never expires (enabled accounts)
MATCH (u:User {enabled:true, pwdneverexpires:true}) RETURN u.name
// Service accounts with SPNs (Kerberoastable, old passwords)
MATCH (u:User {hasspn:true, enabled:true})
WHERE u.pwdlastset < (datetime().epochseconds - (365 * 86400))
AND NOT u.name STARTS WITH 'KRBTGT'
RETURN u.name, u.serviceprincipalnames,
datetime({epochSeconds: toInteger(u.pwdlastset)}) AS pwd_set
ORDER BY u.pwdlastset ASC
// GMSA passwords readable by principals
MATCH p=(n)-[:ReadGMSAPassword]->(u:User) RETURN p
// Accounts with adminCount=1 (protected by SDProp)
MATCH (u:User {admincount:true, enabled:true}) RETURN u.name ORDER BY u.name
// Mark single user as owned
MATCH (u:User {name:"[email protected]"}) SET u.owned = true
// Bulk mark owned from list
MATCH (u:User) WHERE u.name IN ["[email protected]","[email protected]","[email protected]"]
SET u.owned = true
// Mark computer as owned
MATCH (c:Computer {name:"WORKSTATION01.CORP.LOCAL"}) SET c.owned = true
// Mark custom high-value targets
MATCH (c:Computer {name:"FILESERVER01.CORP.LOCAL"}) SET c.highvalue = true
MATCH (g:Group {name:"IT [email protected]"}) SET g.highvalue = true
// Find path from ALL owned to ALL high-value
MATCH p=shortestPath((n {owned:true})-[*1..]->(m {highvalue:true}))
WHERE n <> m RETURN p LIMIT 25
// List all owned nodes by type
MATCH (n {owned:true}) RETURN labels(n)[0] AS type, n.name ORDER BY type
// List groups of owned users (lateral movement surface)
MATCH (u:User {owned:true})
MATCH p=(u)-[:MemberOf*1..]->(g:Group)
RETURN u.name, g.name
// Reset all owned flags (cleanup)
MATCH (n {owned:true}) SET n.owned = false
| Category | Query Name | Purpose |
|---|---|---|
| Find all Domain Admins | All members of DA group | |
| Map Domain Trusts | Trust relationships between domains | |
| Computers with unsupported OS | Legacy OS (2000/2003/2008/XP/Vista/7) | |
| Find Principals with DCSync | GetChangesAll / DCSync on domain | |
| Find users with Foreign Domain Group Membership | Cross-domain group nesting | |
| Computers where Domain Users are Local Admin | Domain Users in Administrators | |
| LAPS Password Readers | Non-admins who can read LAPS | |
| All paths from Domain Users to HVT | High-value target exposure | |
| Dangerous privileges for Domain Users | DCO,ExecuteDCOM,GenericAll etc. | |
| Kerberoastable Members of High Value Groups | SPN users in privileged groups | |
| All Kerberoastable Accounts | All users with SPN | |
| Kerberoastable Users with most privileges | Ranked by admin count | |
| AS-REP Roastable Users | dontreqpreauth=true | |
| Shortest Paths to Unconstrained Delegation | Paths to machines with unconstrained deleg | |
| Shortest Paths from Kerberoastable Users | Kerberoastable to computer | |
| Shortest Paths to DA from Kerberoastable | Kerberoastable to DA path | |
| Sessions | Find Domain Admin Logons to non-DCs | DA token exposure on workstations |
| Sessions | Workstations where Domain Users can RDP | RDP exposure |
| Sessions | Servers where Domain Users can RDP | RDP on servers |
| Shortest Path from Owned Principals | Owned node to computers | |
| Shortest Paths to DA from Owned | Owned to Domain Admin | |
| Shortest Paths to High Value Targets | Any node to HVT | |
| Shortest Paths from Domain Users to HVT | Domain Users group to HVT | |
| Find Shortest Paths to DA | Any to DA | |
| Shortest Paths from Owned to HVT | Owned to HVT | |
| Azure | Find all Global Administrators | AZGlobalAdmin role members |
| Azure | High Privileged Role Members | GA/UA/CA/EA/HA roles |
| Azure | OnPrem synced users in privileged roles | Hybrid attack surface |
| Azure | Azure Users path to HVT | AZUser to highvalue |
| Azure | OnPrem synced users path to HVT | Hybrid path to HVT |
| Azure | Shortest paths to privileged roles | Any to GA/EA roles |
| Azure | Azure Apps path to HVT | AZApp to highvalue |
| Azure | Shortest paths to Azure Subscriptions | Any to subscription |
| Azure | Shortest paths from owned to HVT | Owned AZ node to HVT |
| Azure | Shortest paths from owned to privileged roles | Owned to GA/EA |
| Azure | Service Principals with MS Graph privilege | AZMGGrantAppRoles |
| Azure | Service Principals MS Graph App Roles | High-risk Graph permissions |
| Azure | Direct Controllers of MS Graph | AddOwner/AddSecret on Graph SP |
| Azure | Shortest paths to MS Graph | Any to Microsoft Graph SP |
| Node Type | Description | Key Properties |
|---|---|---|
User |
hasspn, dontreqpreauth, admincount, enabled, pwdlastset | |
Computer |
AD computer object | unconstraineddelegation, allowedtodelegate, haslaps, operatingsystem |
Group |
AD security/distribution group | admincount, highvalue |
Domain |
AD domain root object | functionallevel, trusttype |
OU |
Organizational Unit | blocksinheritance |
Container |
AD container (not OU) | - |
GPO |
Group Policy Object | gpcpath |
CertTemplate |
enrolleesuppliessubject, requiresmanagerapproval, schemaversion | |
EnterpriseCA |
caname, dnshostname, flags | |
RootCA |
Root Certificate Authority | certchain |
AiaCA |
Authority Information Access CA | - |
NTAuthStore |
NT Auth Certificate Store | certthumbprints |
| Node Type | Description |
|---|---|
AZUser |
|
AZGroup |
Azure AD group |
AZApp |
Azure App registration |
AZServicePrincipal |
|
AZDevice |
Azure-joined device |
AZTenant |
|
AZSubscription |
Azure subscription |
AZResourceGroup |
Azure resource group |
AZVM |
Azure virtual machine |
AZKeyVault |
Azure Key Vault |
AZRole |
Azure RBAC / Entra role definition |
AZManagedIdentity |
Managed identity |
| Edge | Abuse Method | Severity |
|---|---|---|
AdminTo |
PSExec, WMI, DCOM, WinRM, Token impersonation | |
MemberOf |
Inherits all rights of the group | |
HasSession |
Token stealing, credential dumping | |
DCSync |
secretsdump.py -just-dc / Mimikatz lsadump::dcsync |
|
GetChanges + GetChangesAll |
Combined = DCSync capability | |
GenericAll |
Change password, add to group, modify any attr | |
GenericWrite |
Write specific attributes (e.g. scriptpath, allowedtodelegate) | |
WriteDacl |
Grant self any right on object | |
WriteOwner |
Take ownership, then WriteDacl | |
Owns |
Implies WriteOwner (take ownership) | |
ForceChangePassword |
net user, Set-ADAccountPassword without knowing current |
|
AllExtendedRights |
Read LAPS, change password, cert enrollment | |
AddMember |
Add principals to group | |
AddSelf |
Add yourself to group | |
AddKeyCredentialLink |
Shadow Credentials (Whisker/Pywhisker) | |
WriteSPN |
Set SPN for targeted Kerberoasting | |
WriteAccountRestrictions |
Write msDS-AllowedToActOnBehalfOfOtherIdentity (RBCD) | |
AllowedToDelegate |
Constrained delegation TGS request for any user | |
AllowedToAct |
RBCD - obtain ticket as any user to target | |
CanRDP |
Remote Desktop access | Medium |
CanPSRemote |
WinRM / Enter-PSSession | Medium |
ExecuteDCOM |
DCOM lateral movement (MMC20, ShellBrowserWindow) | Medium |
SQLAdmin |
xp_cmdshell, lateral movement via SQL | Medium |
ReadLAPSPassword |
Retrieve LAPS plaintext password | |
ReadGMSAPassword |
Retrieve GMSA NT hash | |
DumpSMSAPassword |
Dump sMSA password | Medium |
SyncLAPSPassword |
Sync LAPS password | |
HasSIDHistory |
Old SID history for privilege escalation | |
GPLink |
GPO applied to OU/container/site | |
WriteGPLink |
Link custom GPO to OU | |
Contains |
OU contains objects (scope of delegation) | Info |
DCFor |
Computer is a DC for this domain | Info |
| Edge | Description | Abuse |
|---|---|---|
ADCSESC1 |
Enroll cert for any user including DA | |
ADCSESC3 |
Issue cert on behalf of any user | |
ADCSESC4 |
Modify template to enable ESC1 | |
ADCSESC6a/6b |
SAN on any template | |
ADCSESC9a/9b |
Certificate mapping attack | |
ADCSESC10a/10b |
Certificate auth bypass | |
ADCSESC13 |
Cert grants group membership | |
GoldenCert |
Forge cert for any principal | |
Enroll |
Enrollment right on cert template | Request certificate |
PublishedTo |
Template published to Enterprise CA | Template is issuable |
HasEnrollmentRights |
Principal can enroll on template | First step to ESC1 |
ManageCA |
CA admin rights | Issue/revoke certs, change config |
ManageCertificates |
Certificate officer rights | Approve pending requests |
IssuedSignedBy |
CA cert chain relationship | Maps PKI hierarchy |
TrustedForNTAuth |
CA cert in NTAuth store | CA can authenticate to AD |
NTAuthStoreFor |
NTAuth store belongs to domain | PKI trust anchor |
RootCAFor |
Root CA for domain | PKI root |
EnterpriseCAFor |
Enterprise CA for domain | Issuing CA |
EnrollOnBehalfOf |
Enrollment agent template | Agent can enroll for others |
DelegatedEnrollmentAgent |
Explicit enrollment agent | Scoped agent delegation |
OIDGroupLink |
Template linked to AD group via OID | ESC13 group membership |
ExtendedByPolicy |
Template extended by issuance policy | Policy chain |
WritePKIEnrollmentFlag |
Write enrollment flags on template | Modify template behavior |
WritePKINameFlag |
Write name flags on template | Enable SAN abuse |
HostsCAService |
Computer runs CA service | CA location |
| Edge | Description |
|---|---|
CrossForestTrust |
|
SameForestTrust |
Same-forest trust relationship |
SpoofSIDHistory |
|
AbuseTGTDelegation |
|
HasTrustKeys |
Trust keys (for inter-realm ticket forgery) |
CoerceToTGT |
Coerce authentication for TGT |
CoerceAndRelayNTLMToSMB |
|
CoerceAndRelayNTLMToADCS |
|
CoerceAndRelayNTLMToLDAP |
|
CoerceAndRelayNTLMToLDAPS |
|
SyncedToADUser |
Azure user synced from on-prem AD user |
| Edge | Description | Abuse |
|---|---|---|
AZGlobalAdmin |
Global Administrator role | Full tenant control |
AZPrivilegedRoleAdmin |
Privileged Role Administrator | Assign/modify any role |
AZResetPassword |
Can reset user's password | Account takeover |
AZOwns |
Owns Azure object | Full object control |
AZContributor |
Azure RBAC Contributor on resource | Modify resource |
AZUserAccessAdministrator |
Manage access to resource | Assign roles |
AZHasRole |
Has Azure RBAC role assignment | Scope-specific rights |
AZMemberOf |
Member of Azure AD group | Inherits group rights |
AZAddMembers |
Can add members to group | Escalate via group |
AZAddOwner |
Can add owners to app/group | Escalate ownership |
AZAddSecret |
Can add client secret to app | Authenticate as app |
AZExecuteCommand |
Run command on Azure VM (Intune) | RCE on Azure VM |
AZVMContributor |
VM Contributor role | Manage VM |
AZMGAddSecret |
MS Graph - App.ReadWrite.All | Add secrets to any app |
AZMGAddOwner |
MS Graph - add owner via Graph | Escalate app ownership |
AZMGGrantAppRoles |
MS Graph - AppRoleAssignment.ReadWrite | Grant Graph roles |
AZMGGrantRole |
MS Graph - RoleManagement.ReadWrite | Grant directory roles |
AZKeyVaultContributor |
Key Vault Contributor | Manage vault |
// Find all Enterprise Admins (cross-domain)
MATCH p=(n)-[:MemberOf*1..]->(g:Group) WHERE g.objectid ENDS WITH '-519' RETURN p
// Find AdminSDHolder-protected accounts
MATCH (u:User {admincount:true}) RETURN u.name ORDER BY u.name
// Find all computers (map full environment)
MATCH (c:Computer {enabled:true}) RETURN c.name, c.operatingsystem ORDER BY c.operatingsystem
// Find all trusts to pivot laterally into child/parent domains
MATCH p=(d1:Domain)-[r:CrossForestTrust|SameForestTrust]->(d2:Domain) RETURN p
// KRBTGT for golden ticket operations
MATCH (u:User) WHERE u.name STARTS WITH 'KRBTGT' RETURN u.name, u.domain
// Find backup operators (VSS shadow copy, NTDS.dit)
MATCH (n)-[:MemberOf*1..]->(g:Group) WHERE g.objectid ENDS WITH '-551' RETURN n.name, labels(n)
# Get JWT token (login)
TOKEN=$(curl -s -X POST http://localhost:8080/api/v2/login \
-H "Content-Type: application/json" \
-d '{"login_method":"secret","secret":"admin_password","username":"admin"}' \
| jq -r '.data.session_token')
# Create API key (for scripting, persists without login)
curl -s -X POST http://localhost:8080/api/v2/tokens \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"token_name":"automation_key"}' | jq .
# Use API key in requests
API_KEY="your_api_key_here"
curl -s http://localhost:8080/api/v2/domains \
-H "Authorization: Bearer $API_KEY" | jq .
BASE="http://localhost:8080/api/v2"
# List collected domains
curl -s "$BASE/domains" -H "Authorization: Bearer $TOKEN" | jq '.data[].name'
# Run Cypher query
curl -s -X POST "$BASE/graphs/cypher" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"query":"MATCH (u:User {enabled:true}) RETURN u.name LIMIT 10","include_properties":true}' \
| jq '.data.nodes'
# Search for a node
curl -s "$BASE/search?q=Administrator&type=User" \
-H "Authorization: Bearer $TOKEN" | jq .
# Get node details by object ID
curl -s "$BASE/objects/S-1-5-21-XXXX-YYYY-ZZZZ-500" \
-H "Authorization: Bearer $TOKEN" | jq .
# Upload collection file
curl -s -X POST "$BASE/file-upload" \
-H "Authorization: Bearer $TOKEN" \
-F "file=@bloodhound_output.zip"
# List file upload jobs
curl -s "$BASE/file-upload" \
-H "Authorization: Bearer $TOKEN" | jq '.data[] | {id, status, filename}'
# Get attack paths to high-value target
curl -s "$BASE/attack-paths?finding_type=ComputerLocalAdminRights" \
-H "Authorization: Bearer $TOKEN" | jq .
# Get graph path between two nodes
curl -s "$BASE/graphs/path?start_node=OWNED_USER_OBJECTID&end_node=DA_GROUP_OBJECTID" \
-H "Authorization: Bearer $TOKEN" | jq .
Custom queries are stored locally in ~/.config/bloodhound/customqueries.json (legacy) or managed via the BloodHound CE UI under Explore > Custom Queries.
// customqueries.json format for BloodHound CE
{
"queries": [
{
"name": "Find Users with Password in Description",
"category": "Users",
"queryList": [
{
"final": true,
"query": "MATCH (u:User) WHERE u.description =~ '(?i).*(pass|pwd|password|cred).*' RETURN u.name, u.description"
}
]
},
{
"name": "ADCS: All ESC Paths",
"category": "ADCS",
"queryList": [
{
"final": true,
"query": "MATCH p=()-[r:ADCSESC1|ADCSESC3|ADCSESC4|ADCSESC6a|ADCSESC6b|ADCSESC9a|ADCSESC9b|ADCSESC10a|ADCSESC10b|ADCSESC13]->() RETURN type(r) AS esc, startNode(p).name AS src, endNode(p).name AS dst"
}
]
},
{
"name": "Computers Without LAPS",
"category": "Computers",
"queryList": [
{
"final": true,
"query": "MATCH (c:Computer) WHERE c.haslaps = false OR c.haslaps IS NULL RETURN c.name, c.operatingsystem ORDER BY c.operatingsystem"
}
]
},
{
"name": "Parameterized: Shortest Paths to Group",
"category": "Paths",
"queryList": [
{
"final": false,
"title": "Select target group",
"query": "MATCH (g:Group) RETURN g.name ORDER BY g.name"
},
{
"final": true,
"query": "MATCH p=shortestPath((n {owned:true})-[*1..]->(g:Group {name:$result})) WHERE n<>g RETURN p"
}
]
}
]
}
# Import custom queries file to BloodHound CE via API
curl -s -X PUT "$BASE/customqueries" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @customqueries.json
# List existing custom queries
curl -s "$BASE/customqueries" -H "Authorization: Bearer $TOKEN" | jq '.data[].name'
# Delete a custom query by ID
curl -s -X DELETE "$BASE/customqueries/42" -H "Authorization: Bearer $TOKEN"
| Property | Node Type | Cypher Filter | Meaning |
|---|---|---|---|
owned |
Any | {owned:true} |
|
highvalue |
Any | {highvalue:true} |
|
enabled |
User/Computer | {enabled:true} |
Account is active |
admincount |
User/Group | {admincount:true} |
Protected by AdminSDHolder |
hasspn |
User | {hasspn:true} |
|
dontreqpreauth |
User | {dontreqpreauth:true} |
|
unconstraineddelegation |
Computer/User | {unconstraineddelegation:true} |
|
allowedtodelegate |
Computer/User | WHERE n.allowedtodelegate IS NOT NULL |
|
pwdneverexpires |
User | {pwdneverexpires:true} |
|
pwdlastset |
User | epoch seconds | Last password change timestamp |
lastlogontimestamp |
User | epoch seconds | Last AD logon (replicates ~14 days) |
haslaps |
Computer | {haslaps:false} |
|
operatingsystem |
Computer | regex match | OS version string |
description |
User | regex match | |
serviceprincipalnames |
User/Computer | list contains | SPNs registered on object |
functionallevel |
Domain | string | Domain functional level |
objectid |
Any | ends with RID | SID/object identifier |
enrolleesuppliessubject |
CertTemplate | {enrolleesuppliessubject:true} |
|
requiresmanagerapproval |
CertTemplate | {requiresmanagerapproval:false} |
Template auto-issues |
| RID Suffix | Group Name | Why It Matters |
|---|---|---|
-500 |
Administrator | |
-502 |
krbtgt | |
-512 |
Domain Admins | |
-513 |
Domain Users | |
-514 |
Domain Guests | Restricted access |
-516 |
Domain Controllers | DC computer group |
-517 |
Cert Publishers | |
-518 |
Schema Admins | |
-519 |
Enterprise Admins | |
-520 |
Group Policy Creator Owners | |
-544 |
Administrators (local) | Local admin |
-548 |
Account Operators | |
-549 |
Server Operators | |
-550 |
Print Operators | Can logon to DCs |
-551 |
Backup Operators | |
-553 |
RAS and IAS Servers | Network access |
| Feature | Legacy (v4.x) | |
|---|---|---|
| Backend | Neo4j + REST API | Neo4j direct |
| Interface | Web UI (port 8080) | Electron app |
| Auth | Username/password + API keys | No auth |
| Multi-user | No | |
| Collectors | SharpHound 2.x / CE | SharpHound 1.x |
| ADCS support | Partial | |
| Azure/Entra | Limited | |
| Docker support | Manual | |
| API | None |
BloodHound CE is the current actively developed version. Legacy v4.x is no longer maintained. All commands and queries in this cheatsheet target CE unless noted otherwise.