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