Files
Anthropic-Cybersecurity-Skills/skills/detecting-lateral-movement-with-splunk/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

143 lines
4.9 KiB
Python

#!/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()