BloodHound CE for Defensive AD Analysis: Attack Path Management | Tağmaç - root@Tagoletta:~#
Contents

BloodHound CE for Defensive AD Analysis: Attack Path Management

Mon Jun 29 2026 · 19 min read

Category: Security Research

# Active-Directory# BloodHound# Blue-Team# Defense# Windows

Introduction: Using the Attacker's Map for Defense

When Andy Robbins, Rohan Vazarkar, and Will Schroeder unveiled BloodHound at DEF CON 2016, the audience reaction was mixed: red teamers were elated, while many system administrators struggled to grasp how dangerous it truly was. BloodHound modeled Active Directory relationships in a graph database and calculated the shortest attack paths across that graph. An attacker with any standard domain user account could automatically find the path to Domain Admin.

Today the picture is different. SpecterOps transformed BloodHound into the foundation of Attack Path Management (APM): no longer just a reconnaissance weapon for attackers, but a defensive platform that blue teams use to continuously measure their AD security posture. BloodHound Community Edition (CE) is the free, open-source implementation of this next-generation approach.

In this post we examine BloodHound CE from a strictly defensive perspective — from installation and collection methodology to tier model implementation, custom Cypher queries, attack path prioritization, and systematic remediation workflows. The goal is to see your Active Directory environment through an attacker's eyes and close critical security gaps before they are exploited.

BloodHound CE vs. Legacy BloodHound: What Changed?

BloodHound has evolved into three distinct products. Understanding the differences is essential for choosing the right tool.

Legacy BloodHound (v4 and earlier)

The original BloodHound was an Electron-based desktop application. Single-user, with Neo4j running locally. Installation was painful, especially on non-Windows platforms. While still functional for certain use cases, it is no longer recommended for enterprise environments.

BloodHound CE (Community Edition — v5+)

Modern BloodHound was completely rewritten. It ships with a React-based web UI, services that spin up in seconds via Docker Compose, multi-user access with RBAC, a REST API, and two database layers (PostgreSQL + Neo4j). One command to install, access from any browser. BloodHound CE is distributed on GitHub under the Apache 2.0 license.

BloodHound Enterprise (BHE)

SpecterOps's commercial product. Everything in CE plus continuous Attack Path Management, automated reporting, tenant-based multi-forest support, Tier Zero asset management, and SIEM integrations. Designed for large enterprises; pricing is license-based.

SharpHound vs BloodHound.py: The Collectors

  • SharpHound (.NET): The official collector. Runs from Windows, does not require a domain-joined machine (can be used remotely), produces the most comprehensive dataset. Supports all collection methods: ACL, Session, LoggedOn, ObjectProps, Trusts, Container, CertServices, GPOLocalGroup.
  • BloodHound.py (Python): Community-supported, runs on Linux/macOS. Requires only network access and valid domain credentials — no domain-joined machine needed. Works over LDAP+Kerberos. Some collection methods are absent or slower than SharpHound.

BloodHound CE Installation

Docker Compose is the fastest path to BloodHound CE. In production environments, use a dedicated analysis machine (jump host or secure workstation).

# Single-command installation — requires Docker and Docker Compose
curl -L https://ghst.ly/getbhce | docker compose -f - up

# Run in background
curl -L https://ghst.ly/getbhce | docker compose -f - up -d

# Default URL: http://localhost:8080
# Initial login password shown in Docker logs:
docker logs bloodhound-ce_bloodhound_1 2>&1 | grep "Initial Password"

After installation, BloodHound CE runs three containers:

  • bloodhound: Main application (Go backend + React frontend)
  • graph-db: Neo4j 4.4 (Cypher query engine)
  • app-db: PostgreSQL (user management, application state)

On first login, change your password and create a reporting service account or API token.

Secure Deployment Notes:

# In production, bind to localhost only instead of 0.0.0.0
# In docker-compose.yml:
# ports:
#   - "127.0.0.1:8080:8080"

# For TLS, place an nginx reverse proxy in front
# Keep the collection endpoint behind VPN during data ingestion

Data Collection with SharpHound

Collection is the foundation of all analysis. Understanding what SharpHound collects and how it operates is critical both for correct analysis and for evaluating the network noise the collection creates.

Core Collection Methods:

# Collect all data (most comprehensive, most noisy)
.\SharpHound.exe -c All --zipfilename corp_bhdata.zip

# Collect from DCs only — much less noisy, faster
.\SharpHound.exe -c DCOnly

# ACL and group data only (no session data)
.\SharpHound.exe -c ACL,Group,ObjectProps,Container

