#!/usr/bin/env python3 """Lateral movement detection agent using Splunk SPL query generation. Generates and analyzes SPL queries for detecting lateral movement techniques including pass-the-hash, RDP pivoting, WMI/PSExec execution, and SMB abuse. """ import argparse import json from datetime import datetime LATERAL_MOVEMENT_QUERIES = { "pass_the_hash": { "mitre": "T1550.002", "severity": "CRITICAL", "spl": """index=wineventlog EventCode=4624 Logon_Type=3 | where Authentication_Package="NTLM" AND Logon_Process="NtLmSsp" | where NOT match(Source_Network_Address, "^(127\\.0\\.0\\.1|::1|-)") | stats count dc(Computer) as target_count values(Computer) as targets by Source_Network_Address Account_Name | where target_count > 3 | sort -target_count""" }, "psexec_execution": { "mitre": "T1569.002", "severity": "HIGH", "spl": """index=sysmon EventCode=1 | where (ParentImage="*\\services.exe" AND Image="*\\PSEXESVC.exe") OR (Image="*\\psexec.exe" OR Image="*\\psexec64.exe") | stats count by Image, ParentImage, CommandLine, Computer, User | sort -count""" }, "wmi_remote_execution": { "mitre": "T1047", "severity": "HIGH", "spl": """index=sysmon EventCode=1 | where (Image="*\\wmiprvse.exe" AND ParentImage="*\\svchost.exe") | where CommandLine!="" | stats count by CommandLine, Computer, User | sort -count""" }, "rdp_pivoting": { "mitre": "T1021.001", "severity": "MEDIUM", "spl": """index=wineventlog EventCode=4624 Logon_Type=10 | stats count dc(Computer) as rdp_targets values(Computer) as targets by Source_Network_Address Account_Name | where rdp_targets > 3 | sort -rdp_targets""" }, "smb_lateral": { "mitre": "T1021.002", "severity": "HIGH", "spl": """index=network dest_port=445 | stats count dc(dest_ip) as smb_targets values(dest_ip) as targets by src_ip | where smb_targets > 5 | sort -smb_targets""" }, "winrm_execution": { "mitre": "T1021.006", "severity": "HIGH", "spl": """index=sysmon EventCode=1 | where Image="*\\wsmprovhost.exe" OR (ParentImage="*\\winrshost.exe") | stats count by Image, CommandLine, Computer, User | sort -count""" }, "service_creation": { "mitre": "T1543.003", "severity": "HIGH", "spl": """index=wineventlog EventCode=7045 | where Service_Type="user mode service" | stats count by Service_Name, Service_File_Name, Computer | where match(Service_File_Name, "(cmd|powershell|\\\\\\\\|%COMSPEC%)") | sort -count""" }, "scheduled_task_remote": { "mitre": "T1053.005", "severity": "HIGH", "spl": """index=sysmon EventCode=1 Image="*\\schtasks.exe" | where match(CommandLine, "/create.*/s\\s") | stats count by CommandLine, Computer, User | sort -count""" }, } def generate_queries(techniques=None): if techniques: selected = {k: v for k, v in LATERAL_MOVEMENT_QUERIES.items() if k in techniques} else: selected = LATERAL_MOVEMENT_QUERIES return [{"technique": name, **details} for name, details in selected.items()] def parse_splunk_results(filepath): findings = [] with open(filepath, "r") as f: try: data = json.load(f) results = data.get("results", data if isinstance(data, list) else [data]) except json.JSONDecodeError: f.seek(0) import csv reader = csv.DictReader(f) results = list(reader) for row in results: target_count = int(row.get("target_count", row.get("dc(Computer)", 0))) if target_count >= 3: findings.append({ "source": row.get("Source_Network_Address", row.get("src_ip", "")), "user": row.get("Account_Name", row.get("User", "")), "target_count": target_count, "targets": row.get("targets", row.get("Computer", "")), "severity": "CRITICAL" if target_count >= 10 else "HIGH", }) return findings def main(): parser = argparse.ArgumentParser(description="Lateral Movement Detector (Splunk SPL)") parser.add_argument("--generate-queries", action="store_true", help="Generate SPL queries") parser.add_argument("--techniques", nargs="+", choices=list(LATERAL_MOVEMENT_QUERIES.keys()), help="Specific techniques to query") parser.add_argument("--parse-results", help="Parse Splunk JSON/CSV results file") args = parser.parse_args() results = {"timestamp": datetime.utcnow().isoformat() + "Z"} if args.generate_queries: results["queries"] = generate_queries(args.techniques) results["total_queries"] = len(results["queries"]) if args.parse_results: findings = parse_splunk_results(args.parse_results) results["findings"] = findings results["total_findings"] = len(findings) print(json.dumps(results, indent=2)) if __name__ == "__main__": main()