Files
Anthropic-Cybersecurity-Skills/skills/detecting-pass-the-ticket-attacks/scripts/agent.py
T
mukul975 757f1c8eae Add 5 new cybersecurity skills with full implementations
- implementing-vulnerability-management-with-greenbone: python-gvm GMP API, scan task creation, XML report parsing
- detecting-email-account-compromise: Microsoft Graph inbox rules, impossible travel detection, OAuth grant analysis
- performing-threat-intelligence-sharing-with-misp: PyMISP event creation, attribute management, sharing validation
- analyzing-cobaltstrike-malleable-c2-profiles: dissect.cobaltstrike C2Profile parsing, Suricata rule generation
- hunting-for-registry-run-key-persistence: Sysmon Event 13 analysis, T1547.001 detection, Sigma rule generation
2026-03-11 00:41:59 +01:00

204 lines
8.1 KiB
Python

#!/usr/bin/env python3
"""Detect Kerberos Pass-the-Ticket attacks via Windows Event ID 4768/4769/4771 analysis."""
import json
import argparse
import xml.etree.ElementTree as ET
from collections import defaultdict
from datetime import datetime
def parse_evtx_xml(xml_path):
"""Parse exported Windows Security event log XML for Kerberos events."""
tree = ET.parse(xml_path)
root = tree.getroot()
ns = {"e": "http://schemas.microsoft.com/win/2004/08/events/event"}
events = []
for event_el in root.findall(".//e:Event", ns):
sys_el = event_el.find("e:System", ns)
event_id = int(sys_el.find("e:EventID", ns).text)
if event_id not in (4768, 4769, 4771):
continue
time_created = sys_el.find("e:TimeCreated", ns).attrib.get("SystemTime", "")
data_el = event_el.find("e:EventData", ns)
fields = {}
for d in data_el.findall("e:Data", ns):
fields[d.attrib.get("Name", "")] = d.text or ""
events.append({
"event_id": event_id,
"timestamp": time_created,
"target_user": fields.get("TargetUserName", ""),
"target_domain": fields.get("TargetDomainName", ""),
"ip_address": fields.get("IpAddress", ""),
"service_name": fields.get("ServiceName", ""),
"ticket_encryption_type": fields.get("TicketEncryptionType", ""),
"status": fields.get("Status", ""),
"pre_auth_type": fields.get("PreAuthType", ""),
})
return events
def detect_rc4_downgrade(events):
"""Detect RC4 encryption downgrade (TicketEncryptionType 0x17) in TGS requests."""
alerts = []
for ev in events:
enc_type = ev["ticket_encryption_type"]
if enc_type in ("0x17", "23"):
alerts.append({
"detection": "RC4 Encryption Downgrade",
"mitre_technique": "T1550.003",
"event_id": ev["event_id"],
"timestamp": ev["timestamp"],
"user": ev["target_user"],
"domain": ev["target_domain"],
"service": ev["service_name"],
"ip_address": ev["ip_address"],
"encryption_type": enc_type,
"severity": "high",
"description": "RC4 (0x17) ticket encryption detected; may indicate Pass-the-Ticket or Kerberoasting",
})
return alerts
def detect_cross_host_ticket_reuse(events):
"""Detect same user TGS requests from multiple source IPs within short window."""
user_ips = defaultdict(set)
user_events = defaultdict(list)
for ev in events:
if ev["event_id"] == 4769 and ev["target_user"] and ev["ip_address"]:
key = f"{ev['target_user']}@{ev['target_domain']}"
user_ips[key].add(ev["ip_address"])
user_events[key].append(ev)
alerts = []
for user, ips in user_ips.items():
if len(ips) >= 2:
sample = user_events[user][:5]
alerts.append({
"detection": "Cross-Host Ticket Reuse",
"mitre_technique": "T1550.003",
"user": user,
"source_ips": list(ips),
"ip_count": len(ips),
"request_count": len(user_events[user]),
"severity": "critical",
"sample_timestamps": [e["timestamp"] for e in sample],
"description": "Same user ticket used from multiple IPs, indicating stolen ticket replay",
})
return alerts
def detect_anomalous_tgs_volume(events, threshold=50):
"""Detect users requesting abnormally high number of TGS tickets."""
user_tgs = defaultdict(int)
for ev in events:
if ev["event_id"] == 4769 and ev["target_user"]:
user_tgs[f"{ev['target_user']}@{ev['target_domain']}"] += 1
alerts = []
for user, count in user_tgs.items():
if count >= threshold:
alerts.append({
"detection": "Anomalous TGS Volume",
"mitre_technique": "T1550.003",
"user": user,
"tgs_request_count": count,
"threshold": threshold,
"severity": "high",
"description": f"User requested {count} service tickets (threshold: {threshold})",
})
return alerts
def detect_preauth_failures(events, threshold=10):
"""Detect excessive Kerberos pre-authentication failures (Event ID 4771)."""
user_failures = defaultdict(int)
for ev in events:
if ev["event_id"] == 4771:
user_failures[f"{ev['target_user']}@{ev['target_domain']}"] += 1
alerts = []
for user, count in user_failures.items():
if count >= threshold:
alerts.append({
"detection": "Excessive Pre-Auth Failures",
"mitre_technique": "T1110.003",
"user": user,
"failure_count": count,
"severity": "medium",
"description": f"{count} Kerberos pre-authentication failures detected",
})
return alerts
def generate_splunk_queries():
"""Return SPL queries for Splunk-based PtT detection."""
return {
"rc4_downgrade": (
'index=wineventlog sourcetype="WinEventLog:Security" EventCode=4769 '
'TicketEncryptionType=0x17 | stats count by TargetUserName, IpAddress, ServiceName'
),
"cross_host_reuse": (
'index=wineventlog EventCode=4769 | stats dc(IpAddress) as ip_count, '
'values(IpAddress) as source_ips by TargetUserName | where ip_count > 1'
),
"tgs_volume_anomaly": (
'index=wineventlog EventCode=4769 | stats count by TargetUserName '
'| where count > 50 | sort -count'
),
"preauth_failures": (
'index=wineventlog EventCode=4771 | stats count by TargetUserName, IpAddress '
'| where count > 10'
),
}
def main():
parser = argparse.ArgumentParser(description="Pass-the-Ticket Attack Detector")
parser.add_argument("--evtx-xml", help="Path to exported Security event log XML")
parser.add_argument("--tgs-threshold", type=int, default=50, help="TGS volume alert threshold")
parser.add_argument("--preauth-threshold", type=int, default=10, help="Pre-auth failure threshold")
parser.add_argument("--output", default="ptt_detection_report.json", help="Output report path")
parser.add_argument("--show-splunk", action="store_true", help="Print Splunk SPL queries")
args = parser.parse_args()
if args.show_splunk:
queries = generate_splunk_queries()
for name, spl in queries.items():
print(f"\n--- {name} ---\n{spl}")
return
if not args.evtx_xml:
print("[!] Provide --evtx-xml path to exported Windows Security event log XML")
print("[*] Or use --show-splunk to get Splunk detection queries")
return
events = parse_evtx_xml(args.evtx_xml)
print(f"[+] Parsed {len(events)} Kerberos events (4768/4769/4771)")
rc4_alerts = detect_rc4_downgrade(events)
reuse_alerts = detect_cross_host_ticket_reuse(events)
volume_alerts = detect_anomalous_tgs_volume(events, args.tgs_threshold)
preauth_alerts = detect_preauth_failures(events, args.preauth_threshold)
report = {
"analysis_time": datetime.utcnow().isoformat() + "Z",
"total_kerberos_events": len(events),
"detections": {
"rc4_downgrade": rc4_alerts,
"cross_host_ticket_reuse": reuse_alerts,
"anomalous_tgs_volume": volume_alerts,
"preauth_failures": preauth_alerts,
},
"total_alerts": len(rc4_alerts) + len(reuse_alerts) + len(volume_alerts) + len(preauth_alerts),
"mitre_techniques": ["T1550.003", "T1558.003", "T1110.003"],
"splunk_queries": generate_splunk_queries(),
}
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
print(f"[+] Alerts: RC4={len(rc4_alerts)}, Reuse={len(reuse_alerts)}, "
f"Volume={len(volume_alerts)}, PreAuth={len(preauth_alerts)}")
print(f"[+] Report saved to {args.output}")
if __name__ == "__main__":
main()