# Session data — which user is logged on to which machine
# Requires local admin rights on targets (over SMB)
.\SharpHound.exe -c Session,LoggedOn --computermaxconn 5

# Target a specific OU (partitioned collection for large environments)
.\SharpHound.exe -c All --searchbase "OU=Workstations,DC=corp,DC=local"

# ADCS data (certificate templates, CAs)
.\SharpHound.exe -c CertServices

# Remote domain collection (from a non-domain-joined machine)
.\SharpHound.exe -c All -d corp.local --ldapusername jdoe --ldappassword 'P@ssw0rd'
# Remote collection from Linux (BloodHound.py)
pip3 install bloodhound
python3 bloodhound.py \
  -u jdoe \
  -p 'Password123' \
  -d corp.local \
  -dc dc01.corp.local \
  -c all \
  --zip \
  --dns-tcp

# With Kerberos authentication (Pass-the-Hash or PKINIT)
python3 bloodhound.py \
  -u jdoe@corp.local \
  --hashes :aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0 \
  -d corp.local \
  -dc dc01.corp.local \
  -c all \
  --zip

Collection Security and Noise Management:

BloodHound collection leaves traces in the domain environment. As a blue team operator running authorized collection, define a maintenance window:

  • Session collection: Opens SMB connections to each target. Can be detected by IDS/IPS.
  • LoggedOn collection: Requires Remote Registry service — won't work on systems that haven't enabled it.
  • ACL collection: Works over LDAP, nearly silent. Preferred starting point.
  • Create a dedicated service account for collection: svc_bloodhound. Minimum rights: domain user + RemoteRegistry read.

Tier Model: Credential Isolation Architecture

However rich the BloodHound data, it is meaningless without context. That context is the Tier Model — a credential isolation architecture based on Microsoft's ESAE (Enhanced Security Admin Environment) and PAW (Privileged Access Workstations) framework.

Tier 0 — Control Plane

Tier 0 encompasses assets that control the identity plane. If this tier is compromised, every digital asset in the organization is at risk.

Tier 0 assets:

  • Domain Controllers (all DCs, including RODCs)
  • Enterprise CA servers (ADCS PKI infrastructure)
  • ADFS servers (federation and token signing)
  • Azure AD Connect / AAD Sync (identity sync, includes hash sync)
  • Backup systems managing Tier 0 (Veeam, Backup Exec — AD-aware)
  • Domain Admins, Enterprise Admins, Schema Admins groups
  • Tier 0 PAWs (privileged access workstations used to manage these assets)

Tier 1 — Server Tier

Enterprise applications, databases, and infrastructure services. Tier 0 credentials are never used here.

Tier 1 assets:

  • Application servers (IIS, Tomcat, JBoss)
  • Database servers (SQL Server, Oracle)
  • File servers (DFS, SMB shares)
  • Exchange / mail servers (Exchange WriteDACL risk is particularly notable)
  • SCCM / Intune management servers
  • Tier 1 service accounts

Tier 2 — User Tier

The tier most exposed to the internet; primary target for phishing and initial-access attacks.

Tier 2 assets:

  • User workstations and laptops
  • Virtual desktops (VDI)
  • Helpdesk accounts (must be kept within Tier 2 scope)
  • Standard domain users

Detecting Tier Violations with BloodHound:

Before implementing the tier model you must detect existing violations. BloodHound is extremely valuable here:

// Any path from a Tier 2 user to a Tier 0 asset
MATCH p=shortestPath(
  (u:User {enabled:true})
  -[*1..8]->
  (t:Computer)
)
WHERE t.name IN ['DC01.CORP.LOCAL', 'CA01.CORP.LOCAL', 'ADFSRV01.CORP.LOCAL']
AND NOT u.name STARTS WITH 'CORP\\DA-'
RETURN u.name, t.name, length(p) as Hops
ORDER BY Hops ASC

// Is a service account a member of Tier 0 groups?
MATCH (u:User)-[:MemberOf*1..]->(g:Group)
WHERE g.name =~ '(?i).*(domain admins|enterprise admins|schema admins).*'
AND u.name =~ '(?i).*(svc_|service_|sa_).*'
RETURN u.name, g.name

Critical Attack Path Categories

BloodHound excels at identifying several critical attack path categories. Here we examine each one — how to detect it and how to remediate it.

1. Kerberoasting Paths

Kerberoasting exploits accounts with registered SPNs (Service Principal Names) by requesting and offline-cracking their TGS tickets. Queried in BloodHound via the hasspn:true property.

