BloodHound Cheat Sheet — Collection & Cypher Queries
End-to-end BloodHound reference — spin up BloodHound CE, collect with SharpHound / bloodhound-python / NetExec, mark owned nodes, and run the Cypher queries that actually find paths to Domain Admin.

BloodHound maps Active Directory (and Entra ID) as a graph so you can see attack paths a defender would never spot by eye. This note covers the full loop: stand it up → collect data → mark what you own → query for paths.
Editions. BloodHound CE (Community Edition) is the current, actively-maintained version — a Docker stack with a web UI, backed by Postgres + Neo4j. Legacy BloodHound is the old Electron app you point straight at Neo4j. Collectors are not cross-compatible: a CE deployment needs a CE-era SharpHound /
bloodhound-ce-python, and legacy needs the legacy collectors. The Cypher in this note runs in both — only the way you open the query box differs.
Stand Up BloodHound CE
# One-liner: pull the official compose file and start the stack
curl -L https://ghst.ly/getbhce | docker compose -f - up
# Or save it first so you can stop/start later
curl -L https://ghst.ly/getbhce -o docker-compose.yml
docker compose up -d
docker compose logs bloodhound | grep -i "Initial Password" # one-time admin passwordThen browse to http://localhost:8080 and log in as admin with the password printed in the logs. Upload your collected .zip under Administration → File Ingest.
# Legacy BloodHound (Neo4j) — if you're on the old app
sudo neo4j console # default creds neo4j:neo4j (change on first login)
# then launch the BloodHound Electron app and point it at bolt://localhost:7687Data Collection
SharpHound (Windows, on a domain-joined or runas context)
# Collect everything (most common)
.\SharpHound.exe -c All
# Everything + GPO local-group correlation, custom zip name
.\SharpHound.exe -c All,GPOLocalGroup --zipfilename corp
# Target a specific domain / DC with explicit creds (off-host / runas)
.\SharpHound.exe -c All -d corp.local --domaincontroller 10.10.10.1 --ldapusername svc --ldappassword 'Passw0rd!'
# Stealthier: only what you need, throttle + jitter to blend in
.\SharpHound.exe -c Session,LoggedOn --stealth --throttle 1500 --jitter 30
# Loop session collection (catch logons over time)
.\SharpHound.exe -c Session --loop --loopduration 02:00:00 --loopinterval 00:05:00# PowerShell ingestor variant
Import-Module .\SharpHound.ps1
Invoke-BloodHound -CollectionMethod All -OutputDirectory C:\Tempbloodhound-python (remote, from Linux — no agent on the host)
# Standard run with password (use the CE-compatible collector for CE)
bloodhound-python -u svc -p 'Passw0rd!' -d corp.local -ns 10.10.10.1 -c All --zip
# Pass-the-hash
bloodhound-python -u svc -hashes :2b576acbe6bcfda7294d6bd18041b8fe -d corp.local -ns 10.10.10.1 -c All --zip
# Kerberos (with a ccache in KRB5CCNAME)
bloodhound-python -u svc -k -no-pass -d corp.local -ns 10.10.10.1 -dc dc01.corp.local -c All --zip
# CE deployments need the CE ingestor
bloodhound-ce-python -u svc -p 'Passw0rd!' -d corp.local -ns 10.10.10.1 -c All --zipNetExec (LDAP module — quick collection in one command)
nxc ldap dc01.corp.local -u svc -p 'Passw0rd!' --bloodhound --collection All --dns-server 10.10.10.1AzureHound (Entra ID / Azure)
azurehound -u user@tenant.onmicrosoft.com -p 'Passw0rd!' list --tenant tenant.onmicrosoft.com -o azure.jsonCollection methods worth knowing:
All(everything but local-group via GPO),DCOnly(LDAP only — quiet, no host touching),Session/LoggedOn(who's logged in where — the gold for lateral movement),ACL(object permissions),Trusts,CertServices(AD CS / ESC paths).
Mark What You Own
Marking nodes owned / high value is what makes path queries meaningful. In the CE/legacy UI: right-click a node → Mark as Owned / High Value. Or set it directly in Cypher:
// Mark a single user as owned
MATCH (u:User {name:"SVC-ALFRESCO@CORP.LOCAL"}) SET u.owned=true
// Mark a computer you have a shell on
MATCH (c:Computer {name:"WS01.CORP.LOCAL"}) SET c.owned=true
// Bulk-mark from a list of names
WITH ["BOB@CORP.LOCAL","SQLSVC@CORP.LOCAL"] AS owned
MATCH (u:User) WHERE u.name IN owned SET u.owned=trueBasic Path Queries
// Shortest path from any owned principal to Domain Admins
MATCH p=shortestPath((n {owned:true})-[*1..]->(g:Group {name:"DOMAIN ADMINS@CORP.LOCAL"}))
RETURN p
// All shortest paths (shows every equally-short route, not just one)
MATCH p=allShortestPaths((n {owned:true})-[*1..]->(g:Group {name:"DOMAIN ADMINS@CORP.LOCAL"}))
RETURN p
// Shortest path from a specific user to any high-value target
MATCH p=shortestPath((u:User {name:"BOB@CORP.LOCAL"})-[*1..]->(t {highvalue:true}))
RETURN pKerberoastable Accounts
// Every Kerberoastable user (has an SPN)
MATCH (u:User {hasspn:true}) RETURN u.name, u.description
// Kerberoastable users that are members of Domain Admins
MATCH (u:User {hasspn:true})-[:MemberOf*1..]->(g:Group {name:"DOMAIN ADMINS@CORP.LOCAL"})
RETURN u.name
// Privileged Kerberoastable accounts (admincount=true)
MATCH (u:User {hasspn:true, admincount:true}) RETURN u.name
// Kerberoastable users with ANY path to a high-value target
MATCH p=shortestPath((u:User {hasspn:true})-[*1..]->(t {highvalue:true}))
RETURN pAS-REP Roastable Users
// Accounts without Kerberos pre-auth
MATCH (u:User {dontreqpreauth:true}) RETURN u.name, u.description
// AS-REP roastable users with a path to Domain Admins
MATCH p=shortestPath(
(u:User {dontreqpreauth:true})-[*1..]->(g:Group {name:"DOMAIN ADMINS@CORP.LOCAL"})
) RETURN pACL / Permission Abuse
// Principals with WriteDacl over the domain object (edge is WriteDacl — case-sensitive!)
MATCH (n)-[:WriteDacl]->(d:Domain) RETURN n.name, labels(n)
// GenericAll over privileged users (full control → reset password / shadow creds)
MATCH (n)-[:GenericAll]->(v:User {admincount:true})
RETURN n.name, v.name
// ForceChangePassword over any user
MATCH (n)-[:ForceChangePassword]->(u:User) RETURN n.name, u.name
// WriteOwner / Owns (take ownership → rewrite the DACL)
MATCH (n)-[:WriteOwner|Owns]->(u:User {admincount:true}) RETURN n.name, u.name
// Anything that grants DCSync on the domain (DA-equivalent)
MATCH p=(n)-[:DCSync|GetChanges|GetChangesAll|AllExtendedRights|GenericAll*1..]->(d:Domain)
RETURN p
// AddKeyCredentialLink — shadow-credentials targets (no password reset needed)
MATCH (n)-[:AddKeyCredentialLink]->(t) RETURN n.name, t.name, labels(t)Delegation
// Unconstrained delegation hosts (excluding DCs) — coerce + capture TGTs
MATCH (c:Computer {unconstraineddelegation:true})
WHERE NOT c.name STARTS WITH "DC"
RETURN c.name
// Constrained delegation: who can delegate to what
MATCH (n)-[:AllowedToDelegate]->(c:Computer)
RETURN n.name, c.name
// Resource-based constrained delegation (write to msDS-AllowedToActOnBehalfOf)
MATCH (n)-[:AllowedToAct|AddAllowedToAct]->(c:Computer)
RETURN n.name, c.name, type(last(relationships(shortestPath((n)-[*1]->(c)))))Owned-Node Reach
// What can everything I own directly do? (one hop out)
MATCH p=(n {owned:true})-[r]->(m) RETURN p LIMIT 200
// Reachable high-value targets from owned nodes (up to 6 hops)
MATCH p=shortestPath((n {owned:true})-[*1..6]->(t {highvalue:true}))
RETURN p
// Sessions of owned users still live on computers (more creds to grab)
MATCH p=(u:User {owned:true})-[:HasSession]->(c:Computer) RETURN p
// Computers where an owned user is a local admin
MATCH p=(u:User {owned:true})-[:AdminTo]->(c:Computer) RETURN pTier 0 / High-Value Mapping
// Approximate Tier 0 — every admincount=true principal
MATCH (n {admincount:true}) RETURN n.name, labels(n)
// Who has admin rights directly on a Domain Controller
MATCH p=(n)-[:AdminTo]->(c:Computer)
WHERE c.name STARTS WITH "DC"
RETURN p
// Members of Domain Admins / Enterprise Admins (incl. nested)
MATCH (u:User)-[:MemberOf*1..]->(g:Group)
WHERE g.name IN ["DOMAIN ADMINS@CORP.LOCAL","ENTERPRISE ADMINS@CORP.LOCAL"]
RETURN DISTINCT u.name, g.name
// LAPS / gMSA readable passwords (instant lateral / privilege)
MATCH (n)-[:ReadLAPSPassword|ReadGMSAPassword]->(c) RETURN n.name, c.nameCross-Domain & Trusts
// Domain trust map
MATCH p=(:Domain)-[:TrustedBy]->(:Domain) RETURN p
// Foreign group members (principals in a group of another domain)
MATCH (u)-[:MemberOf]->(g:Group)
WHERE u.domain <> g.domain
RETURN u.name, g.name
// Users with SID history (potential cross-domain escalation)
MATCH (u:User)-[:HasSIDHistory]->(t) RETURN u.name, t.nameCypher Tips & Edge Reference
- Relationship names are case-sensitive in Neo4j —
WriteDacl, notWriteDACL. Group/domainnamevalues are upper-case andNAME@FQDN. shortestPath()returns one route;allShortestPaths()returns every minimum-length route. Use a bounded depth ([*1..6]) on big graphs to avoid runaway queries.- Common edges to query:
MemberOf,AdminTo,HasSession,CanRDP,CanPSRemote,ExecuteDCOM,GenericAll,GenericWrite,WriteDacl,WriteOwner,Owns,ForceChangePassword,AddMember,AddSelf,WriteSPN,AddKeyCredentialLink,AllowedToDelegate,AllowedToAct,AddAllowedToAct,ReadLAPSPassword,ReadGMSAPassword,DCSync,GetChanges,GetChangesAll,SQLAdmin,HasSIDHistory,Contains,GPLink,TrustedBy. - Useful node properties:
owned,highvalue,admincount,hasspn,dontreqpreauth,unconstraineddelegation,enabled,pwdlastset,lastlogon,operatingsystem,description.
Saving queries. In BloodHound CE, open the Cypher tab in the search panel, paste a query, run it, and click Save — saved queries live in the app's database (not a file). In legacy BloodHound, custom queries persist in
~/.config/bloodhound/customqueries.json(Linux) /%USERPROFILE%\AppData\Roaming\bloodhound\customqueries.json(Windows).
OPSEC.
Session/LoggedOncollection touches every host and is noisy. PreferDCOnlyfor a quiet first pass, mark what you own, then collect sessions selectively. Off-host collection withbloodhound-python/nxcavoids dropping a binary on the target entirely.
Related
Active Directory Attack Paths: Foothold to Domain Admin
The reliable AD kill chain — foothold to Domain Admin — with verified impacket / NetExec / Certipy / bloodyAD commands, Windows (Rubeus) equivalents, BloodHound path-finding, ACL abuse, DCSync, and golden-ticket persistence.
Kerberoasting: From a Standard Domain User to Cracked Service Accounts
A hands-on, end-to-end guide to Kerberoasting — how it works, every command (enumerate, request, extract, crack), targeted Kerberoasting against users with no SPN, plus the encryption-downgrade trick, detection, and hardening.