Files
mukul975 c21af3347e Complete folder anatomy for all 649 cybersecurity skills + update LICENSE to Mahipal
- Add scripts/agent.py and references/api-reference.md to all remaining skills
- Update all 648 LICENSE files: copyright now reads 'Mahipal'
- Add implementing-security-monitoring-with-datadog (new skill with full anatomy)
- All 649 skills now have: SKILL.md, LICENSE, scripts/agent.py, references/api-reference.md
2026-03-11 00:22:12 +01:00

135 lines
5.1 KiB
Python

#!/usr/bin/env python3
"""BloodHound CE reconnaissance agent using bloodhound Python ingestor and Neo4j."""
import json
import sys
import argparse
import subprocess
from datetime import datetime
try:
from neo4j import GraphDatabase
except ImportError:
print("Install: pip install neo4j")
sys.exit(1)
def collect_bloodhound_data(domain, username, password, dc_ip, method="all"):
"""Run BloodHound Python ingestor to collect AD data."""
cmd = [
"bloodhound-python", "-d", domain, "-u", username, "-p", password,
"-ns", dc_ip, "-c", method, "--zip",
]
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
return {"status": "completed", "output": result.stdout[:1000]}
except FileNotFoundError:
return {"status": "error", "message": "Install: pip install bloodhound"}
except subprocess.TimeoutExpired:
return {"status": "timeout"}
def query_shortest_path_to_da(driver):
"""Find shortest path to Domain Admins."""
with driver.session() as session:
result = session.run(
"MATCH p=shortestPath((u:User)-[*1..]->(g:Group {name: $group})) "
"WHERE u.enabled = true RETURN u.name AS user, length(p) AS hops "
"ORDER BY hops LIMIT 10",
group="DOMAIN ADMINS@DOMAIN.LOCAL",
)
return [{"user": r["user"], "hops": r["hops"]} for r in result]
def query_kerberoastable_users(driver):
"""Find kerberoastable user accounts."""
with driver.session() as session:
result = session.run(
"MATCH (u:User) WHERE u.hasspn = true AND u.enabled = true "
"RETURN u.name AS user, u.serviceprincipalnames AS spns, "
"u.admincount AS admin_count ORDER BY u.admincount DESC"
)
return [{"user": r["user"], "spns": r["spns"],
"admin": r["admin_count"]} for r in result]
def query_unconstrained_delegation(driver):
"""Find computers with unconstrained delegation."""
with driver.session() as session:
result = session.run(
"MATCH (c:Computer) WHERE c.unconstraineddelegation = true "
"RETURN c.name AS computer, c.operatingsystem AS os"
)
return [{"computer": r["computer"], "os": r["os"]} for r in result]
def query_as_rep_roastable(driver):
"""Find AS-REP roastable accounts (no pre-auth required)."""
with driver.session() as session:
result = session.run(
"MATCH (u:User) WHERE u.dontreqpreauth = true AND u.enabled = true "
"RETURN u.name AS user, u.admincount AS admin_count"
)
return [{"user": r["user"], "admin": r["admin_count"]} for r in result]
def run_recon(neo4j_uri, neo4j_user, neo4j_password):
"""Run BloodHound reconnaissance queries."""
driver = GraphDatabase.driver(neo4j_uri, auth=(neo4j_user, neo4j_password))
print(f"\n{'='*60}")
print(f" BLOODHOUND CE RECONNAISSANCE")
print(f" Generated: {datetime.utcnow().isoformat()} UTC")
print(f"{'='*60}\n")
paths = query_shortest_path_to_da(driver)
print(f"--- SHORTEST PATHS TO DOMAIN ADMIN ({len(paths)}) ---")
for p in paths:
print(f" {p['user']}: {p['hops']} hops")
kerb = query_kerberoastable_users(driver)
print(f"\n--- KERBEROASTABLE USERS ({len(kerb)}) ---")
for k in kerb[:10]:
print(f" {k['user']} (admin={k['admin']})")
deleg = query_unconstrained_delegation(driver)
print(f"\n--- UNCONSTRAINED DELEGATION ({len(deleg)}) ---")
for d in deleg:
print(f" {d['computer']}: {d['os']}")
asrep = query_as_rep_roastable(driver)
print(f"\n--- AS-REP ROASTABLE ({len(asrep)}) ---")
for a in asrep:
print(f" {a['user']} (admin={a['admin']})")
driver.close()
return {"paths_to_da": paths, "kerberoastable": kerb,
"unconstrained_delegation": deleg, "asrep_roastable": asrep}
def main():
parser = argparse.ArgumentParser(description="BloodHound CE Recon Agent")
parser.add_argument("--neo4j-uri", default="bolt://localhost:7687", help="Neo4j URI")
parser.add_argument("--neo4j-user", default="neo4j", help="Neo4j username")
parser.add_argument("--neo4j-password", required=True, help="Neo4j password")
parser.add_argument("--collect", action="store_true", help="Run data collection first")
parser.add_argument("--domain", help="AD domain for collection")
parser.add_argument("--ad-user", help="AD username for collection")
parser.add_argument("--ad-pass", help="AD password for collection")
parser.add_argument("--dc-ip", help="Domain controller IP")
parser.add_argument("--output", help="Save report to JSON file")
args = parser.parse_args()
if args.collect and args.domain:
result = collect_bloodhound_data(args.domain, args.ad_user, args.ad_pass, args.dc_ip)
print(json.dumps(result, indent=2))
report = run_recon(args.neo4j_uri, args.neo4j_user, args.neo4j_password)
if args.output:
with open(args.output, "w") as f:
json.dump(report, f, indent=2, default=str)
print(f"\n[+] Report saved to {args.output}")
if __name__ == "__main__":
main()