Risk factors:

  • Account has admincount:true — direct Domain Admin path
  • Password last changed date is old — higher probability of weak password
  • RC4 encryption is supported — cracked very quickly by modern GPUs
  • Account last logon date is old — orphaned account
// Kerberoastable accounts and their distance to DA
MATCH (u:User {hasspn:true, enabled:true})
OPTIONAL MATCH p=shortestPath(
  (u)-[*1..10]->
  (g:Group {name:"DOMAIN ADMINS@CORP.LOCAL"})
)
RETURN u.name,
       u.serviceprincipalnames,
       u.pwdlastset,
       u.admincount,
       length(p) as HopsToDA
ORDER BY HopsToDA ASC NULLS LAST

// High-value Kerberoastable accounts (on path to DA)
MATCH (u:User {hasspn:true, enabled:true, admincount:true})
RETURN u.name,
       u.serviceprincipalnames,
       datetime({epochSeconds: toInteger(u.pwdlastset)}) as PasswordLastSet

Remediation priorities:

  • All Kerberoastable admin accounts → replace with gMSA (Group Managed Service Account)
  • If gMSA is not possible: enforce AES256 encryption, 25+ character password, 180-day rotation
  • Add account to Protected Users group (removes RC4, shortens TGS lifetime)

2. AS-REP Roasting

Accounts with Kerberos pre-authentication disabled can receive an encrypted response without any authentication — that response can be cracked offline.

// AS-REP Roastable accounts and impact analysis
MATCH (u:User {dontreqpreauth:true, enabled:true})
OPTIONAL MATCH p=shortestPath(
  (u)-[*1..10]->
  (g:Group {name:"DOMAIN ADMINS@CORP.LOCAL"})
)
RETURN u.name,
       u.admincount,
       u.description,
       length(p) as HopsToDA
ORDER BY u.admincount DESC, HopsToDA ASC

// AS-REP Roastable + active in last 90 days
MATCH (u:User {dontreqpreauth:true, enabled:true})
WHERE u.lastlogontimestamp > (timestamp()/1000 - 7776000)
RETURN u.name, u.lastlogontimestamp, u.admincount

Remediation: Enable Kerberos pre-authentication for all accounts. This option is typically disabled to support older UNIX/Linux systems — modern Kerberos clients support it.

3. Unconstrained Delegation

When any user connects to a computer configured with unconstrained delegation, that user's TGT is stored in the computer's memory (LSASS). If an attacker compromises this computer and can coerce a DC to connect to it (via PrintSpooler/MS-RPRN, PetitPotam, etc.), they can steal the DC's TGT — equivalent to full domain compromise.

// Unconstrained delegation computers (excluding DCs)
MATCH (c:Computer {unconstraineddelegation:true, enabled:true})
WHERE NOT c.name CONTAINS 'DC'
RETURN c.name,
       c.operatingsystem,
       c.lastlogontimestamp,
       c.haslaps
ORDER BY c.name

// Users with local admin on unconstrained delegation computers
MATCH (u:User)-[:AdminTo]->(c:Computer {unconstraineddelegation:true, enabled:true})
WHERE NOT c.name CONTAINS 'DC'
RETURN u.name, c.name, u.enabled

Remediation options:

  1. Completely disable unconstrained delegation
  2. Replace with Resource-Based Constrained Delegation (RBCD)
  3. Add the computer to Protected Users group (delegation blocked)
  4. Set "Account is sensitive and cannot be delegated" flag (per-account basis)
  5. Disable PrintSpooler service on DCs (blocks coercion vector)

4. DCSync Rights

DCSync replicates the entire domain's hash database by impersonating a Domain Controller. An account needs specific AD replication permissions (DS-Replication-Get-Changes-All) to do this. This permission should exist only on DC computer accounts and specific service accounts (MSOL_, AZURE_).

// All principals with DCSync rights
MATCH p=(n)-[:DCSync]->(d:Domain)
RETURN n.name, n.objectid, labels(n) as NodeType

// Exclude DC computers — these are legitimate
MATCH p=(n)-[:DCSync]->(d:Domain)
WHERE NOT (n:Computer AND (
  n.name CONTAINS 'DC'
  OR n.name CONTAINS 'DOMAINCTRL'
))
RETURN n.name, labels(n) as NodeType, n.objectid

// DCSync accounts that are not Domain Admins
MATCH (n)-[:DCSync]->(d:Domain)
WHERE NOT EXISTS {
  MATCH (n)-[:MemberOf*..]->(g:Group {name:"DOMAIN ADMINS@CORP.LOCAL"})
}
RETURN n.name, labels(n)

