Skip to content

Instantly share code, notes, and snippets.

@Esonhugh
Last active April 28, 2025 15:30
Show Gist options
  • Save Esonhugh/00f2f2fbcd4dcfca56661d5d60a3b147 to your computer and use it in GitHub Desktop.
Save Esonhugh/00f2f2fbcd4dcfca56661d5d60a3b147 to your computer and use it in GitHub Desktop.
Bloodhound Kubernetes Configuration

Bloodhound as a service (Kubernetes Deployments/Docker Compose)

Usage

  1. Change password (default password: default_admin_passwords)
  2. Change user (default user: esonhugh)
  3. Change ingress host name to your team server
  4. Deploy it!
  5. kubectl apply -f deployment-service.yaml -f ingress.yaml -f configmap.yaml # -n <change namespace you deploy>
  6. use pipx install git+https://github.com/exploide/bloodhound-cli.git
  7. bhcli auth https://to/your/server
  8. Auth with username + password
  9. bhcli queries ./custom-queries.json to import it
  10. upload collected data: http://<your teamserver - bloodhound server>/ui/administration/file-ingest (support json and zip)

exposure neo4j service

  1. uncomment service part of neo4j node port
  2. change passwords for your neo4j
  3. connect with your k8s neo4j://neo4j:yourpassword@<cluster-ip>:47687

Simple deployment with docker compose/swarm

Use official docker-compose.yml

