← Back to writing
Tools & Cheatsheets

BloodHound & SharpHound Guide

Feb 10, 2025
4 min read
lawbyte

BloodHound visualizes Active Directory as a graph, revealing attack paths that are otherwise invisible. One Cypher query can expose a path from a compromised help desk account to Domain Admin.

Installation

# BloodHound CE (Community Edition — Docker)
git clone https://github.com/SpecterOps/BloodHound.git
cd BloodHound/examples/docker-compose/
docker compose up -d

# Access at http://localhost:8080
# Default creds: admin / (generated on first run, check docker logs)

# Legacy BloodHound (pre-CE)
# Requires: neo4j + BloodHound binary
sudo apt install neo4j
neo4j start
# Download BloodHound from: https://github.com/BloodHoundAD/BloodHound/releases
./BloodHound --no-sandbox

SharpHound — Data Collection

From Windows (on-target)

# Download
iex (New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/BloodHoundAD/SharpHound/main/SharpHound.ps1')

# Or use binary
.\SharpHound.exe --help

# Collect all data types
.\SharpHound.exe -c All

# Collect specific methods
.\SharpHound.exe -c DCOnly # DC data only (faster)
.\SharpHound.exe -c Session # active sessions
.\SharpHound.exe -c ACL # ACL/permissions
.\SharpHound.exe -c Trusts # domain trusts
.\SharpHound.exe -c Group # group membership
.\SharpHound.exe -c LocalAdmin # local admin rights

# Stealth options
.\SharpHound.exe -c All --stealth # avoid noisy LDAP queries
.\SharpHound.exe -c All --ExcludeDomainControllers

# Output options
.\SharpHound.exe -c All --outputdirectory C:\temp --zipfilename output.zip
.\SharpHound.exe -c All --randomfilenames # randomize output file names

# Loop collection (catch transient sessions)
.\SharpHound.exe -c Session --loop --loopduration 02:00:00 --loopinterval 00:05:00

From Linux (BloodHound.py)

# Install
pip install bloodhound

# Collect with credentials
bloodhound-python -u user -p password -ns DC_IP -d domain.local -c all

# With hash (Pass-the-Hash)
bloodhound-python -u user --hashes :NT_HASH -ns DC_IP -d domain.local -c all

# Kerberos auth
KRB5CCNAME=ticket.ccache bloodhound-python -u user -ns DC_IP -d domain.local -c all -k --no-pass

# Specific collection
bloodhound-python -u user -p pass -ns DC_IP -d domain.local -c DCOnly
bloodhound-python -u user -p pass -ns DC_IP -d domain.local -c Group,ACL,Trusts

# Output to folder
bloodhound-python -u user -p pass -ns DC_IP -d domain.local -c all --zip -o /tmp/bh/

Uploading Data

BloodHound → Upload Data button → select ZIP or JSON files
# Or drag-and-drop into the UI

# BloodHound CE API upload
curl -X POST http://localhost:8080/api/v2/file-upload \
-H "Authorization: Bearer TOKEN" \
-F "file=@bloodhound_output.zip"

Built-in Analysis Queries

In BloodHound’s Analysis tab:

# Domain Dominance paths
"Find Shortest Paths to Domain Admins"
"Find Principals with DCSync Rights"
"Find All Paths from Domain Users to Domain Admins"
"Find Computers where Domain Users are Local Admin"

# Kerberos attacks
"Find Kerberoastable Users with Path to DA"
"Find Kerberoastable Members of High Value Groups"
"Find AS-REP Roastable Users (DontReqPreAuth)"

# Delegation
"Find Computers with Unconstrained Delegation"
"Find Computers with Constrained Delegation"
"Find Users with Constrained Delegation"

# Lateral movement
"Find Shortest Paths to Domain Controllers"
"Find Machines where Domain Admins have Active Sessions"
"Find All Paths from Kerberoastable Users to Domain Admin"

Custom Cypher Queries

Open Raw Query bar in BloodHound:

// Find all users with path to DA in <5 hops
MATCH p=shortestPath((u:User)-[*1..5]->(g:Group {name:"DOMAIN ADMINS@DOMAIN.LOCAL"}))
WHERE u.enabled = true
RETURN p

// Find computers where a specific user is local admin
MATCH (u:User {name:"USER@DOMAIN.LOCAL"})-[r:AdminTo]->(c:Computer)
RETURN c.name

// Find all AD groups a user is member of (recursively)
MATCH p=(u:User {name:"USER@DOMAIN.LOCAL"})-[:MemberOf*1..]->(g:Group)
RETURN g.name

// Kerberoastable users with admin rights somewhere
MATCH (u:User {hasspn:true})-[r:AdminTo|MemberOf*1..]->(c:Computer)
WHERE u.enabled = true
RETURN u.name, c.name

// Find users with DCSync rights
MATCH (u)-[:MemberOf*0..]->(g)-[:DCSync]->(d:Domain)
RETURN u.name, d.name

// Find computers with sessions of DA members
MATCH (c:Computer)-[:HasSession]->(u:User)-[:MemberOf*1..]->(g:Group {name:"DOMAIN ADMINS@DOMAIN.LOCAL"})
RETURN c.name, u.name

// Find accounts with Password Never Expires
MATCH (u:User {pwdneverexpires:true, enabled:true})
RETURN u.name, u.description

// Find accounts with empty description (often contain passwords)
MATCH (u:User) WHERE u.description IS NOT NULL AND u.description <> ""
RETURN u.name, u.description

// Shortest path from owned to DA
MATCH p=shortestPath((u:User {owned:true})-[*1..]->(g:Group {name:"DOMAIN ADMINS@DOMAIN.LOCAL"}))
RETURN p

// Find all WriteDACL relationships
MATCH (u)-[r:WriteDacl]->(t)
WHERE u.name <> t.name
RETURN u.name, type(r), t.name

// Accounts with GenericAll on other accounts
MATCH (u)-[:GenericAll]->(t:User)
RETURN u.name, t.name

// Find AS-REP roastable with a path to DA
MATCH p=shortestPath((u:User {dontreqpreauth:true})-[*1..]->(g:Group {name:"DOMAIN ADMINS@DOMAIN.LOCAL"}))
WHERE u.enabled = true
RETURN p

Marking Owned Objects

# In BloodHound UI:
# Right-click any node → Mark as Owned

# Via Cypher (bulk mark)
MATCH (u:User) WHERE u.name IN ["USER1@DOMAIN.LOCAL", "USER2@DOMAIN.LOCAL"]
SET u.owned = true

# Mark compromised computers
MATCH (c:Computer) WHERE c.name IN ["WORKSTATION01.DOMAIN.LOCAL"]
SET c.owned = true

Marking High Value Targets

// Add to high value manually
MATCH (n {name:"TARGET@DOMAIN.LOCAL"})
SET n.highvalue = true

// Remove false positive from high value
MATCH (n:Computer {name:"SOME_SERVER.DOMAIN.LOCAL"})
SET n.highvalue = false

Bloodhound CE — API Usage

# Authenticate
TOKEN=$(curl -s -X POST http://localhost:8080/api/v2/login \
-H "Content-Type: application/json" \
-d '{"login_name":"admin","secret":"password"}' | jq -r '.data.session_token')

# List domains
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v2/domains

# Run custom Cypher
curl -X POST http://localhost:8080/api/v2/graphs/cypher \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"query":"MATCH (u:User {enabled:true}) RETURN u.name LIMIT 20"}'

AzureHound — Azure / Entra ID

# Download AzureHound
wget https://github.com/BloodHoundAD/AzureHound/releases/latest/download/azurehound-linux-amd64.zip
unzip azurehound-linux-amd64.zip

# Collect with service principal
./azurehound -u user@domain.com -p password list --tenant "TENANT_ID" -o output.json

# With client ID and secret
./azurehound -a CLIENT_ID -s CLIENT_SECRET --tenant TENANT_ID list -o azure.json

# With refresh token
./azurehound -r REFRESH_TOKEN --tenant TENANT_ID list -o azure.json

Quick Analysis Workflow

1. Upload SharpHound/bloodhound-python ZIP
2. Analysis → "Find Shortest Paths to Domain Admins"
3. If no path from owned user yet:
- Mark your current user as Owned
- Analysis → "Shortest Path from Owned Principals to Domain Admins"
4. Look for ACL-based edges: WriteDACL, GenericAll, GenericWrite, ForceChangePassword
5. Look for Kerberoastable nodes in the path
6. Use "Find Computers where Domain Users are Local Admin" for quick wins
7. Check BloodHound "Outbound Object Control" on your owned user node

Node Property Reference

// User properties worth checking
u.enabled // account enabled
u.hasspn // kerberoastable
u.dontreqpreauth // AS-REP roastable
u.pwdneverexpires // password never expires
u.lastlogontimestamp // last logon
u.description // may contain passwords
u.admincount // protected by AdminSDHolder

// Computer properties
c.unconstraineddelegation // unconstrained delegation
c.operatingsystem // OS version
c.lastlogontimestamp // last logon

// Group properties
g.admincount // high privilege group

Discussion

Leave a comment · All fields required · No spam

No comments yet. Be the first.