Remediation: No accounts other than Azure AD Connect and ADFS service accounts should have DCSync rights. For unauthorized DCSync rights, remove immediately and review the last 30 days of NTDS audit logs for potential abuse.

5. Computers Missing LAPS

LAPS (Local Administrator Password Solution) automatically rotates each workstation's local Administrator password and stores it encrypted in AD. Without LAPS, all workstations may share the same local admin password — compromising one machine provides pivot capability across the entire network.

// Active computers missing LAPS
MATCH (c:Computer {enabled:true})
WHERE c.haslaps = false
RETURN c.name,
       c.operatingsystem,
       c.lastlogontimestamp
ORDER BY c.lastlogontimestamp DESC NULLS LAST

// LAPS coverage percentage
MATCH (c:Computer {enabled:true})
WITH count(c) as total
MATCH (c:Computer {enabled:true, haslaps:true})
RETURN count(c) as WithLAPS, total, (count(c)*100/total) as Percentage

// LAPS status by operating system
MATCH (c:Computer {enabled:true})
RETURN c.operatingsystem,
       count(c) as Total,
       sum(CASE WHEN c.haslaps THEN 1 ELSE 0 END) as WithLAPS
ORDER BY c.operatingsystem

Defensive Cypher Query Library

Below is the complete set of eight core query categories that blue teams should regularly run in BloodHound CE.

Query 1: Shortest Paths to Domain Admin

// All enabled users to DA — sorted by path length (top 20)
MATCH p=shortestPath(
  (u:User {enabled:true})
  -[*1..10]->
  (g:Group {name:"DOMAIN ADMINS@CORP.LOCAL"})
)
WHERE NOT u.name STARTS WITH 'CORP\\DA-'
RETURN u.name, length(p) as PathLength
ORDER BY PathLength ASC
LIMIT 20

// Path length distribution (how many users at each hop count)
MATCH p=shortestPath(
  (u:User {enabled:true})
  -[*1..10]->
  (g:Group {name:"DOMAIN ADMINS@CORP.LOCAL"})
)
WITH length(p) as hops, count(u) as UserCount
RETURN hops, UserCount
ORDER BY hops ASC

Query 2: DCSync Rights

// Full DCSync rights picture
MATCH p=(n)-[:DCSync]->(d:Domain)
WHERE NOT (n:Computer AND n.name CONTAINS 'DC')
RETURN n.name,
       n.objectid,
       labels(n) as NodeType,
       d.name as Domain
ORDER BY n.name

Query 3: Unconstrained Delegation Computers

// Unconstrained delegation + LAPS status + last seen
MATCH (c:Computer {unconstraineddelegation:true, enabled:true})
WHERE NOT c.name CONTAINS 'DC'
RETURN c.name,
       c.operatingsystem,
       c.haslaps,
       datetime({epochSeconds: toInteger(c.lastlogontimestamp)}) as LastSeen
ORDER BY c.lastlogontimestamp DESC NULLS LAST

Query 4: Kerberoastable Accounts Reachable from Tier 2

// Kerberoastable targets reachable from non-admin users
MATCH p=shortestPath(
  (u:User {enabled:true})
  -[*1..5]->
  (k:User {hasspn:true, enabled:true})
)
WHERE NOT u.admincount
RETURN u.name as SourceUser,
       k.name as KerberoastableTarget,
       k.serviceprincipalnames as SPNs,
       length(p) as Hops
ORDER BY Hops ASC
LIMIT 50

Query 5: Stale Admin Accounts (90+ days)

// admincount accounts with no logon in 90 days
MATCH (u:User {admincount:true, enabled:true})
WHERE u.lastlogontimestamp < (timestamp() / 1000 - 7776000)
RETURN u.name,
       u.description,
       datetime({epochSeconds: toInteger(u.lastlogontimestamp)}) as LastLogon,
       datetime({epochSeconds: toInteger(u.pwdlastset)}) as PasswordLastSet
ORDER BY u.lastlogontimestamp ASC

// Admin accounts that have never logged on
MATCH (u:User {admincount:true, enabled:true})
WHERE u.lastlogontimestamp <= 0 OR u.lastlogontimestamp IS NULL
RETURN u.name, u.description, u.whencreated

Query 6: Computers Missing LAPS

// LAPS missing, active, domain-joined computers
MATCH (c:Computer {enabled:true})
WHERE c.haslaps = false
RETURN c.name,
       c.operatingsystem,
       datetime({epochSeconds: toInteger(c.lastlogontimestamp)}) as LastSeen
