mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-15 20:25:18 +03:00
Initial commit - 611 cybersecurity skills across all subdomains
This commit is contained in:
@@ -0,0 +1,402 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
BloodHound Data Analyzer
|
||||
|
||||
Parses BloodHound JSON collection data to identify high-risk attack paths,
|
||||
Kerberoastable accounts, ACL misconfigurations, and delegation abuse
|
||||
opportunities without requiring the BloodHound GUI.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class ADUser:
|
||||
name: str
|
||||
enabled: bool = True
|
||||
has_spn: bool = False
|
||||
dont_req_preauth: bool = False
|
||||
admin_count: bool = False
|
||||
password_last_set: str = ""
|
||||
last_logon: str = ""
|
||||
description: str = ""
|
||||
sid: str = ""
|
||||
|
||||
|
||||
@dataclass
|
||||
class ADComputer:
|
||||
name: str
|
||||
os: str = ""
|
||||
enabled: bool = True
|
||||
unconstrained_delegation: bool = False
|
||||
allowed_to_delegate: list = field(default_factory=list)
|
||||
local_admins: list = field(default_factory=list)
|
||||
has_sessions: list = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ADGroup:
|
||||
name: str
|
||||
members: list = field(default_factory=list)
|
||||
high_value: bool = False
|
||||
sid: str = ""
|
||||
|
||||
|
||||
@dataclass
|
||||
class Finding:
|
||||
severity: str # critical, high, medium, low
|
||||
category: str
|
||||
title: str
|
||||
description: str
|
||||
affected_objects: list = field(default_factory=list)
|
||||
attack_path: str = ""
|
||||
remediation: str = ""
|
||||
mitre_technique: str = ""
|
||||
|
||||
|
||||
class BloodHoundAnalyzer:
|
||||
"""Analyze BloodHound collection data for attack paths."""
|
||||
|
||||
def __init__(self):
|
||||
self.users: dict[str, ADUser] = {}
|
||||
self.computers: dict[str, ADComputer] = {}
|
||||
self.groups: dict[str, ADGroup] = {}
|
||||
self.acl_edges: list[dict] = []
|
||||
self.findings: list[Finding] = []
|
||||
self.domain_admins: set = set()
|
||||
|
||||
def load_bloodhound_data(self, data_dir: str) -> None:
|
||||
"""Load BloodHound JSON files from collection directory."""
|
||||
for filename in os.listdir(data_dir):
|
||||
filepath = os.path.join(data_dir, filename)
|
||||
if not filename.endswith(".json"):
|
||||
continue
|
||||
|
||||
with open(filepath) as f:
|
||||
try:
|
||||
data = json.load(f)
|
||||
except json.JSONDecodeError:
|
||||
print(f"[-] Failed to parse {filename}")
|
||||
continue
|
||||
|
||||
if "users" in filename.lower() or (isinstance(data, dict) and data.get("meta", {}).get("type") == "users"):
|
||||
self._parse_users(data)
|
||||
elif "computers" in filename.lower() or (isinstance(data, dict) and data.get("meta", {}).get("type") == "computers"):
|
||||
self._parse_computers(data)
|
||||
elif "groups" in filename.lower() or (isinstance(data, dict) and data.get("meta", {}).get("type") == "groups"):
|
||||
self._parse_groups(data)
|
||||
|
||||
print(f"[+] Loaded: {len(self.users)} users, {len(self.computers)} computers, {len(self.groups)} groups")
|
||||
|
||||
def _parse_users(self, data: dict) -> None:
|
||||
"""Parse user data from BloodHound JSON."""
|
||||
items = data.get("data", data) if isinstance(data, dict) else data
|
||||
if isinstance(items, dict):
|
||||
items = items.get("data", [])
|
||||
|
||||
for user_data in items:
|
||||
props = user_data.get("Properties", user_data.get("properties", {}))
|
||||
name = props.get("name", user_data.get("name", "unknown"))
|
||||
user = ADUser(
|
||||
name=name,
|
||||
enabled=props.get("enabled", True),
|
||||
has_spn=props.get("hasspn", False),
|
||||
dont_req_preauth=props.get("dontreqpreauth", False),
|
||||
admin_count=props.get("admincount", False),
|
||||
password_last_set=str(props.get("pwdlastset", "")),
|
||||
last_logon=str(props.get("lastlogon", "")),
|
||||
description=props.get("description", ""),
|
||||
sid=props.get("objectid", props.get("objectsid", "")),
|
||||
)
|
||||
self.users[name.upper()] = user
|
||||
|
||||
def _parse_computers(self, data: dict) -> None:
|
||||
"""Parse computer data from BloodHound JSON."""
|
||||
items = data.get("data", data) if isinstance(data, dict) else data
|
||||
if isinstance(items, dict):
|
||||
items = items.get("data", [])
|
||||
|
||||
for comp_data in items:
|
||||
props = comp_data.get("Properties", comp_data.get("properties", {}))
|
||||
name = props.get("name", comp_data.get("name", "unknown"))
|
||||
computer = ADComputer(
|
||||
name=name,
|
||||
os=props.get("operatingsystem", ""),
|
||||
enabled=props.get("enabled", True),
|
||||
unconstrained_delegation=props.get("unconstraineddelegation", False),
|
||||
allowed_to_delegate=props.get("allowedtodelegate", []) or [],
|
||||
)
|
||||
self.computers[name.upper()] = computer
|
||||
|
||||
def _parse_groups(self, data: dict) -> None:
|
||||
"""Parse group data from BloodHound JSON."""
|
||||
items = data.get("data", data) if isinstance(data, dict) else data
|
||||
if isinstance(items, dict):
|
||||
items = items.get("data", [])
|
||||
|
||||
for group_data in items:
|
||||
props = group_data.get("Properties", group_data.get("properties", {}))
|
||||
name = props.get("name", group_data.get("name", "unknown"))
|
||||
|
||||
members_raw = group_data.get("Members", group_data.get("members", []))
|
||||
members = [m.get("MemberId", m.get("ObjectIdentifier", "")) for m in members_raw] if members_raw else []
|
||||
|
||||
group = ADGroup(
|
||||
name=name,
|
||||
members=members,
|
||||
high_value=props.get("highvalue", False),
|
||||
sid=props.get("objectid", ""),
|
||||
)
|
||||
self.groups[name.upper()] = group
|
||||
|
||||
if "DOMAIN ADMINS" in name.upper():
|
||||
self.domain_admins = set(members)
|
||||
|
||||
def find_kerberoastable_accounts(self) -> list[Finding]:
|
||||
"""Identify Kerberoastable service accounts."""
|
||||
kerberoastable = [u for u in self.users.values() if u.has_spn and u.enabled]
|
||||
findings = []
|
||||
|
||||
if kerberoastable:
|
||||
privileged_kerberoastable = [
|
||||
u for u in kerberoastable if u.admin_count
|
||||
]
|
||||
|
||||
findings.append(Finding(
|
||||
severity="critical" if privileged_kerberoastable else "high",
|
||||
category="Kerberoasting",
|
||||
title=f"Found {len(kerberoastable)} Kerberoastable Accounts",
|
||||
description=(
|
||||
f"{len(kerberoastable)} enabled user accounts have Service Principal Names (SPNs) "
|
||||
f"set, making them vulnerable to Kerberoasting (T1558.003). "
|
||||
f"{len(privileged_kerberoastable)} of these are privileged accounts."
|
||||
),
|
||||
affected_objects=[u.name for u in kerberoastable],
|
||||
attack_path="GetUserSPNs.py -> Request TGS -> Crack offline with hashcat -m 13100",
|
||||
remediation=(
|
||||
"1. Use Group Managed Service Accounts (gMSA) where possible\n"
|
||||
"2. Set 25+ character passwords on service accounts\n"
|
||||
"3. Enable AES encryption only (disable RC4)\n"
|
||||
"4. Monitor Event ID 4769 for anomalous TGS requests"
|
||||
),
|
||||
mitre_technique="T1558.003",
|
||||
))
|
||||
|
||||
return findings
|
||||
|
||||
def find_asrep_roastable_accounts(self) -> list[Finding]:
|
||||
"""Identify AS-REP Roastable accounts."""
|
||||
asrep = [u for u in self.users.values() if u.dont_req_preauth and u.enabled]
|
||||
findings = []
|
||||
|
||||
if asrep:
|
||||
findings.append(Finding(
|
||||
severity="high",
|
||||
category="AS-REP Roasting",
|
||||
title=f"Found {len(asrep)} AS-REP Roastable Accounts",
|
||||
description=(
|
||||
f"{len(asrep)} accounts have 'Do not require Kerberos pre-authentication' "
|
||||
f"enabled, allowing offline password cracking (T1558.004)."
|
||||
),
|
||||
affected_objects=[u.name for u in asrep],
|
||||
attack_path="GetNPUsers.py -> Request AS-REP -> Crack with hashcat -m 18200",
|
||||
remediation=(
|
||||
"1. Enable Kerberos pre-authentication for all accounts\n"
|
||||
"2. Use strong passwords (25+ characters) on affected accounts\n"
|
||||
"3. Monitor Event ID 4768 with pre-auth type 0"
|
||||
),
|
||||
mitre_technique="T1558.004",
|
||||
))
|
||||
|
||||
return findings
|
||||
|
||||
def find_unconstrained_delegation(self) -> list[Finding]:
|
||||
"""Identify computers with unconstrained delegation."""
|
||||
unconstrained = [
|
||||
c for c in self.computers.values()
|
||||
if c.unconstrained_delegation and c.enabled
|
||||
and "DOMAIN CONTROLLER" not in c.name.upper()
|
||||
]
|
||||
findings = []
|
||||
|
||||
if unconstrained:
|
||||
findings.append(Finding(
|
||||
severity="critical",
|
||||
category="Delegation Abuse",
|
||||
title=f"Found {len(unconstrained)} Non-DC Computers with Unconstrained Delegation",
|
||||
description=(
|
||||
f"{len(unconstrained)} computers (excluding DCs) have unconstrained delegation "
|
||||
f"enabled. An attacker with admin access to these systems can capture TGTs "
|
||||
f"from any user that authenticates to them, including Domain Admins."
|
||||
),
|
||||
affected_objects=[c.name for c in unconstrained],
|
||||
attack_path=(
|
||||
"Compromise unconstrained host -> Coerce DC auth (PetitPotam/PrinterBug) -> "
|
||||
"Capture DC TGT with Rubeus monitor -> DCSync"
|
||||
),
|
||||
remediation=(
|
||||
"1. Remove unconstrained delegation from non-DC computers\n"
|
||||
"2. Migrate to constrained delegation or RBCD\n"
|
||||
"3. Add sensitive accounts to 'Protected Users' group\n"
|
||||
"4. Enable 'Account is sensitive and cannot be delegated'"
|
||||
),
|
||||
mitre_technique="T1558.001",
|
||||
))
|
||||
|
||||
return findings
|
||||
|
||||
def find_constrained_delegation(self) -> list[Finding]:
|
||||
"""Identify constrained delegation abuse opportunities."""
|
||||
constrained = [
|
||||
c for c in self.computers.values()
|
||||
if c.allowed_to_delegate and c.enabled
|
||||
]
|
||||
findings = []
|
||||
|
||||
if constrained:
|
||||
findings.append(Finding(
|
||||
severity="high",
|
||||
category="Delegation Abuse",
|
||||
title=f"Found {len(constrained)} Computers with Constrained Delegation",
|
||||
description=(
|
||||
f"{len(constrained)} computers have constrained delegation configured. "
|
||||
f"If protocol transition is enabled (TrustedToAuthForDelegation), an attacker "
|
||||
f"can abuse S4U2Self and S4U2Proxy to impersonate any user to the target service."
|
||||
),
|
||||
affected_objects=[
|
||||
f"{c.name} -> {', '.join(c.allowed_to_delegate)}" for c in constrained
|
||||
],
|
||||
remediation=(
|
||||
"1. Review all constrained delegation configurations\n"
|
||||
"2. Disable protocol transition where not needed\n"
|
||||
"3. Use RBCD instead of traditional constrained delegation\n"
|
||||
"4. Add sensitive accounts to 'Protected Users' group"
|
||||
),
|
||||
mitre_technique="T1550.003",
|
||||
))
|
||||
|
||||
return findings
|
||||
|
||||
def run_full_analysis(self) -> list[Finding]:
|
||||
"""Run all analysis checks and return findings."""
|
||||
self.findings = []
|
||||
self.findings.extend(self.find_kerberoastable_accounts())
|
||||
self.findings.extend(self.find_asrep_roastable_accounts())
|
||||
self.findings.extend(self.find_unconstrained_delegation())
|
||||
self.findings.extend(self.find_constrained_delegation())
|
||||
|
||||
# Sort by severity
|
||||
severity_order = {"critical": 0, "high": 1, "medium": 2, "low": 3}
|
||||
self.findings.sort(key=lambda f: severity_order.get(f.severity, 99))
|
||||
|
||||
return self.findings
|
||||
|
||||
def generate_report(self) -> str:
|
||||
"""Generate analysis report."""
|
||||
lines = []
|
||||
lines.append("=" * 70)
|
||||
lines.append("BLOODHOUND ACTIVE DIRECTORY ANALYSIS REPORT")
|
||||
lines.append("=" * 70)
|
||||
lines.append(f"\nDomain Statistics:")
|
||||
lines.append(f" Users: {len(self.users)}")
|
||||
lines.append(f" Computers: {len(self.computers)}")
|
||||
lines.append(f" Groups: {len(self.groups)}")
|
||||
lines.append(f" Findings: {len(self.findings)}")
|
||||
|
||||
# Summary by severity
|
||||
sev_counts = defaultdict(int)
|
||||
for f in self.findings:
|
||||
sev_counts[f.severity] += 1
|
||||
lines.append(f"\n Critical: {sev_counts['critical']}")
|
||||
lines.append(f" High: {sev_counts['high']}")
|
||||
lines.append(f" Medium: {sev_counts['medium']}")
|
||||
lines.append(f" Low: {sev_counts['low']}")
|
||||
|
||||
lines.append("\n" + "=" * 70)
|
||||
lines.append("DETAILED FINDINGS")
|
||||
lines.append("=" * 70)
|
||||
|
||||
for i, finding in enumerate(self.findings, 1):
|
||||
lines.append(f"\n--- Finding #{i}: [{finding.severity.upper()}] {finding.title} ---")
|
||||
lines.append(f"Category: {finding.category}")
|
||||
lines.append(f"MITRE ATT&CK: {finding.mitre_technique}")
|
||||
lines.append(f"\nDescription:\n {finding.description}")
|
||||
lines.append(f"\nAttack Path:\n {finding.attack_path}")
|
||||
lines.append(f"\nAffected Objects ({len(finding.affected_objects)}):")
|
||||
for obj in finding.affected_objects[:10]:
|
||||
lines.append(f" - {obj}")
|
||||
if len(finding.affected_objects) > 10:
|
||||
lines.append(f" ... and {len(finding.affected_objects) - 10} more")
|
||||
lines.append(f"\nRemediation:\n {finding.remediation}")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main():
|
||||
"""Demonstrate BloodHound data analysis."""
|
||||
analyzer = BloodHoundAnalyzer()
|
||||
|
||||
# Create sample data for demonstration
|
||||
sample_users = {
|
||||
"meta": {"type": "users"},
|
||||
"data": [
|
||||
{"Properties": {"name": "SVC_SQL@CORP.LOCAL", "enabled": True, "hasspn": True,
|
||||
"dontreqpreauth": False, "admincount": True, "description": "SQL Service Account"}},
|
||||
{"Properties": {"name": "SVC_WEB@CORP.LOCAL", "enabled": True, "hasspn": True,
|
||||
"dontreqpreauth": False, "admincount": False, "description": "Web Service"}},
|
||||
{"Properties": {"name": "SVC_BACKUP@CORP.LOCAL", "enabled": True, "hasspn": True,
|
||||
"dontreqpreauth": False, "admincount": True, "description": "Backup Service"}},
|
||||
{"Properties": {"name": "J.SMITH@CORP.LOCAL", "enabled": True, "hasspn": False,
|
||||
"dontreqpreauth": True, "admincount": False}},
|
||||
{"Properties": {"name": "ADMIN@CORP.LOCAL", "enabled": True, "hasspn": False,
|
||||
"dontreqpreauth": False, "admincount": True}},
|
||||
],
|
||||
}
|
||||
|
||||
sample_computers = {
|
||||
"meta": {"type": "computers"},
|
||||
"data": [
|
||||
{"Properties": {"name": "DC01.CORP.LOCAL", "enabled": True,
|
||||
"unconstraineddelegation": True, "operatingsystem": "Windows Server 2022"}},
|
||||
{"Properties": {"name": "WEB01.CORP.LOCAL", "enabled": True,
|
||||
"unconstraineddelegation": True, "operatingsystem": "Windows Server 2019"}},
|
||||
{"Properties": {"name": "SQL01.CORP.LOCAL", "enabled": True,
|
||||
"unconstraineddelegation": False, "operatingsystem": "Windows Server 2019",
|
||||
"allowedtodelegate": ["MSSQLSvc/DB01.CORP.LOCAL:1433"]}},
|
||||
],
|
||||
}
|
||||
|
||||
sample_groups = {
|
||||
"meta": {"type": "groups"},
|
||||
"data": [
|
||||
{"Properties": {"name": "DOMAIN ADMINS@CORP.LOCAL", "highvalue": True},
|
||||
"Members": [{"MemberId": "S-1-5-21-xxx-500"}]},
|
||||
{"Properties": {"name": "BACKUP OPERATORS@CORP.LOCAL", "highvalue": True},
|
||||
"Members": []},
|
||||
],
|
||||
}
|
||||
|
||||
# Write sample data
|
||||
sample_dir = "./bloodhound_sample"
|
||||
os.makedirs(sample_dir, exist_ok=True)
|
||||
for name, data in [("users.json", sample_users), ("computers.json", sample_computers), ("groups.json", sample_groups)]:
|
||||
with open(os.path.join(sample_dir, name), "w") as f:
|
||||
json.dump(data, f)
|
||||
|
||||
# Load and analyze
|
||||
analyzer.load_bloodhound_data(sample_dir)
|
||||
analyzer.run_full_analysis()
|
||||
print(analyzer.generate_report())
|
||||
|
||||
# Cleanup
|
||||
import shutil
|
||||
shutil.rmtree(sample_dir)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user