Skip to content

Instantly share code, notes, and snippets.

@Esonhugh
Last active June 14, 2024 09:48
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

version: '3'
services:
  app-db:
    image: docker.io/library/postgres:13.2
    environment:
      - POSTGRES_USER=${POSTGRES_USER:-bloodhound}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-bloodhoundcommunityedition}
      - POSTGRES_DB=${POSTGRES_DB:-bloodhound}
    # Database ports are disabled by default. Please change your database password to something secure before uncommenting
    # ports:
    #   - 127.0.0.1:${POSTGRES_PORT:-5432}:5432
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test:
        [
          "CMD-SHELL",
          "pg_isready -U ${POSTGRES_USER:-bloodhound} -d ${POSTGRES_DB:-bloodhound} -h 127.0.0.1 -p ${POSTGRES_PORT:-5432}"
        ]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  graph-db:
    image: docker.io/library/neo4j:4.4
    environment:
      - NEO4J_AUTH=${NEO4J_USER:-neo4j}/${NEO4J_SECRET:-bloodhoundcommunityedition}
      - NEO4J_dbms_allow__upgrade=${NEO4J_ALLOW_UPGRADE:-true}
    # Database ports are disabled by default. Please change your database password to something secure before uncommenting
    ports:
      - 127.0.0.1:${NEO4J_DB_PORT:-7687}:7687
      - 127.0.0.1:${NEO4J_WEB_PORT:-7474}:7474
    volumes:
      - ${NEO4J_DATA_MOUNT:-neo4j-data}:/data
    healthcheck:
      test:
        [
          "CMD-SHELL",
          "wget -O /dev/null -q http://localhost:${NEO4J_WEB_PORT:-7474} || exit 1"
        ]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  bloodhound:
    image: docker.io/specterops/bloodhound:${BLOODHOUND_TAG:-latest}
    environment:
      - bhe_disable_cypher_qc=${bhe_disable_cypher_qc:-false}
      - bhe_database_connection=user=${POSTGRES_USER:-bloodhound} password=${POSTGRES_PASSWORD:-bloodhoundcommunityedition} dbname=${POSTGRES_DB:-bloodhound} host=app-db
      - bhe_neo4j_connection=neo4j://${NEO4J_USER:-neo4j}:${NEO4J_SECRET:-bloodhoundcommunityedition}@graph-db:7687/
  
      - bhe_default_admin_first_name="default admin"
      - bhe_default_admin_last_name="default admin"
      - bhe_default_admin_email_address="anti-spam@eson.ninja" 
      - bhe_default_admin_password="default_admin_passwords" # Change it
      - bhe_default_admin_principal_name="esonhugh" # Change it
  
      ### Add additional environment variables you wish to use here.
      ### For common configuration options that you might want to use environment variables for, see `.env.example`
      ### example: bhe_database_connection=${bhe_database_connection}
      ### The left side is the environment variable you're setting for bloodhound, the variable on the right in `${}`
      ### is the variable available outside of Docker
    ports:
      ### Default to localhost to prevent accidental publishing of the service to your outer networks
      ### These can be modified by your .env file or by setting the environment variables in your Docker host OS
      - ${BLOODHOUND_HOST:-127.0.0.1}:${BLOODHOUND_PORT:-8080}:8080
    ### Uncomment to use your own bloodhound.config.json to configure the application
    # volumes:
    #   - ./bloodhound.config.json:/bloodhound.config.json:ro
    depends_on:
      app-db:
        condition: service_healthy
      graph-db:
        condition: service_healthy

volumes:
  neo4j-data:
  postgres-data:
  
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:bloodhoundcommunityedition@127.0.0.1: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: "anti-spam@eson.ninja"
bhe_default_admin_password: "default_admin_passwords"
[
{
"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