npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
NIST CSF 2.0
Overview
A Golden Ticket attack (MITRE ATT&CK T1558.001) involves forging a Kerberos Ticket Granting Ticket (TGT) using the krbtgt account NTLM hash, granting unrestricted access to any service in the Active Directory domain. This skill detects Golden Ticket usage by analyzing Event ID 4769 for RC4 encryption type (0x17) in environments enforcing AES, identifying tickets with abnormal lifetimes exceeding domain policy, correlating TGS requests with missing corresponding TGT requests (Event ID 4768), and detecting krbtgt password age anomalies.
When to Use
- When investigating security incidents that require detecting golden ticket forgery
- When building detection rules or threat hunting queries for this domain
- When SOC analysts need structured procedures for this analysis type
- When validating security monitoring coverage for related attack techniques
Prerequisites
- Windows Domain Controller with Kerberos audit logging enabled
- Splunk or Elastic SIEM ingesting Windows Security event logs
- Python 3.8+ for offline event log analysis
- Knowledge of domain Kerberos encryption policy (AES vs RC4)
Steps
- Audit domain Kerberos encryption policy to establish AES-only baseline
- Forward Event IDs 4768 and 4769 to SIEM platform
- Detect RC4 (0x17) encryption in TGS requests where AES is enforced
- Identify TGS requests without corresponding TGT requests (forged ticket indicator)
- Alert on ticket lifetimes exceeding MaxTicketAge domain policy
- Monitor krbtgt account password age and last reset date
- Correlate findings with host/user context for risk scoring
Expected Output
JSON report with Golden Ticket indicators including RC4 downgrades, orphaned TGS requests, abnormal ticket lifetimes, and risk-scored alerts with MITRE ATT&CK technique mapping.
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 1
api-reference.md2.3 KB
Golden Ticket Forgery Detection API Reference
Windows Security Event IDs
Event ID 4768 - TGT Requested (AS-REQ)
Key Fields:
TargetUserName - Account requesting TGT
TargetDomainName - Domain of requesting account
IpAddress - Source IP
TicketEncryptionType - 0x12 (AES256), 0x11 (AES128), 0x17 (RC4)
PreAuthType - 15 = PA-ENC-TIMESTAMP (normal)Event ID 4769 - TGS Requested (TGS-REQ)
Key Fields:
TargetUserName - Account using the ticket
ServiceName - SPN of target service
IpAddress - Source IP of requestor
TicketEncryptionType - 0x17 = RC4 (Golden Ticket indicator)
TicketOptions - Kerberos ticket flags
LogonGuid - Correlate with Event 4624Detection Indicators
| Indicator | Normal | Golden Ticket |
|---|---|---|
| TicketEncryptionType | 0x12 (AES256) | 0x17 (RC4-HMAC) |
| TGT Lifetime | <= 10 hours | Often 10+ years |
| TGS without TGT | Always preceded by 4768 | 4769 without 4768 |
| Domain field | Matches domain | May be blank or incorrect |
Splunk SPL Queries
RC4 TGS Detection (Golden Ticket)
index=wineventlog sourcetype="WinEventLog:Security" EventCode=4769
TicketEncryptionType=0x17
ServiceName!="krbtgt"
| stats count by TargetUserName, IpAddress, ServiceName
| where count > 3
| sort -countOrphaned TGS (No Prior TGT)
index=wineventlog EventCode=4769
| join type=left TargetUserName
[search index=wineventlog EventCode=4768
| dedup TargetUserName | fields TargetUserName]
| where isnull(TargetUserName)
| stats count by TargetUserName, IpAddresskrbtgt Service Anomaly
index=wineventlog EventCode=4769 ServiceName="krbtgt*"
| table _time, TargetUserName, IpAddress, TicketEncryptionTypeElastic KQL
RC4 Downgrade in Elastic
event.code: "4769" AND winlog.event_data.TicketEncryptionType: "0x17"
AND NOT winlog.event_data.ServiceName: "krbtgt"MITRE ATT&CK
| Technique | ID | Description |
|---|---|---|
| Steal or Forge Kerberos Tickets: Golden Ticket | T1558.001 | Forge TGT using krbtgt hash |
CLI Usage
python agent.py --evtx-xml security_events.xml --output golden_ticket_report.json
python agent.py --show-splunk
python agent.py --evtx-xml events.xml --max-ticket-hours 8Scripts 1
agent.py8.3 KB
#!/usr/bin/env python3
"""Detect Kerberos Golden Ticket forgery via Windows Security event log analysis."""
import json
import argparse
import xml.etree.ElementTree as ET
from collections import defaultdict
from datetime import datetime
def parse_security_events(xml_path):
"""Parse exported Windows Security event log XML for Kerberos events 4768/4769."""
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):
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, **fields})
return events
def detect_rc4_in_aes_environment(events):
"""Detect RC4 encryption (0x17) in TGS requests where AES should be enforced."""
alerts = []
for ev in events:
if ev["event_id"] != 4769:
continue
enc_type = ev.get("TicketEncryptionType", "")
if enc_type in ("0x17", "23"):
alerts.append({
"detection": "RC4 Encryption in TGS Request",
"mitre_technique": "T1558.001",
"timestamp": ev["timestamp"],
"user": ev.get("TargetUserName", ""),
"domain": ev.get("TargetDomainName", ""),
"service": ev.get("ServiceName", ""),
"ip_address": ev.get("IpAddress", ""),
"encryption_type": enc_type,
"severity": "critical",
"description": "RC4 (0x17) encryption detected in TGS request; Golden Ticket indicator in AES-enforced environments",
})
return alerts
def detect_orphaned_tgs(events):
"""Detect TGS requests (4769) without preceding TGT request (4768) from same user."""
tgt_users = set()
for ev in events:
if ev["event_id"] == 4768:
tgt_users.add(f"{ev.get('TargetUserName', '')}@{ev.get('TargetDomainName', '')}")
alerts = []
tgs_without_tgt = defaultdict(list)
for ev in events:
if ev["event_id"] != 4769:
continue
user_key = f"{ev.get('TargetUserName', '')}@{ev.get('TargetDomainName', '')}"
if user_key not in tgt_users and ev.get("TargetUserName", ""):
tgs_without_tgt[user_key].append(ev)
for user, tgs_events in tgs_without_tgt.items():
alerts.append({
"detection": "Orphaned TGS Request (No Preceding TGT)",
"mitre_technique": "T1558.001",
"user": user,
"tgs_count": len(tgs_events),
"services": list({e.get("ServiceName", "") for e in tgs_events}),
"source_ips": list({e.get("IpAddress", "") for e in tgs_events}),
"first_seen": tgs_events[0]["timestamp"],
"severity": "critical",
"description": "TGS requests without corresponding TGT; forged ticket likely",
})
return alerts
def detect_abnormal_ticket_lifetime(events, max_lifetime_hours=10):
"""Detect tickets with lifetime exceeding domain policy (default MaxTicketAge=10h)."""
user_tgt_times = defaultdict(list)
for ev in events:
if ev["event_id"] == 4768 and ev.get("TargetUserName"):
try:
ts = datetime.fromisoformat(ev["timestamp"].replace("Z", "+00:00"))
user_tgt_times[ev["TargetUserName"]].append(ts)
except (ValueError, AttributeError):
continue
alerts = []
for user, times in user_tgt_times.items():
if len(times) < 2:
continue
times.sort()
for i in range(1, len(times)):
gap_hours = (times[i] - times[i - 1]).total_seconds() / 3600
if gap_hours > max_lifetime_hours * 2:
alerts.append({
"detection": "Abnormal TGT Renewal Gap",
"mitre_technique": "T1558.001",
"user": user,
"gap_hours": round(gap_hours, 2),
"max_expected_hours": max_lifetime_hours,
"severity": "high",
"description": f"TGT renewal gap of {gap_hours:.1f}h exceeds 2x MaxTicketAge ({max_lifetime_hours}h)",
})
return alerts
def detect_krbtgt_service_anomaly(events):
"""Detect TGS requests targeting the krbtgt service (unusual and suspicious)."""
alerts = []
for ev in events:
if ev["event_id"] == 4769 and ev.get("ServiceName", "").lower().startswith("krbtgt"):
alerts.append({
"detection": "TGS Request Targeting krbtgt Service",
"mitre_technique": "T1558.001",
"timestamp": ev["timestamp"],
"user": ev.get("TargetUserName", ""),
"service": ev.get("ServiceName", ""),
"ip_address": ev.get("IpAddress", ""),
"severity": "critical",
"description": "Direct TGS request for krbtgt service is highly anomalous",
})
return alerts
def generate_splunk_queries():
"""Return Splunk SPL queries for Golden Ticket detection."""
return {
"rc4_downgrade": (
'index=wineventlog sourcetype="WinEventLog:Security" EventCode=4769 '
'TicketEncryptionType=0x17 ServiceName!="krbtgt" '
'| stats count by TargetUserName, IpAddress, ServiceName'
),
"orphaned_tgs": (
'index=wineventlog EventCode=4769 '
'| join type=left TargetUserName [search index=wineventlog EventCode=4768 '
'| rename TargetUserName as tgt_user | dedup tgt_user | fields tgt_user] '
'| where isnull(tgt_user) | stats count by TargetUserName, IpAddress'
),
"krbtgt_tgs": (
'index=wineventlog EventCode=4769 ServiceName="krbtgt*" '
'| table _time, TargetUserName, IpAddress, ServiceName, TicketEncryptionType'
),
}
def main():
parser = argparse.ArgumentParser(description="Golden Ticket Forgery Detector")
parser.add_argument("--evtx-xml", help="Path to exported Security event log XML")
parser.add_argument("--max-ticket-hours", type=int, default=10, help="MaxTicketAge in hours (default: 10)")
parser.add_argument("--output", default="golden_ticket_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:
for name, spl in generate_splunk_queries().items():
print(f"\n--- {name} ---\n{spl}")
return
if not args.evtx_xml:
print("[!] Provide --evtx-xml path or use --show-splunk for detection queries")
return
events = parse_security_events(args.evtx_xml)
print(f"[+] Parsed {len(events)} Kerberos events (4768/4769)")
rc4_alerts = detect_rc4_in_aes_environment(events)
orphan_alerts = detect_orphaned_tgs(events)
lifetime_alerts = detect_abnormal_ticket_lifetime(events, args.max_ticket_hours)
krbtgt_alerts = detect_krbtgt_service_anomaly(events)
report = {
"analysis_time": datetime.utcnow().isoformat() + "Z",
"total_events": len(events),
"detections": {
"rc4_encryption_downgrade": rc4_alerts,
"orphaned_tgs_requests": orphan_alerts,
"abnormal_ticket_lifetime": lifetime_alerts,
"krbtgt_service_anomaly": krbtgt_alerts,
},
"total_alerts": len(rc4_alerts) + len(orphan_alerts) + len(lifetime_alerts) + len(krbtgt_alerts),
"mitre_techniques": ["T1558.001"],
"splunk_queries": generate_splunk_queries(),
}
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
print(f"[+] RC4 downgrades: {len(rc4_alerts)}")
print(f"[+] Orphaned TGS: {len(orphan_alerts)}")
print(f"[+] Lifetime anomalies: {len(lifetime_alerts)}")
print(f"[+] krbtgt anomalies: {len(krbtgt_alerts)}")
print(f"[+] Report saved to {args.output}")
if __name__ == "__main__":
main()