ORDER BY c.operatingsystem, c.name

Query 7: AS-REP Roastable Accounts

// Kerberos pre-auth disabled, enabled accounts
MATCH (u:User {dontreqpreauth:true, enabled:true})
OPTIONAL MATCH p=shortestPath(
  (u)-[*1..10]->
  (g:Group {name:"DOMAIN ADMINS@CORP.LOCAL"})
)
RETURN u.name,
       u.admincount,
       u.description,
       length(p) as HopsToDA
ORDER BY HopsToDA ASC NULLS LAST

Query 8: Non-Admin Accounts with GenericAll on High-Value Targets

// Low-privilege accounts with control over high-value targets
MATCH (u:User {enabled:true})-[:GenericAll|GenericWrite]->(t)
WHERE NOT u.admincount
AND (t:Group OR t:Computer OR t:User)
AND (t.highvalue OR t.admincount)
RETURN u.name as Attacker,
       t.name as HighValueTarget,
       labels(t) as TargetType
ORDER BY u.name

Bonus — Shadow Credentials (msDS-KeyCredentialLink):

// GenericWrite/GenericAll → msDS-KeyCredentialLink manipulation
MATCH (u:User {enabled:true})-[r:GenericWrite|GenericAll|WriteAccountRestrictions]->(t:User|Computer)
WHERE NOT u.admincount
RETURN u.name as Source,
       t.name as Target,
       type(r) as EdgeType,
       t.enabled as TargetEnabled
ORDER BY u.name

Bonus — ADCS ESC Paths:

// Certificate template abuse path to DC
MATCH p=(u:User {enabled:true})
  -[:Enroll|GenericAll|GenericWrite]->
  (ct:CertTemplate)
  -[:PublishedTo]->
  (ca:EnterpriseCA)
WHERE ct.enrolleeSuppliesSubject = true
OR ct.authenticationenabled = true
RETURN u.name, ct.name, ca.name, ct.enrolleeSuppliesSubject

// ADCS ESC1: Any user can enroll + EKU authentication
MATCH (ct:CertTemplate)
WHERE ct.enrolleeSuppliesSubject = true
AND ct.authenticationenabled = true
AND ct.requiresmanagerapproval = false
RETURN ct.name, ct.oid

Attack Path Prioritization

In large organizations, hundreds of attack paths may exist. It is impossible to remediate all of them simultaneously. A systematic approach to prioritization is required.

Risk Score Calculation:

Risk Score = Impact × Probability × Exposure

  • Impact (1-5): Where does the path lead? Tier 0 (5), Tier 1 (3), Tier 2 (1)?
  • Probability (1-5): Path length. 1-2 hops (5), 3-4 hops (3), 5+ hops (1).
  • Exposure (1-3): Is the source account internet-facing? Helpdesk/VPN/Webmail account (3), internal-only user (1).

Prioritization Matrix:

Risk Score Action SLA
60-75 CRITICAL — Address immediately 24-48 hours
40-59 HIGH — This sprint 1 week
20-39 MEDIUM — Planned remediation 1 month
0-19 LOW — Backlog 3 months
// Prioritization data: path length + account activity + admin status
MATCH p=shortestPath(
  (u:User {enabled:true})
  -[*1..10]->
  (g:Group {name:"DOMAIN ADMINS@CORP.LOCAL"})
)
WITH u, length(p) as hops
RETURN u.name,
       hops,
       u.admincount,
       u.lastlogontimestamp,
       CASE
         WHEN hops <= 2 THEN 'CRITICAL'
         WHEN hops <= 4 THEN 'HIGH'
         WHEN hops <= 6 THEN 'MEDIUM'
         ELSE 'LOW'
       END as Priority
ORDER BY hops ASC, u.lastlogontimestamp DESC
LIMIT 100

Attack Path Remediation Workflow

Specific remediation steps for each edge type:

GenericAll / GenericWrite → Remove

# Check who has access (PowerView)
Import-Module PowerView.ps1

Get-DomainObjectAcl -Identity "SVC_SQL" -ResolveGUIDs |
  Where-Object {$_.ActiveDirectoryRights -match "GenericAll"}

# Remove the ACE
Remove-DomainObjectAcl `
  -TargetIdentity "SVC_SQL" `
  -PrincipalIdentity "jdoe" `
  -Rights All `
  -Verbose

