Back to Blog
Note

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.

01 Nov 20256 min read1,282 words
BloodHound Cheat Sheet — Collection & Cypher Queries

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

bash
# 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 password

Then 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.

bash
# 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:7687

Data Collection

SharpHound (Windows, on a domain-joined or runas context)

powershell
# 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
# PowerShell ingestor variant
Import-Module .\SharpHound.ps1
Invoke-BloodHound -CollectionMethod All -OutputDirectory C:\Temp

bloodhound-python (remote, from Linux — no agent on the host)

bash
# 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 --zip

NetExec (LDAP module — quick collection in one command)

bash
nxc ldap dc01.corp.local -u svc -p 'Passw0rd!' --bloodhound --collection All --dns-server 10.10.10.1

AzureHound (Entra ID / Azure)

bash
azurehound -u user@tenant.onmicrosoft.com -p 'Passw0rd!' list --tenant tenant.onmicrosoft.com -o azure.json

Collection 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:

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=true

Basic Path Queries

cypher
// 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 p

Kerberoastable Accounts

cypher
// 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 p

AS-REP Roastable Users

cypher
// 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 p

ACL / Permission Abuse

cypher
// 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

cypher
// 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

cypher
// 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 p

Tier 0 / High-Value Mapping

cypher
// 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.name

Cross-Domain & Trusts

cypher
// 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.name

Cypher Tips & Edge Reference

  • Relationship names are case-sensitive in Neo4j — WriteDacl, not WriteDACL. Group/domain name values are upper-case and NAME@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/LoggedOn collection touches every host and is noisy. Prefer DCOnly for a quiet first pass, mark what you own, then collect sessions selectively. Off-host collection with bloodhound-python/nxc avoids dropping a binary on the target entirely.

#bloodhound#active-directory#cypher#sharphound#neo4j#netexec

Related