apiVersion: v1
kind: ConfigMap
metadata:
name: bloodhound-config
data:
NEO4J_AUTH: "neo4j/bloodhoundcommunityedition"
POSTGRES_USER: "bloodhound"
POSTGRES_PASSWORD: "bloodhoundcommunityedition"
bhe_database_connection: "user=bloodhound password=bloodhoundcommunityedition dbname=bloodhound host=127.0.0.1"
bhe_neo4j_connection: "neo4j://neo4j:[email protected]:7687/"
bhe_default_admin_principal_name: "esonhugh"
bhe_default_admin_first_name: "default admin"
bhe_default_admin_last_name: "default admin"
bhe_default_admin_email_address: "[email protected]"
bhe_default_admin_password: "default_admin_passwords"
## Let it don't expire when start
bhe_default_admin_expire_now: "false"
## New version
bhe_disable_cypher_complexity_limit: 'false'
bhe_enable_cypher_mutations: 'false'
bhe_graph_driver: neo4j
bhe_recreate_default_admin: 'false'
[
{
"name": "ALL Path from Domain Users to High Value Targets",
"query": "MATCH (g:Group) WHERE g.name STARTS WITH 'DOMAIN USERS' MATCH (n {highvalue:true}),p=shortestPath((g)-[r*1..]->(n)) return p"
},
{
"name": "Find all active Domain Admin sessions",
"query": "MATCH (n:User)-[:MemberOf]->(g:Group) WHERE g.objectid ENDS WITH '-512' MATCH p = (c:Computer)-[:HasSession]->(n) return p"
},
{
"name": "Find all computers with Unconstrained Delegation",
"query": "MATCH (c:Computer {unconstraineddelegation:true}) return c"
},
{
"name": "Find all computers with unsupported operating systems",
"query": "MATCH (H:Computer) WHERE H.operatingsystem =~ '.*(2000|2003|2008|xp|vista|7|me)*.' RETURN H"
},
{
"name": "Find all Kerberoastable Users",
"query": "MATCH (n:User)WHERE n.hasspn=true RETURN n"
},
{
"name": "Find all the unconstrained delegation systems that are not part of the domain controllers group",
"query": "MATCH (dc:Computer)-[:MemberOf*1..]->(g:Group) WHERE g.objectsid ENDS WITH \"516\" WITH COLLECT(dc) as domainControllers MATCH p = (d:Domain)-[:Contains*1..]->(c:Computer {unconstraineddelegation:true}) WHERE NOT c in domainControllers RETURN COUNT(p)"
},
{
"name": "Find all users a part of the VPN group",
"query": "Match p=(u:User)-[:MemberOf]->(g:Group) WHERE toUPPER (g.name) CONTAINS 'VPN' return p"
},
{
"name": "Find all users that have local admin rights",
"query": "MATCH p=(m:User)-[r:AdminTo]->(n:Computer) RETURN p"
},
{
"name": "Find All Users with an SPN/Find all Kerberoastable Users with passwords last set > 5 years ago",
"query": "MATCH (u:User) WHERE u.hasspn=true AND u.pwdlastset < (datetime().epochseconds - (1825 * 86400)) and NOT u.pwdlastset IN [-1.0, 0.0] RETURN u"
},
{
"name": "Find All Users with an SPN/Find all Kerberoastable Users with passwords last set less than 5 years ago",
"query": "MATCH (u:User) WHERE u.hasspn=true AND u.pwdlastset < (datetime().epochseconds - (1825 * 86400)) AND NOT u.pwdlastset IN [-1.0, 0.0] RETURN u.name, u.pwdlastset order by u.pwdlastset "
},
{
"name": "Find computers that allow unconstrained delegation that AREN\u2019T domain controllers.",
"query": "MATCH (c1:Computer)-[:MemberOf*1..]->(g:Group) WHERE g.objectid ENDS WITH '-516' WITH COLLECT(c1.name) AS domainControllers MATCH (c2:Computer {unconstraineddelegation:true}) WHERE NOT c2.name IN domainControllers RETURN c2"
},
{
"name": "Find computers with constrained delegation permissions and the corresponding targets where they allowed to delegate",
"query": "MATCH (c:Computer) WHERE c.allowedtodelegate IS NOT NULL RETURN c"
},
{
"name": "Find constrained delegation",
"query": "MATCH p=(u:User)-[:AllowedToDelegate]->(c:Computer) RETURN p"
},
{
"name": "Find groups that can reset passwords (Warning: Heavy)",
"query": "MATCH p=(m:Group)-[r:ForceChangePassword]->(n:User) RETURN p"
},
{
"name": "Find groups that contain both users and computers",
"query": "MATCH (c:Computer)-[r:MemberOf*1..]->(groupsWithComps:Group) WITH groupsWithComps MATCH (u:User)-[r:MemberOf*1..]->(groupsWithComps) RETURN DISTINCT(groupsWithComps) as groupsWithCompsAndUsers"
},
{
"name": "Find groups that have local admin rights (Warning: Heavy)",
"query": "MATCH p=(m:Group)-[r:AdminTo]->(n:Computer) RETURN p"
},
{
"name": "Find if any domain user has interesting permissions against a GPO (Warning: Heavy)",
"query": "MATCH p=(u:User)-[r:AllExtendedRights|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|GpLink*1..]->(g:GPO) RETURN p"
},
{
"name": "Find if unprivileged users have rights to add members into groups",
"query": "MATCH (n:User {admincount:False}) MATCH p=allShortestPaths((n)-[r:AddMember*1..]->(m:Group)) RETURN p"
},
{
"name": "Find Kerberoastable users and where they are AdminTo",
"query": "OPTIONAL MATCH (u1:User) WHERE u1.hasspn=true OPTIONAL MATCH (u1)-[r:AdminTo]->(c:Computer) RETURN u1"
},
{
"name": "Find Kerberoastable users who are members of high value groups",
"query": "MATCH (u:User)-[r:MemberOf*1..]->(g:Group) WHERE g.highvalue=true AND u.hasspn=true RETURN u"
},
{
"name": "Find Kerberoastable Users with a path to DA",
"query": "MATCH (u:User {hasspn:true}) MATCH (g:Group) WHERE g.objectid ENDS WITH '-512' MATCH p = shortestPath( (u)-[*1..]->(g) ) RETURN p"
},
{
"name": "Find Kerberoastable Users with a path to High Value",
"query": "MATCH (u:User {hasspn:true}),(n {highvalue:true}),p = shortestPath( (u)-[*1..]->(n) ) RETURN p"
},
{
"name": "Find logged in Admins",
"query": "MATCH p=(a:Computer)-[r:HasSession]->(b:User) WITH a,b,r MATCH p=shortestPath((b)-[:AdminTo|MemberOf*1..]->(a)) RETURN p"
},
{
"name": "Find machines Domain Users can RDP into",
"query": "match p=(g:Group)-[:CanRDP]->(c:Computer) where g.objectid ENDS WITH '-513' return p"
},
{
"name": "Find Servers Domain Users can RDP To",
"query": "match p=(g:Group)-[:CanRDP]->(c:Computer) where g.name STARTS WITH 'DOMAIN USERS' AND c.operatingsystem CONTAINS 'Server' return p"
},
{
"name": "Find users that can be AS-REP roasted",
"query": "MATCH (u:User {dontreqpreauth: true}) RETURN u"
},
{
"name": "Find users that have never logged on and account is still active",
"query": "MATCH (n:User) WHERE n.lastlogontimestamp=-1.0 AND n.enabled=TRUE RETURN n "
},
{
"name": "Find users that logged in within the last 90 days",
"query": "MATCH (u:User) WHERE u.lastlogon < (datetime().epochseconds - (90 * 86400)) and NOT u.lastlogon IN [-1.0, 0.0] RETURN u"
},
{
"name": "Find users with passwords last set within the last 90 days",
"query": "MATCH (u:User) WHERE u.pwdlastset < (datetime().epochseconds - (90 * 86400)) and NOT u.pwdlastset IN [-1.0, 0.0] RETURN u"
},
{
"name": "Find what groups can RDP",
"query": "MATCH p=(m:Group)-[r:CanRDP]->(n:Computer) RETURN p"
},
{
"name": "Find what's next",
"query": "MATCH p=shortestPath((c {owned: true})-[*1..3]->(s)) WHERE NOT c = s RETURN p"
},
{
"name": "Groups with Computer and User Objects",
"query": "MATCH (c:Computer)-[r:MemberOf*1..]->(groupsWithComps:Group) WITH groupsWithComps MATCH (u:User)-[r:MemberOf*1..]->(groupsWithComps) RETURN DISTINCT(groupsWithComps) as groupsWithCompsAndUsers"
},
{
"name": "List all owned computers",
"query": "MATCH (m:Computer) WHERE m.owned=TRUE RETURN m"
},
{
"name": "List all owned groups",
"query": "MATCH (m:User) WHERE m.owned=TRUE RETURN m"
},
{
"name": "List all owned users",
"query": "MATCH (m:User) WHERE m.owned=TRUE RETURN m"
},
{
"name": "List the groups of all owned users",
"query": "MATCH (m:User) WHERE m.owned=TRUE WITH m MATCH p=(m)-[:MemberOf*1..]->(n:Group) RETURN p"
},
{
"name": "Non Admin Groups with High Value Privileges",
"query": "MATCH p=(g:Group)-[r:Owns|:WriteDacl|:GenericAll|:WriteOwner|:ExecuteDCOM|:GenericWrite|:AllowedToDelegate|:ForceChangePassword]->(n:Computer) WHERE NOT g.name CONTAINS 'ADMIN' RETURN p"
},
{
"name": " Return the name of every computer in the database where at least one SPN for the computer contains the string 'MSSQL'",
"query": "MATCH (c:Computer) WHERE ANY (x IN c.serviceprincipalnames WHERE toUpper(x) CONTAINS 'MSSQL') RETURN c"
},
{
"name": "Shortest Path from Domain Users to High Value Targets",
"query": "MATCH (g:Group),(n {highvalue:true}),p=shortestPath((g)-[r*1..]->(n)) WHERE g.name STARTS WITH 'DOMAIN USERS' return p"
},
{
"name": "Show all high value target's groups",
"query": "MATCH p=(n:User)-[r:MemberOf*1..]->(m:Group {highvalue:true}) RETURN p"
},
{
"name": "Show owned Nodes with Groups",
"query": "MATCH (u:User {owned:true}), (g:Group), p=(u)-[:MemberOf]->(g) RETURN p"
},
{
"name": "Top Ten Computers with Most Admins",
"query": "MATCH (n:User),(m:Computer), (n)-[r:AdminTo]->(m) WHERE NOT n.name STARTS WITH 'ANONYMOUS LOGON' AND NOT n.name='' WITH m, count(r) as rel_count order by rel_count desc LIMIT 10 MATCH p=(m)<-[r:AdminTo]-(n) RETURN p"
},
{
"name": "Top Ten Computers with Most Sessions",
"query": "MATCH (n:User),(m:Computer), (n)<-[r:HasSession]-(m) WHERE NOT n.name STARTS WITH 'ANONYMOUS LOGON' AND NOT n.name='' WITH m, count(r) as rel_count order by rel_count desc LIMIT 10 MATCH p=(m)-[r:HasSession]->(n) RETURN n,r,m"
},
{
"name": "Top Ten Users with Most Local Admin Rights",
"query": "MATCH (n:User),(m:Computer), (n)-[r:AdminTo]->(m) WHERE NOT n.name STARTS WITH 'ANONYMOUS LOGON' AND NOT n.name='' WITH n, count(r) as rel_count order by rel_count desc LIMIT 10 MATCH p=(m)<-[r:AdminTo]-(n) RETURN p"
},
{
"name": "Top Ten Users with Most Sessions",
"query": "MATCH (n:User),(m:Computer), (n)<-[r:HasSession]-(m) WHERE NOT n.name STARTS WITH 'ANONYMOUS LOGON' AND NOT n.name='' WITH n, count(r) as rel_count order by rel_count desc LIMIT 10 MATCH p=(m)-[r:HasSession]->(n) RETURN p"
},
{
"name": "View all GPOs",
"query": "Match (n:GPO) RETURN n"
},
{
"name": "View all groups that contain the word 'admin'",
"query": "Match (n:Group) WHERE n.name CONTAINS 'ADMIN' RETURN n"
}
]
apiVersion: apps/v1
kind: Deployment
metadata:
name: bloodhound
spec:
replicas: 1
selector:
matchLabels:
app: bloodhound
template:
metadata:
labels:
app: bloodhound
spec:
containers:
- name: graph-db
image: docker.io/library/neo4j:4.4
resources: {}
env:
- name: NEO4J_AUTH
valueFrom:
configMapKeyRef:
name: bloodhound-config
key: NEO4J_AUTH
- name: NEO4J_dbms_allow_upgrade
value: "true"
- name: app-db
ports:
- containerPort: 5432
image: docker.io/library/postgres:13.2
imagePullPolicy: Always
resources: {}
env:
- name: POSTGRES_USER
valueFrom:
configMapKeyRef:
name: bloodhound-config
key: POSTGRES_USER
- name: POSTGRES_PASSWORD
valueFrom:
configMapKeyRef:
name: bloodhound-config
key: POSTGRES_PASSWORD
- name: POSTGRES_DATABASE
value: bloodhound
- name: bloodhound
image: docker.io/specterops/bloodhound
imagePullPolicy: Always
resources: {}
ports:
- containerPort: 8080
env:
- name: bhe_disable_cypher_qc
value: "false"
envFrom:
- configMapRef:
name: bloodhound-config
volumes:
- name: bloodhound-config
configMap:
name: bloodhound-config
---
apiVersion: v1
kind: Service
metadata:
name: bloodhound-svc
spec:
selector:
app: bloodhound
ports:
- protocol: TCP
port: 8080
targetPort: 8080
type: ClusterIP
####
# # Uncommented following if you just need neo4j service
# # Warning: change your neo4j user and password in configmap!
#apiVersion: v1
#kind: Service
#metadata:
# name: bloodhound-neo4j
#spec:
# selector:
# app: bloodhound
# ports:
# - protocol: TCP
# port: 7687
# targetPort: 7687
# nodePort: 47687
# type: nodePort
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: bloodhound-ingress
spec:
rules:
- host: bloodhound.local
http:
paths:
- pathType: Prefix
path: /
backend:
service:
name: bloodhound-svc
port:
number: 8080
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment