Files
Anthropic-Cybersecurity-Skills/skills/performing-lateral-movement-detection/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

221 lines
8.8 KiB
Python

#!/usr/bin/env python3
"""Agent for performing lateral movement detection.
Analyzes Windows event logs and network flow data to detect
Pass-the-Hash, PsExec, WMI, RDP, and SMB-based lateral movement
mapped to MITRE ATT&CK TA0008 techniques.
"""
import json
import sys
import csv
from collections import defaultdict
from datetime import datetime
from pathlib import Path
class LateralMovementDetector:
"""Detects lateral movement patterns from log data."""
def __init__(self, output_dir):
self.output_dir = Path(output_dir)
self.output_dir.mkdir(parents=True, exist_ok=True)
self.auth_events = []
self.process_events = []
self.network_flows = []
def load_auth_events(self, csv_path):
"""Load Windows authentication events (4624, 4625, 4648, 4672)."""
with open(csv_path, "r") as f:
for row in csv.DictReader(f):
self.auth_events.append(row)
def load_process_events(self, csv_path):
"""Load Sysmon process creation events (EventCode 1)."""
with open(csv_path, "r") as f:
for row in csv.DictReader(f):
self.process_events.append(row)
def load_network_flows(self, csv_path):
"""Load network flow data (NetFlow/Zeek)."""
with open(csv_path, "r") as f:
for row in csv.DictReader(f):
self.network_flows.append(row)
def detect_pass_the_hash(self):
"""Detect Pass-the-Hash via NTLM Type 3 logons to multiple hosts."""
ntlm_logons = defaultdict(lambda: {"targets": set(), "count": 0, "events": []})
for event in self.auth_events:
if (event.get("EventCode") == "4624" and
event.get("Logon_Type") == "3" and
event.get("AuthenticationPackageName", "").upper() == "NTLM"):
user = event.get("TargetUserName", "")
src = event.get("src_ip", event.get("IpAddress", ""))
target = event.get("ComputerName", "")
if user and not user.endswith("$") and user != "ANONYMOUS LOGON":
key = f"{src}|{user}"
ntlm_logons[key]["targets"].add(target)
ntlm_logons[key]["count"] += 1
ntlm_logons[key]["events"].append(event)
findings = []
for key, data in ntlm_logons.items():
if len(data["targets"]) > 3:
src_ip, user = key.split("|", 1)
findings.append({
"technique": "T1550.002",
"name": "Pass-the-Hash",
"src_ip": src_ip,
"user": user,
"unique_targets": len(data["targets"]),
"total_logons": data["count"],
"targets": sorted(data["targets"]),
})
return findings
def detect_psexec(self):
"""Detect PsExec execution via process creation and service events."""
findings = []
for event in self.process_events:
image = event.get("Image", "").lower()
parent = event.get("ParentImage", "").lower()
if "psexec" in image or "psexesvc" in image or "psexesvc" in parent:
findings.append({
"technique": "T1021.002",
"name": "PsExec Execution",
"computer": event.get("Computer", ""),
"user": event.get("User", ""),
"image": event.get("Image", ""),
"parent": event.get("ParentImage", ""),
"cmdline": event.get("CommandLine", ""),
"timestamp": event.get("timestamp", event.get("UtcTime", "")),
})
return findings
def detect_wmi_execution(self):
"""Detect WMI remote execution via WmiPrvSE child processes."""
findings = []
for event in self.process_events:
parent = event.get("ParentImage", "").lower()
image = event.get("Image", "").lower()
if "wmiprvse" in parent and ("cmd.exe" in image or "powershell" in image):
findings.append({
"technique": "T1047",
"name": "WMI Remote Execution",
"computer": event.get("Computer", ""),
"user": event.get("User", ""),
"image": event.get("Image", ""),
"cmdline": event.get("CommandLine", ""),
"timestamp": event.get("timestamp", ""),
})
return findings
def detect_rdp_lateral(self):
"""Detect RDP lateral movement via Logon_Type 10."""
rdp_sessions = defaultdict(lambda: {"targets": set(), "count": 0})
for event in self.auth_events:
if event.get("EventCode") == "4624" and event.get("Logon_Type") == "10":
src = event.get("src_ip", event.get("IpAddress", ""))
user = event.get("TargetUserName", "")
target = event.get("ComputerName", "")
key = f"{src}|{user}"
rdp_sessions[key]["targets"].add(target)
rdp_sessions[key]["count"] += 1
findings = []
for key, data in rdp_sessions.items():
if len(data["targets"]) > 2:
src_ip, user = key.split("|", 1)
findings.append({
"technique": "T1021.001",
"name": "RDP Lateral Movement",
"src_ip": src_ip,
"user": user,
"unique_targets": len(data["targets"]),
"targets": sorted(data["targets"]),
})
return findings
def detect_smb_scanning(self):
"""Detect mass SMB connections indicating lateral movement."""
smb_sources = defaultdict(lambda: {"targets": set(), "bytes": 0})
for flow in self.network_flows:
if flow.get("dest_port") == "445":
src = flow.get("src_ip", "")
dst = flow.get("dest_ip", "")
smb_sources[src]["targets"].add(dst)
smb_sources[src]["bytes"] += int(flow.get("bytes", 0))
findings = []
for src, data in smb_sources.items():
if len(data["targets"]) > 10:
findings.append({
"technique": "T1021.002",
"name": "SMB Mass Connection",
"src_ip": src,
"unique_targets": len(data["targets"]),
"total_bytes": data["bytes"],
"severity": "CRITICAL" if len(data["targets"]) > 50 else "HIGH",
})
return findings
def build_movement_graph(self):
"""Build a source->destination graph of lateral movement."""
edges = defaultdict(int)
for event in self.auth_events:
if event.get("EventCode") == "4624" and event.get("Logon_Type") in ("3", "10"):
src = event.get("src_ip", event.get("IpAddress", ""))
dst = event.get("ComputerName", "")
user = event.get("TargetUserName", "")
if src and dst:
edges[f"{src} -> {dst} ({user})"] += 1
return dict(sorted(edges.items(), key=lambda x: x[1], reverse=True)[:50])
def generate_report(self):
"""Run all detections and generate a comprehensive report."""
report = {
"report_date": datetime.utcnow().isoformat(),
"detections": {
"pass_the_hash": self.detect_pass_the_hash(),
"psexec": self.detect_psexec(),
"wmi_execution": self.detect_wmi_execution(),
"rdp_lateral": self.detect_rdp_lateral(),
"smb_scanning": self.detect_smb_scanning(),
},
"movement_graph": self.build_movement_graph(),
}
total_findings = sum(len(v) for v in report["detections"].values())
report["summary"] = {
"total_findings": total_findings,
"techniques_detected": [k for k, v in report["detections"].items() if v],
}
report_path = self.output_dir / "lateral_movement_report.json"
with open(report_path, "w") as f:
json.dump(report, f, indent=2, default=list)
print(json.dumps(report, indent=2, default=list))
return report
def main():
if len(sys.argv) < 3:
print("Usage: agent.py <auth_events_csv> <output_dir> [process_csv] [flows_csv]")
sys.exit(1)
auth_csv = sys.argv[1]
output_dir = sys.argv[2]
detector = LateralMovementDetector(output_dir)
detector.load_auth_events(auth_csv)
if len(sys.argv) > 3:
detector.load_process_events(sys.argv[3])
if len(sys.argv) > 4:
detector.load_network_flows(sys.argv[4])
detector.generate_report()
if __name__ == "__main__":
main()