BloodHound Analysis¶
BloodHound & Cypher Queries Cheatsheet¶
Installation & Setup¶
BloodHound Installation¶
# Install Neo4j (for old BloodHound)
wget -O - https://debian.neo4j.com/neotechnology.gpg.key | sudo apt-key add -
echo 'deb https://debian.neo4j.com stable 4.0' | sudo tee /etc/apt/sources.list.d/neo4j.list
sudo apt update
sudo apt install neo4j
# Download BloodHound
wget https://github.com/BloodHoundAD/BloodHound/releases/download/4.3.1/BloodHound-linux-x64.zip
unzip BloodHound-linux-x64.zip
# Start Neo4j
sudo neo4j console
# Default creds: neo4j:neo4j (will force password change)
# Run BloodHound
./BloodHound --no-sandbox
BloodHound Community Edition (CE) Installation¶
# Using Docker
curl -L https://ghst.ly/bloodhoundcommunityedition | docker compose -f - up
# Access at https://localhost:8080
# Default creds: admin@bloodhound / admin (change on first login)
Data Collection¶
SharpHound.exe (Windows)¶
# All collection methods
.\SharpHound.exe -c All
# Specific collection methods
.\SharpHound.exe -c DCOnly
.\SharpHound.exe -c Session
.\SharpHound.exe -c LoggedOn
.\SharpHound.exe -c ObjectProps
.\SharpHound.exe -c ACL
.\SharpHound.exe -c Trusts
.\SharpHound.exe -c Container
.\SharpHound.exe -c Group
.\SharpHound.exe -c GPOLocalGroup
.\SharpHound.exe -c PSRemote
.\SharpHound.exe -c DCOM
.\SharpHound.exe -c RDP
.\SharpHound.exe -c LocalAdmin
# Stealth options
.\SharpHound.exe -c All --stealth
.\SharpHound.exe -c All --throttle 100 --jitter 15
# Loop collection (for sessions)
.\SharpHound.exe -c Session --loop --loopduration 2:00:00
# Exclude DCs from enumeration
.\SharpHound.exe -c All --excludedc
# Output options
.\SharpHound.exe -c All --outputdirectory C:\temp --zipfilename data.zip
# Domain specification
.\SharpHound.exe -c All -d domain.local
# LDAP options
.\SharpHound.exe -c All --ldapusername user --ldappassword pass
SharpHound.ps1 (PowerShell)¶
# Import module
Import-Module .\SharpHound.ps1
# Invoke collection
Invoke-BloodHound -CollectionMethod All
# With specific methods
Invoke-BloodHound -CollectionMethod DCOnly,Session,ACL
# Stealth and timing
Invoke-BloodHound -CollectionMethod All -Stealth -Throttle 100 -Jitter 15
# Loop for sessions
Invoke-BloodHound -CollectionMethod Session -Loop -LoopDuration 02:00:00 -LoopDelay 00:05:00
# Specify output
Invoke-BloodHound -CollectionMethod All -OutputDirectory C:\temp -ZipFileName bloodhound.zip
bloodhound.py (Linux)¶
# Install
pip install bloodhound
# Basic collection with password
bloodhound-python -d domain.local -u username -p password -c all -ns $dc_ip
# Collection with hash
bloodhound-python -d domain.local -u username --hashes :$ntlm_hash -c all -ns $dc_ip
# With Kerberos
bloodhound-python -d domain.local -u username -k -c all -ns $dc_ip
# Specific collection methods
bloodhound-python -d domain.local -u username -p password -c group,localadmin,session,trusts -ns $dc_ip
# With DNS TCP
bloodhound-python -d domain.local -u username -p password -c all -ns $dc_ip --dns-tcp
# Disable autogc (garbage collection)
bloodhound-python -d domain.local -u username -p password -c all -ns $dc_ip --disable-autogc
# With zip output
bloodhound-python -d domain.local -u username -p password -c all -ns $dc_ip --zip
AzureHound (Azure/AzureAD)¶
# Collection for Azure
azurehound -u "user@tenant.onmicrosoft.com" -p "password" list --tenant "tenant.onmicrosoft.com" -o output.json
# With refresh token
azurehound refresh-token -r $refresh_token list -o output.json
# Specific collections
azurehound -u "user@tenant.onmicrosoft.com" -p "password" list users -o users.json
azurehound -u "user@tenant.onmicrosoft.com" -p "password" list groups -o groups.json
Neo4j Cypher Queries (Old BloodHound)¶
User Queries¶
Find All Domain Admins
MATCH (n:Group) WHERE n.name =~ '(?i).*domain admins.*' WITH n MATCH (n)<-[r:MemberOf*1..]-(m) RETURN m
Find Specific User
MATCH (n:User) WHERE n.name =~ '(?i)username@domain.local' RETURN n
Show All Properties of a User
MATCH (n:User) WHERE n.name =~ '(?i)username@domain.local' RETURN n
Find All Logged On Users
MATCH p=(c:Computer)-[:HasSession]->(u:User) RETURN p
Find Users with DCSync Rights
MATCH p=(u:User)-[:DCSync]->(d:Domain) RETURN p
Find Kerberoastable Users
MATCH (u:User) WHERE u.hasspn=true RETURN u
Find ASREPRoastable Users
MATCH (u:User) WHERE u.dontreqpreauth=true RETURN u
Find Users with Password Never Expires
MATCH (u:User) WHERE u.pwdneverexpires=true RETURN u
Find Users with Password Not Required
MATCH (u:User) WHERE u.passwordnotreqd=true RETURN u
Find Enabled Users
MATCH (u:User) WHERE u.enabled=true RETURN u
Find Disabled Users
MATCH (u:User) WHERE u.enabled=false RETURN u
Find Admin Users
MATCH (u:User) WHERE u.admincount=true RETURN u
Computer Queries¶
Find All Computers
MATCH (c:Computer) RETURN c
Find Domain Controllers
MATCH (c:Computer)-[:MemberOf*1..]->(g:Group) WHERE g.name =~ '(?i).*domain controllers.*' RETURN c
Find Computers with Unconstrained Delegation
MATCH (c:Computer) WHERE c.unconstraineddelegation=true AND NOT c.name =~ '(?i).*DC.*' RETURN c
Find Computers with Constrained Delegation
MATCH (c:Computer) WHERE c.allowedtodelegate IS NOT NULL RETURN c
Find Computers Allow to Delegate
MATCH (c:Computer) WHERE c.allowedtodelegate IS NOT NULL RETURN c
Find Computers with LAPS
MATCH (c:Computer) WHERE c.haslaps=true RETURN c
Find Computers without LAPS
MATCH (c:Computer) WHERE c.haslaps=false RETURN c
Find Computers with Sessions
MATCH (c:Computer)-[:HasSession]->(u:User) RETURN c,u
Path Queries¶
Shortest Path from User to Domain Admin
MATCH p=shortestPath((u:User {name:'username@domain.local'})-[*1..]->(g:Group {name:'DOMAIN ADMINS@DOMAIN.LOCAL'})) RETURN p
All Paths from User to High Value Targets
MATCH p=allShortestPaths((u:User {name:'username@domain.local'})-[*1..]->(h)) WHERE h.highvalue=true RETURN p
Find Shortest Path to Domain Admin from Any Owned User
MATCH p=shortestPath((u:User)-[*1..]->(g:Group)) WHERE u.owned=true AND g.name =~ '(?i).*domain admins.*' RETURN p
Find All Paths from Owned Users to High Value
MATCH p=allShortestPaths((u:User)-[*1..]->(h)) WHERE u.owned=true AND h.highvalue=true RETURN p
Find Shortest Path from Kerberoastable Users
MATCH p=shortestPath((u:User)-[*1..]->(g:Group)) WHERE u.hasspn=true AND g.name =~ '(?i).*domain admins.*' RETURN p
Path from Specific User to Any Goal
MATCH p=shortestPath((n)-[*1..]->(c)) WHERE n.name =~ '(?i)username@domain.local' AND NOT c=n RETURN p
Group Queries¶
Find All Groups
MATCH (g:Group) RETURN g
Find High Value Groups
MATCH (g:Group) WHERE g.highvalue=true RETURN g
Find Groups with Most Members
MATCH (g:Group)<-[:MemberOf]-(u) RETURN g.name, COUNT(u) ORDER BY COUNT(u) DESC
Find Nested Group Memberships
MATCH p=(u:User)-[:MemberOf*1..5]->(g:Group) WHERE u.name =~ '(?i)username@domain.local' RETURN p
Find Groups that Can RDP
MATCH p=(g:Group)-[:CanRDP]->(c:Computer) RETURN p
Find Groups that Can PSRemote
MATCH p=(g:Group)-[:CanPSRemote]->(c:Computer) RETURN p
Find Groups with Local Admin Rights
MATCH p=(g:Group)-[:AdminTo]->(c:Computer) RETURN p
ACL Queries¶
Find All GenericAll Rights
MATCH p=(u)-[:GenericAll]->(c) RETURN p
Find GenericAll Rights on Domain
MATCH p=(u)-[:GenericAll]->(d:Domain) RETURN p
Find WriteDACL Rights
MATCH p=(u)-[:WriteDacl]->(c) RETURN p
Find WriteOwner Rights
MATCH p=(u)-[:WriteOwner]->(c) RETURN p
Find Users with ForceChangePassword
MATCH p=(u)-[:ForceChangePassword]->(c:User) RETURN p
Find AddMembers Rights
MATCH p=(u)-[:AddMember]->(g:Group) RETURN p
Find GenericWrite Rights
MATCH p=(u)-[:GenericWrite]->(c) RETURN p
Find Owns Relationships
MATCH p=(u)-[:Owns]->(c) RETURN p
Find All ACL Rights from Owned Users
MATCH p=(u:User)-[r]->(n) WHERE u.owned=true AND r.isacl=true RETURN p
Find Objects Controllable by Owned Users
MATCH p=(u:User {owned:true})-[r]->(n) WHERE r.isacl=true RETURN p
GPO Queries¶
Find All GPOs
MATCH (g:GPO) RETURN g
Find GPO Affecting Computers
MATCH p=(g:GPO)-[:GPLink]->(c:Computer) RETURN p
Find GPO Affecting OUs
MATCH p=(g:GPO)-[:GPLink]->(o:OU) RETURN p
Find Users that Can Modify GPOs
MATCH p=(u:User)-[:GenericWrite|WriteProperty|WriteDacl|WriteOwner]->(g:GPO) RETURN p
Computer Admin Queries¶
Find All Local Admins
MATCH p=(u)-[:AdminTo]->(c:Computer) RETURN p
Find Users that are Local Admin on Computers
MATCH p=(u:User)-[:AdminTo]->(c:Computer) RETURN p
Find Groups that are Local Admin on Computers
MATCH p=(g:Group)-[:AdminTo]->(c:Computer) RETURN p
Find Computers where Domain Users are Admin
MATCH p=(g:Group)-[:AdminTo]->(c:Computer) WHERE g.name =~ '(?i).*domain users.*' RETURN p
Find All RDP Rights
MATCH p=(u)-[:CanRDP]->(c:Computer) RETURN p
Find All PSRemote Rights
MATCH p=(u)-[:CanPSRemote]->(c:Computer) RETURN p
Find All DCOM Rights
MATCH p=(u)-[:ExecuteDCOM]->(c:Computer) RETURN p
Find All SQL Admin Rights
MATCH p=(u)-[:SQLAdmin]->(c:Computer) RETURN p
Delegation Queries¶
Find All Delegation Relationships
MATCH (u)-[:AllowedToDelegate]->(c) RETURN u,c
Find Users That Can Be Delegated
MATCH (u:User)-[:AllowedToDelegate]->(c:Computer) RETURN u,c
Find Computers That Allow Unconstrained Delegation
MATCH (c:Computer) WHERE c.unconstraineddelegation=true RETURN c
Find Users/Computers Trusted for Delegation
MATCH (n) WHERE n.trustedtoauth=true RETURN n
Session Queries¶
Find All Sessions
MATCH p=(c:Computer)-[:HasSession]->(u:User) RETURN p
Find Privileged Sessions
MATCH p=(c:Computer)-[:HasSession]->(u:User)-[:MemberOf*1..]->(g:Group) WHERE g.highvalue=true RETURN p
Find Sessions of Domain Admins
MATCH p=(c:Computer)-[:HasSession]->(u:User)-[:MemberOf*1..]->(g:Group) WHERE g.name =~ '(?i).*domain admins.*' RETURN p
Find Computers with Most Sessions
MATCH (c:Computer)-[:HasSession]->(u:User) RETURN c.name, COUNT(u) ORDER BY COUNT(u) DESC
Trust Queries¶
Find All Trusts
MATCH (d:Domain)-[:TrustedBy]->(d2:Domain) RETURN d,d2
Find External Trusts
MATCH (d:Domain)-[r:TrustedBy]->(d2:Domain) WHERE r.trusttype = 'External' RETURN d,d2
Find Transitive Trusts
MATCH (d:Domain)-[r:TrustedBy]->(d2:Domain) WHERE r.transitive=true RETURN d,d2
Special Queries¶
Find All Owned Users
MATCH (u:User) WHERE u.owned=true RETURN u
Find All Owned Computers
MATCH (c:Computer) WHERE c.owned=true RETURN c
Find All High Value Targets
MATCH (h) WHERE h.highvalue=true RETURN h
Find Principals with Most Admin Rights
MATCH (u)-[:AdminTo]->(c:Computer) RETURN u.name, COUNT(c) ORDER BY COUNT(c) DESC
Find Users that Can DCSync
MATCH p=(u:User)-[:DCSync|GetChanges|GetChangesAll]->(d:Domain) RETURN p
Find Objects with Most Outbound Control
MATCH (u)-[r]->(n) WHERE r.isacl=true RETURN u.name, COUNT(n) ORDER BY COUNT(n) DESC
Find Objects with Most Inbound Control
MATCH (u)-[r]->(n) WHERE r.isacl=true RETURN n.name, COUNT(u) ORDER BY COUNT(u) DESC
Find Kerberoastable Users with Admin Rights
MATCH p=(u:User)-[:AdminTo]->(c:Computer) WHERE u.hasspn=true RETURN p
Find ASREP Roastable Users with Admin Rights
MATCH p=(u:User)-[:AdminTo]->(c:Computer) WHERE u.dontreqpreauth=true RETURN p
Find Computers in Specific OU
MATCH p=(c:Computer)-[:Contains*1..]->(o:OU) WHERE o.name =~ '(?i).*servers.*' RETURN p
Find All Certificate Templates
MATCH (ct:CertTemplate) RETURN ct
Find Enrollable Certificate Templates
MATCH p=(u:User)-[:Enroll]->(ct:CertTemplate) RETURN p
Azure Queries (If Azure data imported)¶
Find Azure Admin Roles
MATCH p=(u:AZUser)-[:AZGlobalAdmin|AZPrivilegedRoleAdmin]->(t:AZTenant) RETURN p
Find Azure Apps with Permissions
MATCH p=(a:AZApp)-[r]->(n) RETURN p
Find Azure Service Principals
MATCH (sp:AZServicePrincipal) RETURN sp
Custom/Advanced Queries¶
Find All Paths Less Than X Length
MATCH p=shortestPath((u:User)-[*1..5]->(g:Group)) WHERE u.owned=true AND g.highvalue=true AND length(p) <= 5 RETURN p
Find Cross-Domain Paths
MATCH p=(u:User)-[*1..]->(g:Group) WHERE u.domain <> g.domain RETURN p
Find Users Never Logged In
MATCH (u:User) WHERE NOT (u)-[:HasSession]->(:Computer) RETURN u
Count All Relationships
MATCH ()-[r]->() RETURN type(r), COUNT(r) ORDER BY COUNT(r) DESC
Delete All Owned Markers
MATCH (n) WHERE n.owned=true SET n.owned=false
Mark Node as Owned
MATCH (n) WHERE n.name =~ '(?i)username@domain.local' SET n.owned=true
Mark Node as High Value
MATCH (n) WHERE n.name =~ '(?i)sensitive_server.domain.local' SET n.highvalue=true
Find All Edges from a Node
MATCH (n)-[r]->(m) WHERE n.name =~ '(?i)username@domain.local' RETURN n,r,m
Find All Edges to a Node
MATCH (n)-[r]->(m) WHERE m.name =~ '(?i)domain admins@domain.local' RETURN n,r,m
Export All User Names
MATCH (u:User) RETURN u.name
Export All Computer Names
MATCH (c:Computer) RETURN c.name
Complex Attack Path Query
MATCH p=allShortestPaths((u:User {owned:true})-[*1..]->(g:Group {name:'DOMAIN ADMINS@DOMAIN.LOCAL'}))
WHERE NONE(r in relationships(p) WHERE type(r) = 'HasSession')
AND NONE(n in nodes(p) WHERE n:Group AND n.name =~ '(?i).*denied.*')
RETURN p
BloodHound Tips & Tricks¶
Pre-Built Analytics Queries¶
- Find all Domain Admins
- Find Shortest Paths to Domain Admins
- Find Principals with DCSync Rights
- Users with Foreign Domain Group Membership
- Groups with Foreign Domain Group Membership
- Map Domain Trusts
- Shortest Paths to Unconstrained Delegation Systems
- Shortest Paths from Kerberoastable Users
- Shortest Paths to Domain Admins from Kerberoastable Users
- Shortest Path from Owned Principals
- Shortest Paths to Domain Admins from Owned Principals
- Shortest Paths to High Value Targets
- Find Computers with Unsupported Operating Systems
- Find AS-REP Roastable Users (DontReqPreAuth)
Marking Nodes¶
# Mark as Owned
MATCH (n) WHERE n.name =~ '(?i)username@domain.local' SET n.owned=true RETURN n
# Mark as High Value
MATCH (n) WHERE n.name =~ '(?i)target@domain.local' SET n.highvalue=true RETURN n
# Remove Owned flag
MATCH (n) WHERE n.name =~ '(?i)username@domain.local' SET n.owned=false RETURN n
# Bulk mark owned from file (in Neo4j browser)
LOAD CSV FROM 'file:///owned.csv' AS line
MATCH (n) WHERE n.name = line[0] SET n.owned=true
Performance Tips¶
# Create indexes for better performance
CREATE INDEX ON :User(name)
CREATE INDEX ON :Computer(name)
CREATE INDEX ON :Group(name)
CREATE INDEX ON :Domain(name)
# Check existing indexes
CALL db.indexes()
# Get database statistics
CALL db.stats.retrieve('GRAPH COUNTS')
Export/Import¶
# Export database
sudo neo4j-admin dump --database=neo4j --to=/backup/bloodhound.dump
# Import database
sudo neo4j-admin load --database=neo4j --from=/backup/bloodhound.dump --force
# Export query results to CSV (in Neo4j)
MATCH (u:User) WHERE u.owned=true RETURN u.name
# Then click export to CSV
Important Notes¶
Query Case Sensitivity:
- Use
=~for regex matching - Use
(?i)for case-insensitive matching - Example:
WHERE n.name =~ '(?i).*admin.*'
Common Node Types:
- User
- Computer
- Group
- Domain
- GPO
- OU
- Container
- CertTemplate
- AZUser (Azure)
- AZGroup (Azure)
- AZTenant (Azure)
Common Relationship Types:
- MemberOf
- AdminTo
- HasSession
- TrustedBy
- Contains
- GPLink
- CanRDP
- CanPSRemote
- ExecuteDCOM
- AllowedToDelegate
- ForceChangePassword
- GenericAll
- GenericWrite
- Owns
- WriteDacl
- WriteOwner
- ReadLAPSPassword
- ReadGMSAPassword
- AddMember
- DCSync
Edge Properties:
- isacl: Boolean indicating if edge is an ACL
- isinhertied: Boolean for inherited ACLs
- transitive: Boolean for trust transitivity
Node Properties:
- name: Full object name
- domain: Domain name
- enabled: Account status
- highvalue: High value target flag
- owned: Compromised flag
- hasspn: Has SPN (Kerberoastable)
- dontreqpreauth: ASREP roastable
- unconstraineddelegation: Unconstrained delegation
- trustedtoauth: Trusted for delegation
- admincount: Protected admin object
- description: Object description