mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-12 14:14:56 +03:00
322 lines
12 KiB
Python
322 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Kerberoasting Analysis and Detection Tool
|
|
|
|
Parses Kerberos TGS request logs (Event ID 4769) to detect potential
|
|
Kerberoasting activity and analyzes extracted hashes for weak passwords.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import re
|
|
import csv
|
|
from datetime import datetime, timedelta
|
|
from collections import defaultdict
|
|
from dataclasses import dataclass, field
|
|
from typing import Optional
|
|
|
|
|
|
@dataclass
|
|
class TGSRequest:
|
|
"""Represents a Kerberos TGS ticket request (Event ID 4769)."""
|
|
timestamp: str
|
|
source_ip: str
|
|
source_user: str
|
|
target_service: str
|
|
encryption_type: str # 0x17=RC4, 0x11=AES128, 0x12=AES256
|
|
result_code: str
|
|
ticket_options: str = ""
|
|
|
|
|
|
@dataclass
|
|
class KerberoastTarget:
|
|
"""A user account vulnerable to Kerberoasting."""
|
|
username: str
|
|
spn: str
|
|
domain: str
|
|
password_last_set: str = ""
|
|
admin_count: bool = False
|
|
member_of: list = field(default_factory=list)
|
|
risk_score: int = 0
|
|
|
|
|
|
@dataclass
|
|
class KerberoastAlert:
|
|
"""Alert for detected Kerberoasting activity."""
|
|
severity: str
|
|
timestamp: str
|
|
source_ip: str
|
|
source_user: str
|
|
target_count: int
|
|
targets: list = field(default_factory=list)
|
|
encryption_types: list = field(default_factory=list)
|
|
description: str = ""
|
|
|
|
|
|
class KerberoastDetector:
|
|
"""Detect Kerberoasting activity from Windows Event Logs."""
|
|
|
|
# Detection thresholds
|
|
TGS_REQUEST_THRESHOLD = 5 # requests in window
|
|
TIME_WINDOW_SECONDS = 300 # 5 minutes
|
|
RC4_ETYPE = "0x17"
|
|
|
|
def __init__(self):
|
|
self.tgs_requests: list[TGSRequest] = []
|
|
self.alerts: list[KerberoastAlert] = []
|
|
self.targets: list[KerberoastTarget] = []
|
|
|
|
def parse_event_log(self, log_entries: list[dict]) -> None:
|
|
"""Parse Windows Event ID 4769 entries."""
|
|
for entry in log_entries:
|
|
if entry.get("EventID") != 4769:
|
|
continue
|
|
|
|
event_data = entry.get("EventData", {})
|
|
req = TGSRequest(
|
|
timestamp=entry.get("TimeCreated", ""),
|
|
source_ip=event_data.get("IpAddress", ""),
|
|
source_user=event_data.get("TargetUserName", ""),
|
|
target_service=event_data.get("ServiceName", ""),
|
|
encryption_type=event_data.get("TicketEncryptionType", ""),
|
|
result_code=event_data.get("Status", ""),
|
|
ticket_options=event_data.get("TicketOptions", ""),
|
|
)
|
|
self.tgs_requests.append(req)
|
|
|
|
def detect_kerberoasting(self) -> list[KerberoastAlert]:
|
|
"""Analyze TGS requests for Kerberoasting indicators."""
|
|
self.alerts = []
|
|
|
|
# Group requests by source user and time window
|
|
user_requests = defaultdict(list)
|
|
for req in self.tgs_requests:
|
|
user_requests[req.source_user].append(req)
|
|
|
|
for user, requests in user_requests.items():
|
|
# Sort by timestamp
|
|
requests.sort(key=lambda r: r.timestamp)
|
|
|
|
# Sliding window detection
|
|
for i, req in enumerate(requests):
|
|
window_reqs = []
|
|
try:
|
|
req_time = datetime.fromisoformat(req.timestamp.replace("Z", "+00:00"))
|
|
except (ValueError, AttributeError):
|
|
continue
|
|
|
|
for j in range(i, len(requests)):
|
|
try:
|
|
other_time = datetime.fromisoformat(
|
|
requests[j].timestamp.replace("Z", "+00:00")
|
|
)
|
|
except (ValueError, AttributeError):
|
|
continue
|
|
if (other_time - req_time).total_seconds() <= self.TIME_WINDOW_SECONDS:
|
|
window_reqs.append(requests[j])
|
|
else:
|
|
break
|
|
|
|
# Check threshold
|
|
if len(window_reqs) >= self.TGS_REQUEST_THRESHOLD:
|
|
rc4_reqs = [r for r in window_reqs if r.encryption_type == self.RC4_ETYPE]
|
|
unique_services = set(r.target_service for r in window_reqs)
|
|
|
|
severity = "critical" if len(rc4_reqs) > 0 else "high"
|
|
if len(unique_services) > 10:
|
|
severity = "critical"
|
|
|
|
alert = KerberoastAlert(
|
|
severity=severity,
|
|
timestamp=req.timestamp,
|
|
source_ip=req.source_ip,
|
|
source_user=user,
|
|
target_count=len(unique_services),
|
|
targets=list(unique_services),
|
|
encryption_types=list(set(r.encryption_type for r in window_reqs)),
|
|
description=(
|
|
f"User {user} from {req.source_ip} requested "
|
|
f"{len(window_reqs)} TGS tickets for {len(unique_services)} "
|
|
f"unique services within {self.TIME_WINDOW_SECONDS}s window. "
|
|
f"RC4 requests: {len(rc4_reqs)}/{len(window_reqs)}."
|
|
),
|
|
)
|
|
self.alerts.append(alert)
|
|
break # One alert per user
|
|
|
|
return self.alerts
|
|
|
|
def analyze_kerberoast_targets(self, targets: list[KerberoastTarget]) -> list[dict]:
|
|
"""Analyze and risk-score Kerberoastable accounts."""
|
|
self.targets = targets
|
|
scored_targets = []
|
|
|
|
for target in targets:
|
|
score = 0
|
|
risk_factors = []
|
|
|
|
# Check if privileged
|
|
if target.admin_count:
|
|
score += 40
|
|
risk_factors.append("AdminCount=1 (privileged account)")
|
|
|
|
privileged_groups = [
|
|
"domain admins", "enterprise admins", "schema admins",
|
|
"backup operators", "server operators", "account operators",
|
|
]
|
|
for group in target.member_of:
|
|
if group.lower() in privileged_groups:
|
|
score += 30
|
|
risk_factors.append(f"Member of {group}")
|
|
|
|
# Check password age
|
|
if target.password_last_set:
|
|
try:
|
|
pwd_date = datetime.fromisoformat(target.password_last_set)
|
|
age_days = (datetime.now() - pwd_date).days
|
|
if age_days > 730: # 2 years
|
|
score += 25
|
|
risk_factors.append(f"Password age: {age_days} days (>2 years)")
|
|
elif age_days > 365:
|
|
score += 15
|
|
risk_factors.append(f"Password age: {age_days} days (>1 year)")
|
|
elif age_days > 180:
|
|
score += 10
|
|
risk_factors.append(f"Password age: {age_days} days (>6 months)")
|
|
except ValueError:
|
|
pass
|
|
|
|
# Check SPN type
|
|
high_value_spns = ["MSSQLSvc", "HTTP", "exchangeMDB", "ldap"]
|
|
for spn_prefix in high_value_spns:
|
|
if target.spn.startswith(spn_prefix):
|
|
score += 10
|
|
risk_factors.append(f"High-value SPN type: {spn_prefix}")
|
|
break
|
|
|
|
target.risk_score = min(score, 100)
|
|
|
|
scored_targets.append({
|
|
"username": target.username,
|
|
"spn": target.spn,
|
|
"domain": target.domain,
|
|
"risk_score": target.risk_score,
|
|
"risk_level": (
|
|
"critical" if score >= 60
|
|
else "high" if score >= 40
|
|
else "medium" if score >= 20
|
|
else "low"
|
|
),
|
|
"risk_factors": risk_factors,
|
|
"password_last_set": target.password_last_set,
|
|
"admin_count": target.admin_count,
|
|
})
|
|
|
|
scored_targets.sort(key=lambda t: t["risk_score"], reverse=True)
|
|
return scored_targets
|
|
|
|
def generate_report(self) -> str:
|
|
"""Generate Kerberoasting analysis report."""
|
|
lines = []
|
|
lines.append("=" * 70)
|
|
lines.append("KERBEROASTING ANALYSIS REPORT")
|
|
lines.append(f"Generated: {datetime.now().isoformat()}")
|
|
lines.append("=" * 70)
|
|
|
|
# Detection results
|
|
if self.alerts:
|
|
lines.append(f"\nDETECTED KERBEROASTING ACTIVITY: {len(self.alerts)} alert(s)")
|
|
lines.append("-" * 70)
|
|
for i, alert in enumerate(self.alerts, 1):
|
|
lines.append(f"\n Alert #{i} [{alert.severity.upper()}]")
|
|
lines.append(f" Source: {alert.source_user} @ {alert.source_ip}")
|
|
lines.append(f" Time: {alert.timestamp}")
|
|
lines.append(f" Targets: {alert.target_count} services")
|
|
lines.append(f" Encryption: {', '.join(alert.encryption_types)}")
|
|
lines.append(f" Details: {alert.description}")
|
|
else:
|
|
lines.append("\nNo Kerberoasting activity detected in analyzed logs.")
|
|
|
|
# Target analysis
|
|
if self.targets:
|
|
lines.append(f"\nKERBEROASTABLE ACCOUNTS: {len(self.targets)}")
|
|
lines.append("-" * 70)
|
|
for target in sorted(self.targets, key=lambda t: t.risk_score, reverse=True):
|
|
level = (
|
|
"CRITICAL" if target.risk_score >= 60
|
|
else "HIGH" if target.risk_score >= 40
|
|
else "MEDIUM" if target.risk_score >= 20
|
|
else "LOW"
|
|
)
|
|
lines.append(
|
|
f" [{level}] {target.username} "
|
|
f"(Score: {target.risk_score}/100) "
|
|
f"SPN: {target.spn}"
|
|
)
|
|
|
|
lines.append("\nREMEDIATION RECOMMENDATIONS:")
|
|
lines.append("-" * 70)
|
|
lines.append(" 1. Convert service accounts to Group Managed Service Accounts (gMSA)")
|
|
lines.append(" 2. Enforce 25+ character passwords on remaining service accounts")
|
|
lines.append(" 3. Disable RC4 encryption via Group Policy")
|
|
lines.append(" 4. Add sensitive accounts to Protected Users group")
|
|
lines.append(" 5. Deploy SIEM rule for Event ID 4769 with RC4 encryption type")
|
|
lines.append(" 6. Create honeypot SPN accounts for early detection")
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
def main():
|
|
"""Demonstrate Kerberoasting detection and analysis."""
|
|
detector = KerberoastDetector()
|
|
|
|
# Simulate Event ID 4769 logs (Kerberoasting pattern)
|
|
sample_events = []
|
|
base_time = datetime(2025, 1, 15, 10, 30, 0)
|
|
services = [
|
|
"MSSQLSvc/SQL01", "HTTP/WEB01", "HOST/BACKUP01",
|
|
"MSSQLSvc/SQL02", "exchangeMDB/EX01", "HTTP/APP01",
|
|
"ldap/DC02", "HOST/FILE01",
|
|
]
|
|
for i, svc in enumerate(services):
|
|
sample_events.append({
|
|
"EventID": 4769,
|
|
"TimeCreated": (base_time + timedelta(seconds=i * 5)).isoformat(),
|
|
"EventData": {
|
|
"TargetUserName": "jsmith",
|
|
"ServiceName": svc,
|
|
"IpAddress": "10.10.10.50",
|
|
"TicketEncryptionType": "0x17",
|
|
"Status": "0x0",
|
|
"TicketOptions": "0x40810000",
|
|
},
|
|
})
|
|
|
|
detector.parse_event_log(sample_events)
|
|
detector.detect_kerberoasting()
|
|
|
|
# Analyze Kerberoastable targets
|
|
targets = [
|
|
KerberoastTarget(
|
|
username="svc_sql", spn="MSSQLSvc/SQL01.corp.local:1433",
|
|
domain="corp.local", password_last_set="2022-06-15T10:00:00",
|
|
admin_count=True, member_of=["Domain Admins"],
|
|
),
|
|
KerberoastTarget(
|
|
username="svc_web", spn="HTTP/web01.corp.local",
|
|
domain="corp.local", password_last_set="2024-01-20T14:00:00",
|
|
admin_count=False, member_of=["Web Admins"],
|
|
),
|
|
KerberoastTarget(
|
|
username="svc_backup", spn="HOST/backup01.corp.local",
|
|
domain="corp.local", password_last_set="2021-03-01T08:00:00",
|
|
admin_count=True, member_of=["Backup Operators"],
|
|
),
|
|
]
|
|
detector.analyze_kerberoast_targets(targets)
|
|
print(detector.generate_report())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|