# Using AD Module (alternative to PowerView)
$acl = Get-Acl "AD:\CN=SVC_SQL,OU=ServiceAccounts,DC=corp,DC=local"
$ace = $acl.Access | Where-Object {
  $_.IdentityReference -eq "CORP\jdoe" -and
  $_.ActiveDirectoryRights -match "GenericControl"
}
$acl.RemoveAccessRule($ace)
Set-Acl "AD:\CN=SVC_SQL,OU=ServiceAccounts,DC=corp,DC=local" $acl

MemberOf (Incorrect Group Membership) → Remove

# Remove account from Domain Admins
Remove-ADGroupMember `
  -Identity "Domain Admins" `
  -Members "SVC_SQL" `
  -Confirm:$false

# Check all admin group memberships
Get-ADUser "SVC_SQL" -Properties MemberOf |
  Select-Object -ExpandProperty MemberOf |
  Get-ADGroup |
  Where-Object {$_.AdminCount -eq 1}

Unconstrained Delegation → Remove or Constrain

# Remove unconstrained delegation
Set-ADComputer "APPSERVER01" `
  -TrustedForDelegation $false

# Replace with constrained delegation
Set-ADComputer "APPSERVER01" `
  -TrustedToAuthForDelegation $true
Set-ADComputer "APPSERVER01" `
  -ServicePrincipalNames @{Add="HTTP/webapp.corp.local"}

# Add to Protected Users (completely blocks delegation per account)
Add-ADGroupMember "Protected Users" -Members "APPSERVER01$"

Kerberoastable Account → Replace with gMSA

# Create gMSA
New-ADServiceAccount `
  -Name "gMSA_SQL" `
  -DNSHostName "sql-srv01.corp.local" `
  -PrincipalsAllowedToRetrieveManagedPassword "SQL-SRV01$" `
  -KerberosEncryptionType AES128,AES256

# Configure SQL Server service to use gMSA
# SQL Server Configuration Manager → Service Account → NT SERVICE\gMSA_SQL$

# Disable old service account
Disable-ADAccount "svc_sql"

HasSession → Enforce PAW Policy

# Prevent Tier 0 admin accounts from logging on to Tier 2 machines
# GPO → Computer Config → Admin Templates → System → Credentials Delegation
# "Restrict delegation of credentials to remote servers" = Enabled

# Add admin accounts to Protected Users group
Add-ADGroupMember "Protected Users" -Members "DA-jsmith"

# Restrict logon workstations (per-account)
Set-ADUser "DA-jsmith" -LogonWorkstations "PAW-01,PAW-02,PAW-03"

BloodHound ADCS (Active Directory Certificate Services) Analysis

ADCS has emerged in recent years as a critical attack surface. Will Schroeder and Lee Christensen's "Certified Pre-Owned" research documented dozens of certificate template abuse scenarios. BloodHound CE models ADCS relationships (CertTemplate, EnterpriseCA, IssuedSignedBy) on the graph, making these attack paths visible.

ESC1 — Enrollee Supplies Subject + EKU Auth:

// ESC1: Any user can obtain a certificate that impersonates a DC
MATCH (ct:CertTemplate)
WHERE ct.enrolleeSuppliesSubject = true
AND ct.authenticationenabled = true
AND ct.requiresmanagerapproval = false
AND ct.nosecurityextension = false
MATCH (ca:EnterpriseCA)-[:PublishedTo]->(ct)
MATCH (u:User {enabled:true})-[:Enroll|GenericAll|GenericWrite]->(ct)
RETURN u.name, ct.name, ca.name
ORDER BY u.name

ESC4 — Template Overwrite:

// ESC4: User can modify certificate template properties
MATCH (u:User {enabled:true})-[:GenericAll|GenericWrite|WriteDacl|WriteOwner]->(ct:CertTemplate)
WHERE NOT u.admincount
RETURN u.name, ct.name, u.enabled
ORDER BY u.name

ESC7 — CA Officer/Manager:

// ESC7: CA management rights — approve arbitrary certificate requests
MATCH (u:User {enabled:true})-[:ManageCertificates|ManageCA]->(ca:EnterpriseCA)
WHERE NOT u.admincount
RETURN u.name, ca.name

ADCS Remediation Checklist:

# List all certificate templates
certutil -v -template | Select-String "Template Name:"

# For ESC1: Remove EnrolleesCanRequestCertificates
# Certificates snap-in (certtmpl.msc):
# Template → Properties → Subject Name → "Build from this Active Directory information"

# Require manager approval for sensitive templates
# Template → Properties → Issuance Requirements → "CA certificate manager approval" = ON

# Disable HTTP for NTLM relay prevention (ESC8)
# IIS Authentication → Disable: Windows Authentication (NTLM)
# Enable: HTTPS + FQDN enrollment

