Files
Anthropic-Cybersecurity-Skills/skills/detecting-pass-the-hash-attacks/scripts/agent.py
T
mukul975 c47eed6a64 Production hardening: security fixes, code quality, 724 skills complete
- 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
2026-03-19 13:26:49 +01:00

113 lines
4.3 KiB
Python

#!/usr/bin/env python3
"""Pass-the-Hash attack detection agent.
Detects NTLM hash reuse attacks by analyzing Windows Security Event ID 4624
for Type 3 NTLM logons with anomalous patterns across multiple targets.
"""
import argparse
import json
import re
from collections import defaultdict
from datetime import datetime
try:
import Evtx.Evtx as evtx
except ImportError:
evtx = None
LEGITIMATE_SOURCES = {"127.0.0.1", "::1", "-", ""}
def parse_logon_events(filepath):
if evtx is None:
return {"error": "python-evtx not installed: pip install python-evtx"}
events = []
with evtx.Evtx(filepath) as log:
for record in log.records():
xml = record.xml()
if "<EventID>4624</EventID>" not in xml:
continue
logon_type = re.search(r'<Data Name="LogonType">(\d+)', xml)
auth_pkg = re.search(r'<Data Name="AuthenticationPackageName">([^<]+)', xml)
account = re.search(r'<Data Name="TargetUserName">([^<]+)', xml)
domain = re.search(r'<Data Name="TargetDomainName">([^<]+)', xml)
src_ip = re.search(r'<Data Name="IpAddress">([^<]+)', xml)
computer = re.search(r'<Data Name="Computer">([^<]+)', xml)
time_match = re.search(r'SystemTime="([^"]+)"', xml)
lt = logon_type.group(1) if logon_type else ""
ap = auth_pkg.group(1) if auth_pkg else ""
if lt == "3" and "NTLM" in ap.upper():
events.append({
"timestamp": time_match.group(1) if time_match else "",
"logon_type": int(lt), "auth_package": ap.strip(),
"account": account.group(1) if account else "",
"domain": domain.group(1) if domain else "",
"source_ip": src_ip.group(1) if src_ip else "",
"computer": computer.group(1) if computer else "",
})
return events
def detect_pth_patterns(events, target_threshold=3):
if isinstance(events, dict) and "error" in events:
return [events]
findings = []
src_targets = defaultdict(lambda: {"computers": set(), "count": 0,
"source_ip": "", "account": ""})
for evt in events:
src = evt.get("source_ip", "")
if src in LEGITIMATE_SOURCES:
continue
key = f"{src}|{evt.get('account', '')}"
src_targets[key]["computers"].add(evt.get("computer", ""))
src_targets[key]["count"] += 1
src_targets[key]["source_ip"] = src
src_targets[key]["account"] = evt.get("account", "")
for key, data in src_targets.items():
target_count = len(data["computers"])
if target_count >= target_threshold:
findings.append({
"type": "ntlm_type3_multi_target",
"source_ip": data["source_ip"],
"account": data["account"],
"target_count": target_count,
"targets": list(data["computers"])[:20],
"total_logons": data["count"],
"severity": "CRITICAL" if target_count >= 10 else "HIGH",
"mitre": "T1550.002",
})
admin_sources = defaultdict(int)
for evt in events:
if evt.get("account", "").lower() in ("administrator", "admin"):
admin_sources[evt.get("source_ip", "")] += 1
for src, count in admin_sources.items():
if count >= 2 and src not in LEGITIMATE_SOURCES:
findings.append({
"type": "admin_ntlm", "source_ip": src,
"logon_count": count, "severity": "HIGH", "mitre": "T1550.002",
})
return findings
def main():
parser = argparse.ArgumentParser(description="Pass-the-Hash Detector")
parser.add_argument("--security-log", required=True, help="Windows Security EVTX")
parser.add_argument("--target-threshold", type=int, default=3)
args = parser.parse_args()
events = parse_logon_events(args.security_log)
findings = detect_pth_patterns(events, args.target_threshold)
ntlm_count = len(events) if isinstance(events, list) else 0
results = {
"timestamp": datetime.utcnow().isoformat() + "Z",
"total_ntlm_type3_logons": ntlm_count,
"findings": findings, "total_findings": len(findings),
}
print(json.dumps(results, indent=2))
if __name__ == "__main__":
main()