Automated Reporting and Continuous Monitoring

BloodHound CE exposes a REST API. Use it to build a weekly automated reporting system.

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

echo "Token: $TOKEN"
#!/usr/bin/env python3
"""
BloodHound CE Weekly Defensive Report
Run every Monday morning: cron 0 8 * * 1
"""
import requests
import json
from datetime import datetime, timedelta

BHCE_URL = "http://localhost:8080"
USERNAME = "svc_reporting"
PASSWORD = "SecureP@ss!"

def get_token():
    r = requests.post(f"{BHCE_URL}/api/v2/auth/login",
                      json={"login_name": USERNAME, "secret": PASSWORD})
    return r.json()["data"]["session_token"]

def run_cypher(token, query):
    headers = {"Authorization": f"Bearer {token}",
               "Content-Type": "application/json"}
    r = requests.post(f"{BHCE_URL}/api/v2/graphs/cypher",
                      headers=headers,
                      json={"query": query})
    return r.json()

def generate_weekly_report():
    token = get_token()
    report = {
        "date": datetime.now().isoformat(),
        "findings": {}
    }

    # Shortest paths to DA
    da_paths = run_cypher(token, """
        MATCH p=shortestPath(
          (u:User {enabled:true})-[*1..10]->
          (g:Group {name:"DOMAIN ADMINS@CORP.LOCAL"})
        )
        RETURN u.name, length(p) as hops
        ORDER BY hops ASC LIMIT 20
    """)
    report["findings"]["shortest_paths_to_da"] = da_paths

    # DCSync rights
    dcsync = run_cypher(token, """
        MATCH (n)-[:DCSync]->(d:Domain)
        WHERE NOT (n:Computer AND n.name CONTAINS 'DC')
        RETURN n.name, labels(n)
    """)
    report["findings"]["dcsync_accounts"] = dcsync

    # Stale admins
    stale = run_cypher(token, f"""
        MATCH (u:User {admincount:true, enabled:true})
        WHERE u.lastlogontimestamp < {int((datetime.now() - timedelta(days=90)).timestamp())}
        RETURN u.name, u.lastlogontimestamp
        ORDER BY u.lastlogontimestamp ASC
    """)
    report["findings"]["stale_admins"] = stale

    # Save report
    filename = f"bhce_report_{datetime.now().strftime('%Y%m%d')}.json"
    with open(filename, 'w') as f:
        json.dump(report, f, indent=2)

    print(f"[+] Report saved: {filename}")
    return report

if __name__ == "__main__":
    report = generate_weekly_report()

    # Alert on critical findings
    if report["findings"]["dcsync_accounts"].get("data", {}).get("nodes"):
        print("[!] CRITICAL: Unauthorized DCSync accounts detected!")
        # Send email/Slack/Teams notification

Delta Analysis — Week-over-Week Changes:

def compare_reports(old_report, new_report):
    """Detect new attack paths compared to the previous week"""
    old_users = set(
        item["u.name"]
        for item in old_report["findings"]["shortest_paths_to_da"].get("data", {}).get("nodes", [])
    )
    new_users = set(
        item["u.name"]
        for item in new_report["findings"]["shortest_paths_to_da"].get("data", {}).get("nodes", [])
    )

    new_exposures = new_users - old_users
    fixed_paths = old_users - new_users

    if new_exposures:
        print(f"[!] NEW EXPOSED ACCOUNTS: {new_exposures}")
    if fixed_paths:
        print(f"[+] REMEDIATED ACCOUNTS: {fixed_paths}")

Defensive Investment Priorities

Regardless of what BloodHound findings reveal, the following foundational security controls should be present in every AD environment:

1. Tier 0 Isolation

  • Separate OU structure for DCs, CAs, and ADFS
  • PAW (Privileged Access Workstation): Dedicated, hardened workstations for Tier 0 management
  • Separate credentials for admin accounts (jsmithDA-jsmith)
  • JIT (Just-In-Time) access: Admin rights only when needed, with time limits

2. MFA for All Admin Accounts

  • Azure AD MFA or Microsoft Authenticator
  • Smart card / Windows Hello for Business (FIDO2)
  • Phishing-resistant MFA preferred
  • Conditional Access — restrict by location and device compliance

3. LAPS Deployment

  • Windows LAPS (built into Windows 11 / Server 2022)
  • Legacy Windows: Microsoft LAPS MSI
  • Every workstation and server — DCs excluded
  • Password rotation: 24 hours (workstations), 48 hours (servers)
  • Restrict LAPS password read access to privileged groups only

4. SMB Signing Enforcement

# GPO: Computer Configuration → Windows Settings →
# Security Settings → Local Policies → Security Options
# "Microsoft network server: Digitally sign communications (always)" = Enabled
# "Microsoft network client: Digitally sign communications (always)" = Enabled

5. Protected Users Group

  • All Tier 0 accounts should be Protected Users members
  • Effects: removes RC4, blocks delegation, removes NTLM, shortens TGT lifetime
  • Warning: Some legacy applications require NTLM — test before deploying!

6. Credential Guard

# Enable Credential Guard (requires UEFI + Secure Boot)
# GPO: Computer Config → Admin Templates → System → Device Guard
# "Turn On Virtualization Based Security" = Enabled
# "Credential Guard Configuration" = Enabled with UEFI lock

# Verify via PowerShell
(Get-ComputerInfo).DeviceGuardSecurityServicesRunning

7. Weekly BloodHound Analysis

  • SharpHound collection: Automated every Sunday night
  • Delta analysis: Are there new paths?
  • Create tickets: Jira/ServiceNow for prioritized findings
  • Track metrics: Average path length trend across the domain over time

8. Audit Logging (Event Monitoring)

# DCSync attempt detection — Event ID 4662
# DS-Replication-Get-Changes-All access attempt
# SIEM rule: Event 4662 + AccessMask = 0x100 or 0x10

# AdminSDHolder modification detection — Event ID 5136
# SIEM rule: 5136 + ObjectClass = ms-DS-Admin-Count

# Unauthorized ACL change — Event IDs 5136, 4670
# Kerberoast attempt — Event ID 4769 + TicketOptions 0x40810000

# Enable Advanced Audit via GPO
# Computer Config → Windows Settings → Security Settings → Advanced Audit Policy
# Account Logon: Kerberos Service Ticket Operations = Success, Failure
# DS Access: Directory Service Changes = Success
# Logon/Logoff: Special Logon = Success

References and Further Reading

Primary Sources:

  • Andy Robbins, Rohan Vazarkar, Will Schroeder — BloodHound (2016) and all subsequent research
  • SpecterOps — Attack Path Management methodology and BloodHound CE
  • GitHub: github.com/SpecterOps/BloodHound (Apache 2.0)

Academic and Technical References:

  • Microsoft ESAE (Enhanced Security Admin Environment) — Retired; succeeded by the Enterprise Access Model
  • Microsoft Enterprise Access Model — Current reference for tiered administration
  • "Certified Pre-Owned" — Will Schroeder, Lee Christensen (ADCS attacks, 2021)
  • "The Dog Whisperer's Handbook" — harmj0y (BloodHound usage guide)
  • "An Ace Up the Sleeve" — Andy Robbins, Will Schroeder (ACL attacks)
  • "Not a Security Boundary" — Andy Robbins (forest trust attacks)
  • "Kerberoasting Without Mimikatz" — Tim Medin (original Kerberoasting technique)

Tools Referenced:

  • SharpHound: github.com/SpecterOps/SharpHound
  • BloodHound.py: github.com/dirkjanm/BloodHound.py
  • PowerView: github.com/PowerShellMafia/PowerSploit
  • Impacket: github.com/fortra/impacket (DCSync, AS-REP roasting, Kerberoasting)
  • Certipy: github.com/ly4k/Certipy (ADCS attack and analysis)
  • ADACLScanner: github.com/canix1/ADACLScanner (ACL audit reporting)
  • PingCastle: pingcastle.com (AD risk assessment — complementary to BloodHound)

Frequently Asked Questions

What is BloodHound Community Edition?

BloodHound CE is the free, open-source graph tool from SpecterOps that maps relationships in Active Directory and Azure to reveal attack paths. Defenders use it to find and cut the shortest routes an attacker could take to Domain Admin.

How do blue teams use BloodHound defensively?

By ingesting SharpHound/AzureHound data regularly, prioritizing high-value choke-point edges, implementing a tiered administration model, and tracking attack-path exposure over time as a security metric rather than a one-off audit.

What is Attack Path Management?

A continuous discipline formalized by SpecterOps for identifying, prioritizing and remediating the attack paths that lead to critical assets — focusing on the few high-impact edges that eliminate many paths at once, instead of chasing every misconfiguration.

What are custom Cypher queries used for?

Cypher is BloodHound's graph query language. Blue teams write custom queries to surface risks the default ones miss — such as non-tier-0 principals that control tier-0 objects, shadow admins, or dangerous ACL chains specific to their environment.