mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-08-12 13:23:19 +03:00
Initial commit - 611 cybersecurity skills across all subdomains
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
---
|
||||
name: detecting-lateral-movement-with-splunk
|
||||
description: Detect adversary lateral movement across networks using Splunk SPL queries against Windows authentication logs, SMB traffic, and remote service abuse.
|
||||
domain: cybersecurity
|
||||
subdomain: threat-hunting
|
||||
tags: [threat-hunting, mitre-attack, lateral-movement, splunk, siem, proactive-detection, ta0008]
|
||||
version: "1.0"
|
||||
author: mahipal
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Detecting Lateral Movement with Splunk
|
||||
|
||||
## When to Use
|
||||
|
||||
- When hunting for adversary movement between compromised systems
|
||||
- After detecting credential theft to trace subsequent lateral activity
|
||||
- When investigating unusual authentication patterns across the network
|
||||
- During incident response to scope the breadth of compromise
|
||||
- When proactively hunting for TA0008 (Lateral Movement) techniques
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Splunk Enterprise or Splunk Cloud with Windows event data ingested
|
||||
- Windows Security Event Logs forwarded (4624, 4625, 4648, 4672, 4768, 4769)
|
||||
- Sysmon deployed for process creation and network connection data
|
||||
- Network flow data or firewall logs for SMB/RDP/WinRM correlation
|
||||
- Active Directory user and group membership reference data
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Define Lateral Movement Scope**: Identify which lateral movement techniques to hunt (RDP, SMB/Admin Shares, WinRM, PsExec, WMI, DCOM, SSH).
|
||||
2. **Query Authentication Events**: Use SPL to search for Type 3 (Network) and Type 10 (RemoteInteractive) logons across the environment.
|
||||
3. **Build Authentication Graphs**: Map source-to-destination authentication relationships to identify unusual connection patterns.
|
||||
4. **Detect First-Time Relationships**: Identify new source-destination pairs that have not been seen in the historical baseline.
|
||||
5. **Correlate with Process Activity**: Link authentication events to subsequent process creation on destination hosts.
|
||||
6. **Identify Anomalous Patterns**: Flag lateral movement to sensitive servers, unusual hours, service account misuse, or rapid multi-host access.
|
||||
7. **Report and Contain**: Document lateral movement path, affected systems, and coordinate containment response.
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Concept | Description |
|
||||
|---------|-------------|
|
||||
| T1021 | Remote Services (parent technique) |
|
||||
| T1021.001 | Remote Desktop Protocol (RDP) |
|
||||
| T1021.002 | SMB/Windows Admin Shares |
|
||||
| T1021.003 | Distributed COM (DCOM) |
|
||||
| T1021.004 | SSH |
|
||||
| T1021.006 | Windows Remote Management (WinRM) |
|
||||
| T1570 | Lateral Tool Transfer |
|
||||
| T1047 | Windows Management Instrumentation |
|
||||
| T1569.002 | Service Execution (PsExec) |
|
||||
| Logon Type 3 | Network logon (SMB, WinRM, mapped drives) |
|
||||
| Logon Type 10 | Remote Interactive (RDP) |
|
||||
| Event ID 4624 | Successful logon |
|
||||
| Event ID 4648 | Explicit credential logon (runas, PsExec) |
|
||||
|
||||
## Tools & Systems
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| Splunk Enterprise | SIEM for log aggregation and SPL queries |
|
||||
| Splunk Enterprise Security | Threat detection and notable events |
|
||||
| Windows Event Forwarding | Centralize Windows logs |
|
||||
| Sysmon | Detailed process and network telemetry |
|
||||
| BloodHound | AD attack path analysis |
|
||||
| PingCastle | AD security assessment |
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
1. **PsExec Lateral Movement**: Adversary uses PsExec to execute commands on remote systems via SMB, generating Type 3 logon with ADMIN$ share access.
|
||||
2. **RDP Pivoting**: Attacker RDPs to internal systems using stolen credentials, creating Type 10 logon events.
|
||||
3. **WMI Remote Execution**: Adversary uses WMIC process call create to spawn processes on remote hosts.
|
||||
4. **WinRM PowerShell Remoting**: Attacker uses Enter-PSSession or Invoke-Command to execute code on remote systems.
|
||||
5. **Pass-the-Hash via SMB**: Compromised NTLM hashes used to authenticate to remote systems without knowing the plaintext password.
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
Hunt ID: TH-LATMOV-[DATE]-[SEQ]
|
||||
Movement Type: [RDP/SMB/WinRM/WMI/DCOM/PsExec]
|
||||
Source Host: [Hostname/IP]
|
||||
Destination Host: [Hostname/IP]
|
||||
Account Used: [Username]
|
||||
Logon Type: [3/10/other]
|
||||
First Seen: [Timestamp]
|
||||
Event Count: [Number of events]
|
||||
Risk Level: [Critical/High/Medium/Low]
|
||||
Lateral Movement Path: [A -> B -> C -> D]
|
||||
```
|
||||
@@ -0,0 +1,45 @@
|
||||
# Lateral Movement Hunt Template
|
||||
|
||||
## Hunt Metadata
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Hunt ID | TH-LATMOV-YYYY-MM-DD-NNN |
|
||||
| Analyst | |
|
||||
| Date | |
|
||||
| Status | [ ] In Progress / [ ] Complete |
|
||||
|
||||
## Hypothesis
|
||||
> [e.g., "Adversaries are moving laterally via SMB admin shares using compromised domain admin credentials."]
|
||||
|
||||
## Techniques Investigated
|
||||
- [ ] T1021.001 - RDP
|
||||
- [ ] T1021.002 - SMB/Admin Shares
|
||||
- [ ] T1021.006 - WinRM
|
||||
- [ ] T1047 - WMI
|
||||
- [ ] T1569.002 - PsExec/Service Execution
|
||||
- [ ] T1550.002 - Pass the Hash
|
||||
- [ ] T1570 - Lateral Tool Transfer
|
||||
|
||||
## Lateral Movement Path Map
|
||||
|
||||
```
|
||||
[Source A] --RDP--> [Host B] --SMB--> [Host C] --WMI--> [Host D]
|
||||
| |
|
||||
+--PsExec--> [Host E] +--WinRM--> [Server F]
|
||||
```
|
||||
|
||||
## Findings
|
||||
|
||||
| # | Source | Destination | Account | Method | Logon Type | Time | Risk |
|
||||
|---|--------|------------|---------|--------|-----------|------|------|
|
||||
| 1 | | | | | | | |
|
||||
|
||||
## Affected Accounts
|
||||
| Account | Type | Hosts Accessed | Movement Method |
|
||||
|---------|------|---------------|----------------|
|
||||
| | | | |
|
||||
|
||||
## Recommendations
|
||||
1. **Containment**: [Isolate systems, disable accounts]
|
||||
2. **Credential Reset**: [Scope of password resets needed]
|
||||
3. **Detection**: [New rules for identified patterns]
|
||||
@@ -0,0 +1,63 @@
|
||||
# Standards and References - Lateral Movement Detection with Splunk
|
||||
|
||||
## MITRE ATT&CK Lateral Movement (TA0008)
|
||||
|
||||
| Technique | Name | Event Indicators |
|
||||
|-----------|------|-----------------|
|
||||
| T1021.001 | Remote Desktop Protocol | Logon Type 10, RDP certificate events |
|
||||
| T1021.002 | SMB/Windows Admin Shares | Logon Type 3, ADMIN$/C$/IPC$ access |
|
||||
| T1021.003 | Distributed COM | Logon Type 3, DCOM process creation |
|
||||
| T1021.004 | SSH | OpenSSH authentication events |
|
||||
| T1021.006 | Windows Remote Management | WinRM/WSMan logon events |
|
||||
| T1047 | Windows Management Instrumentation | WMI remote process creation |
|
||||
| T1569.002 | Service Execution | PsExec service install + Type 3 logon |
|
||||
| T1570 | Lateral Tool Transfer | File copy over SMB/RDP |
|
||||
| T1550.002 | Pass the Hash | Type 3 logon with NTLM authentication |
|
||||
| T1550.003 | Pass the Ticket | Kerberos TGS without preceding TGT |
|
||||
|
||||
## Windows Logon Types Reference
|
||||
|
||||
| Type | Name | Description |
|
||||
|------|------|-------------|
|
||||
| 2 | Interactive | Local console logon |
|
||||
| 3 | Network | SMB, mapped drives, WinRM |
|
||||
| 4 | Batch | Scheduled task execution |
|
||||
| 5 | Service | Service startup |
|
||||
| 7 | Unlock | Workstation unlock |
|
||||
| 8 | NetworkCleartext | IIS basic auth |
|
||||
| 9 | NewCredentials | RunAs /netonly |
|
||||
| 10 | RemoteInteractive | RDP, Terminal Services |
|
||||
| 11 | CachedInteractive | Cached domain logon |
|
||||
|
||||
## Key Windows Event IDs for Lateral Movement
|
||||
|
||||
| Event ID | Source | Description |
|
||||
|----------|--------|-------------|
|
||||
| 4624 | Security | Successful account logon |
|
||||
| 4625 | Security | Failed account logon |
|
||||
| 4648 | Security | Logon with explicit credentials |
|
||||
| 4672 | Security | Special privileges assigned (admin logon) |
|
||||
| 4768 | Security | Kerberos TGT requested |
|
||||
| 4769 | Security | Kerberos TGS requested |
|
||||
| 4776 | Security | NTLM credential validation |
|
||||
| 5140 | Security | Network share accessed |
|
||||
| 5145 | Security | Network share object access check |
|
||||
| 7045 | System | New service installed |
|
||||
| 1 | Sysmon | Process creation |
|
||||
| 3 | Sysmon | Network connection |
|
||||
|
||||
## Splunk Data Model References
|
||||
|
||||
- `Authentication` data model for login events
|
||||
- `Network_Traffic` data model for connection data
|
||||
- `Endpoint.Processes` for process creation events
|
||||
- `Change.Endpoint_Changes` for service installations
|
||||
|
||||
## Authentication Protocol Indicators
|
||||
|
||||
| Protocol | Lateral Movement | Event Indicators |
|
||||
|----------|-----------------|-----------------|
|
||||
| NTLM | Pass-the-Hash | Event 4776, NtLmSsp package |
|
||||
| Kerberos | Pass-the-Ticket | Event 4768/4769, ticket anomalies |
|
||||
| CredSSP | RDP | Event 4624 Type 10 |
|
||||
| WSMan | WinRM | Event 4624 Type 3, WSMan source |
|
||||
@@ -0,0 +1,142 @@
|
||||
# Detailed Hunting Workflow - Lateral Movement with Splunk
|
||||
|
||||
## Phase 1: Network Logon Analysis
|
||||
|
||||
### Step 1.1 - Type 3 Network Logons (SMB, WinRM)
|
||||
```spl
|
||||
index=wineventlog EventCode=4624 Logon_Type=3
|
||||
| where NOT match(Account_Name, "(?i)(SYSTEM|ANONYMOUS|\\$)")
|
||||
| stats count dc(Computer) as unique_destinations values(Computer) as destinations by Account_Name Source_Network_Address
|
||||
| where unique_destinations > 3
|
||||
| sort -unique_destinations
|
||||
```
|
||||
|
||||
### Step 1.2 - Type 10 RDP Logons
|
||||
```spl
|
||||
index=wineventlog EventCode=4624 Logon_Type=10
|
||||
| stats count by Account_Name Source_Network_Address Computer
|
||||
| lookup dnslookup clientip as Source_Network_Address OUTPUT clienthost as src_hostname
|
||||
| table Account_Name src_hostname Source_Network_Address Computer count
|
||||
| sort -count
|
||||
```
|
||||
|
||||
### Step 1.3 - Explicit Credential Logons (PsExec, RunAs)
|
||||
```spl
|
||||
index=wineventlog EventCode=4648
|
||||
| where NOT match(Target_Server_Name, "(?i)(localhost|\\$)")
|
||||
| stats count values(Target_Server_Name) as targets by Account_Name Process_Name Computer
|
||||
| sort -count
|
||||
```
|
||||
|
||||
## Phase 2: Admin Share Access Detection
|
||||
|
||||
### Step 2.1 - ADMIN$ and C$ Share Access
|
||||
```spl
|
||||
index=wineventlog EventCode=5140
|
||||
| where Share_Name IN ("\\\\*\\ADMIN$", "\\\\*\\C$", "\\\\*\\IPC$")
|
||||
| where NOT match(Account_Name, "(?i)(\\$|SYSTEM)")
|
||||
| stats count values(Share_Name) as shares by Account_Name Source_Address Computer
|
||||
| sort -count
|
||||
```
|
||||
|
||||
### Step 2.2 - SMB File Operations on Admin Shares
|
||||
```spl
|
||||
index=wineventlog EventCode=5145
|
||||
| where match(Share_Name, "(?i)(ADMIN\\$|C\\$)")
|
||||
| where match(Relative_Target_Name, "(?i)(\\.exe|\\.dll|\\.ps1|\\.bat|\\.cmd)")
|
||||
| stats count by Account_Name Source_Address Share_Name Relative_Target_Name Computer
|
||||
```
|
||||
|
||||
## Phase 3: Service-Based Lateral Movement
|
||||
|
||||
### Step 3.1 - PsExec Service Installation
|
||||
```spl
|
||||
index=wineventlog EventCode=7045
|
||||
| where match(Service_File_Name, "(?i)(psexec|PSEXESVC|cmd\.exe|powershell)")
|
||||
| table _time Computer Service_Name Service_File_Name Service_Account
|
||||
```
|
||||
|
||||
### Step 3.2 - Remote Service Creation Correlation
|
||||
```spl
|
||||
index=wineventlog EventCode=7045
|
||||
| eval is_suspicious=if(match(Service_File_Name, "(?i)(temp|appdata|cmd|powershell)"), 1, 0)
|
||||
| where is_suspicious=1
|
||||
| join Computer [
|
||||
search index=wineventlog EventCode=4624 Logon_Type=3
|
||||
| rename Computer as Computer, Source_Network_Address as lateral_src
|
||||
]
|
||||
| table _time Computer Service_Name Service_File_Name lateral_src
|
||||
```
|
||||
|
||||
## Phase 4: WMI and DCOM Lateral Movement
|
||||
|
||||
### Step 4.1 - Remote WMI Execution
|
||||
```spl
|
||||
index=sysmon EventCode=1
|
||||
| where match(ParentImage, "(?i)WmiPrvSE\.exe") AND NOT match(Image, "(?i)(WmiApSrv|scrcons)")
|
||||
| table _time Computer User ParentImage Image CommandLine
|
||||
```
|
||||
|
||||
### Step 4.2 - DCOM Lateral Movement
|
||||
```spl
|
||||
index=sysmon EventCode=1
|
||||
| where match(ParentImage, "(?i)(mmc\.exe|excel\.exe|outlook\.exe)")
|
||||
| where match(Image, "(?i)(cmd\.exe|powershell\.exe|mshta\.exe)")
|
||||
| table _time Computer User ParentImage Image CommandLine
|
||||
```
|
||||
|
||||
## Phase 5: Authentication Graph Analysis
|
||||
|
||||
### Step 5.1 - Build Lateral Movement Graph
|
||||
```spl
|
||||
index=wineventlog EventCode=4624 Logon_Type IN (3, 10)
|
||||
| where NOT match(Account_Name, "(?i)(\\$|SYSTEM|ANONYMOUS)")
|
||||
| eval connection=Source_Network_Address."->".Computer
|
||||
| stats count first(_time) as first_seen last(_time) as last_seen by connection Account_Name
|
||||
| sort -count
|
||||
```
|
||||
|
||||
### Step 5.2 - First-Time Source-Destination Pairs
|
||||
```spl
|
||||
index=wineventlog EventCode=4624 Logon_Type IN (3, 10) earliest=-1d
|
||||
| where NOT match(Account_Name, "(?i)(\\$|SYSTEM)")
|
||||
| eval pair=Account_Name.":".Source_Network_Address."->".Computer
|
||||
| search NOT [
|
||||
| search index=wineventlog EventCode=4624 Logon_Type IN (3, 10) earliest=-30d latest=-1d
|
||||
| eval pair=Account_Name.":".Source_Network_Address."->".Computer
|
||||
| dedup pair
|
||||
| fields pair
|
||||
]
|
||||
| stats count by pair
|
||||
| sort -count
|
||||
```
|
||||
|
||||
## Phase 6: Anomaly Detection
|
||||
|
||||
### Step 6.1 - Velocity Anomaly (Rapid Multi-Host Access)
|
||||
```spl
|
||||
index=wineventlog EventCode=4624 Logon_Type=3
|
||||
| where NOT match(Account_Name, "(?i)(\\$|SYSTEM)")
|
||||
| bin _time span=10m
|
||||
| stats dc(Computer) as hosts_accessed values(Computer) as destinations by _time Account_Name Source_Network_Address
|
||||
| where hosts_accessed > 5
|
||||
| sort -hosts_accessed
|
||||
```
|
||||
|
||||
### Step 6.2 - Off-Hours Lateral Movement
|
||||
```spl
|
||||
index=wineventlog EventCode=4624 Logon_Type IN (3, 10)
|
||||
| where NOT match(Account_Name, "(?i)(\\$|SYSTEM)")
|
||||
| eval hour=strftime(_time, "%H")
|
||||
| where hour < 6 OR hour > 22
|
||||
| stats count by Account_Name Source_Network_Address Computer hour
|
||||
| sort -count
|
||||
```
|
||||
|
||||
### Step 6.3 - Service Account Lateral Movement
|
||||
```spl
|
||||
index=wineventlog EventCode=4624 Logon_Type=10
|
||||
| where match(Account_Name, "(?i)(svc_|service|admin)")
|
||||
| stats count by Account_Name Source_Network_Address Computer
|
||||
| sort -count
|
||||
```
|
||||
@@ -0,0 +1,335 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Lateral Movement Detection Script
|
||||
Analyzes Windows authentication logs to detect lateral movement patterns
|
||||
including RDP, SMB, WinRM, PsExec, and WMI-based movement.
|
||||
"""
|
||||
|
||||
import json
|
||||
import csv
|
||||
import argparse
|
||||
import datetime
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
|
||||
# Lateral movement logon types
|
||||
LATERAL_LOGON_TYPES = {
|
||||
"3": {"name": "Network", "techniques": ["T1021.002", "T1021.006", "T1047"], "risk_base": 20},
|
||||
"10": {"name": "RemoteInteractive", "techniques": ["T1021.001"], "risk_base": 25},
|
||||
}
|
||||
|
||||
# Suspicious account patterns
|
||||
SYSTEM_ACCOUNTS = {"system", "anonymous logon", "anonymous", "local service", "network service", "dwm-1", "umfd-0"}
|
||||
|
||||
# Admin share indicators
|
||||
ADMIN_SHARES = {"admin$", "c$", "ipc$", "d$", "e$"}
|
||||
|
||||
# PsExec and service-based indicators
|
||||
SERVICE_LATERAL_PATTERNS = [
|
||||
r"psexec", r"PSEXESVC", r"csexec", r"remcom",
|
||||
r"cmd\.exe\s+/c", r"powershell.*-enc",
|
||||
]
|
||||
|
||||
|
||||
def parse_logs(input_path: str) -> list[dict]:
|
||||
"""Parse JSON or CSV log files."""
|
||||
path = Path(input_path)
|
||||
if path.suffix == ".json":
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
return data if isinstance(data, list) else data.get("events", [])
|
||||
elif path.suffix == ".csv":
|
||||
with open(path, "r", encoding="utf-8-sig") as f:
|
||||
return [dict(row) for row in csv.DictReader(f)]
|
||||
return []
|
||||
|
||||
|
||||
def normalize_event(event: dict) -> dict:
|
||||
"""Normalize authentication event fields."""
|
||||
field_map = {
|
||||
"event_id": ["EventCode", "EventID", "event_id"],
|
||||
"logon_type": ["Logon_Type", "LogonType", "logon_type"],
|
||||
"account": ["Account_Name", "TargetUserName", "account_name", "user.name"],
|
||||
"source_ip": ["Source_Network_Address", "IpAddress", "source_ip", "source.ip"],
|
||||
"source_host": ["Workstation_Name", "WorkstationName", "source_host"],
|
||||
"dest_host": ["Computer", "hostname", "DeviceName", "host.name"],
|
||||
"logon_process": ["Logon_Process", "LogonProcessName", "logon_process"],
|
||||
"auth_package": ["Authentication_Package", "AuthenticationPackageName", "auth_package"],
|
||||
"share_name": ["Share_Name", "ShareName", "share_name"],
|
||||
"service_name": ["Service_Name", "ServiceName", "service_name"],
|
||||
"service_path": ["Service_File_Name", "ServiceFileName", "service_path"],
|
||||
"process_name": ["Process_Name", "ProcessName", "process_name"],
|
||||
"timestamp": ["_time", "timestamp", "Timestamp", "@timestamp", "UtcTime"],
|
||||
}
|
||||
normalized = {}
|
||||
for target, sources in field_map.items():
|
||||
for src in sources:
|
||||
if src in event and event[src]:
|
||||
normalized[target] = str(event[src]).strip()
|
||||
break
|
||||
if target not in normalized:
|
||||
normalized[target] = ""
|
||||
return normalized
|
||||
|
||||
|
||||
def detect_network_logon(event: dict) -> dict | None:
|
||||
"""Detect lateral movement via network logon events."""
|
||||
event_id = event.get("event_id", "")
|
||||
logon_type = event.get("logon_type", "")
|
||||
|
||||
if event_id != "4624" or logon_type not in LATERAL_LOGON_TYPES:
|
||||
return None
|
||||
|
||||
account = event.get("account", "").lower()
|
||||
if account in SYSTEM_ACCOUNTS or account.endswith("$"):
|
||||
return None
|
||||
|
||||
source_ip = event.get("source_ip", "")
|
||||
if not source_ip or source_ip in ("-", "::1", "127.0.0.1"):
|
||||
return None
|
||||
|
||||
lt_info = LATERAL_LOGON_TYPES[logon_type]
|
||||
risk = lt_info["risk_base"]
|
||||
indicators = [f"Logon Type {logon_type} ({lt_info['name']})"]
|
||||
|
||||
auth_pkg = event.get("auth_package", "").lower()
|
||||
if "ntlm" in auth_pkg:
|
||||
risk += 10
|
||||
indicators.append("NTLM authentication (potential Pass-the-Hash)")
|
||||
if "negotiate" in auth_pkg and logon_type == "3":
|
||||
indicators.append("Negotiate authentication package")
|
||||
|
||||
return {
|
||||
"detection_type": "NETWORK_LOGON",
|
||||
"technique": lt_info["techniques"][0],
|
||||
"account": event.get("account", ""),
|
||||
"source_ip": source_ip,
|
||||
"source_host": event.get("source_host", ""),
|
||||
"dest_host": event.get("dest_host", ""),
|
||||
"logon_type": logon_type,
|
||||
"auth_package": event.get("auth_package", ""),
|
||||
"timestamp": event.get("timestamp", ""),
|
||||
"risk_score": risk,
|
||||
"indicators": indicators,
|
||||
}
|
||||
|
||||
|
||||
def detect_explicit_creds(event: dict) -> dict | None:
|
||||
"""Detect explicit credential usage (Event 4648)."""
|
||||
if event.get("event_id") != "4648":
|
||||
return None
|
||||
|
||||
account = event.get("account", "").lower()
|
||||
if account in SYSTEM_ACCOUNTS or account.endswith("$"):
|
||||
return None
|
||||
|
||||
return {
|
||||
"detection_type": "EXPLICIT_CREDENTIAL",
|
||||
"technique": "T1021",
|
||||
"account": event.get("account", ""),
|
||||
"source_host": event.get("source_host", event.get("dest_host", "")),
|
||||
"dest_host": event.get("dest_host", ""),
|
||||
"process_name": event.get("process_name", ""),
|
||||
"timestamp": event.get("timestamp", ""),
|
||||
"risk_score": 35,
|
||||
"indicators": ["Explicit credential logon (4648) - possible PsExec/RunAs"],
|
||||
}
|
||||
|
||||
|
||||
def detect_share_access(event: dict) -> dict | None:
|
||||
"""Detect admin share access."""
|
||||
if event.get("event_id") != "5140":
|
||||
return None
|
||||
|
||||
share = event.get("share_name", "").lower()
|
||||
share_name = share.split("\\")[-1] if "\\" in share else share
|
||||
|
||||
if share_name not in ADMIN_SHARES:
|
||||
return None
|
||||
|
||||
account = event.get("account", "").lower()
|
||||
if account in SYSTEM_ACCOUNTS or account.endswith("$"):
|
||||
return None
|
||||
|
||||
risk = 40 if share_name in ("admin$", "c$") else 25
|
||||
|
||||
return {
|
||||
"detection_type": "ADMIN_SHARE_ACCESS",
|
||||
"technique": "T1021.002",
|
||||
"account": event.get("account", ""),
|
||||
"source_ip": event.get("source_ip", ""),
|
||||
"dest_host": event.get("dest_host", ""),
|
||||
"share": share,
|
||||
"timestamp": event.get("timestamp", ""),
|
||||
"risk_score": risk,
|
||||
"indicators": [f"Admin share accessed: {share_name}"],
|
||||
}
|
||||
|
||||
|
||||
def detect_service_lateral(event: dict) -> dict | None:
|
||||
"""Detect service-based lateral movement (PsExec)."""
|
||||
if event.get("event_id") not in ("7045", "4697"):
|
||||
return None
|
||||
|
||||
service_path = event.get("service_path", "")
|
||||
for pattern in SERVICE_LATERAL_PATTERNS:
|
||||
if re.search(pattern, service_path, re.IGNORECASE):
|
||||
return {
|
||||
"detection_type": "SERVICE_LATERAL",
|
||||
"technique": "T1569.002",
|
||||
"service_name": event.get("service_name", ""),
|
||||
"service_path": service_path,
|
||||
"dest_host": event.get("dest_host", ""),
|
||||
"timestamp": event.get("timestamp", ""),
|
||||
"risk_score": 60,
|
||||
"indicators": [f"Suspicious service for lateral movement: {pattern}"],
|
||||
}
|
||||
return None
|
||||
|
||||
|
||||
def build_movement_graph(findings: list[dict]) -> dict:
|
||||
"""Build a graph of lateral movement paths."""
|
||||
graph = defaultdict(lambda: defaultdict(list))
|
||||
for finding in findings:
|
||||
src = finding.get("source_ip") or finding.get("source_host", "unknown")
|
||||
dst = finding.get("dest_host", "unknown")
|
||||
if src and dst and src != dst:
|
||||
graph[src][dst].append({
|
||||
"account": finding.get("account", ""),
|
||||
"technique": finding.get("technique", ""),
|
||||
"timestamp": finding.get("timestamp", ""),
|
||||
"type": finding.get("detection_type", ""),
|
||||
})
|
||||
return dict(graph)
|
||||
|
||||
|
||||
def analyze_velocity(findings: list[dict], window_minutes: int = 10, threshold: int = 5) -> list[dict]:
|
||||
"""Detect rapid multi-host access patterns."""
|
||||
account_events = defaultdict(list)
|
||||
for f in findings:
|
||||
if f.get("account") and f.get("timestamp"):
|
||||
account_events[f["account"]].append(f)
|
||||
|
||||
velocity_alerts = []
|
||||
for account, events in account_events.items():
|
||||
events.sort(key=lambda x: x.get("timestamp", ""))
|
||||
unique_dests = set()
|
||||
window_start = 0
|
||||
|
||||
for i, event in enumerate(events):
|
||||
unique_dests.add(event.get("dest_host", ""))
|
||||
if len(unique_dests) >= threshold:
|
||||
velocity_alerts.append({
|
||||
"detection_type": "VELOCITY_ANOMALY",
|
||||
"account": account,
|
||||
"unique_destinations": len(unique_dests),
|
||||
"destinations": list(unique_dests),
|
||||
"risk_score": 80,
|
||||
"risk_level": "CRITICAL",
|
||||
"indicators": [f"Account accessed {len(unique_dests)} hosts rapidly"],
|
||||
})
|
||||
break
|
||||
|
||||
return velocity_alerts
|
||||
|
||||
|
||||
def run_hunt(input_path: str, output_dir: str) -> None:
|
||||
"""Execute lateral movement hunt."""
|
||||
print(f"[*] Lateral Movement Hunt - {datetime.datetime.now().isoformat()}")
|
||||
|
||||
events = parse_logs(input_path)
|
||||
print(f"[*] Loaded {len(events)} events")
|
||||
|
||||
findings = []
|
||||
stats = defaultdict(int)
|
||||
|
||||
detectors = [
|
||||
detect_network_logon,
|
||||
detect_explicit_creds,
|
||||
detect_share_access,
|
||||
detect_service_lateral,
|
||||
]
|
||||
|
||||
for raw_event in events:
|
||||
event = normalize_event(raw_event)
|
||||
for detector in detectors:
|
||||
result = detector(event)
|
||||
if result:
|
||||
risk = result["risk_score"]
|
||||
result["risk_level"] = (
|
||||
"CRITICAL" if risk >= 70 else "HIGH" if risk >= 50
|
||||
else "MEDIUM" if risk >= 30 else "LOW"
|
||||
)
|
||||
findings.append(result)
|
||||
stats[result["detection_type"]] += 1
|
||||
|
||||
# Velocity analysis
|
||||
velocity_alerts = analyze_velocity(findings)
|
||||
findings.extend(velocity_alerts)
|
||||
stats["VELOCITY_ANOMALY"] = len(velocity_alerts)
|
||||
|
||||
# Build movement graph
|
||||
graph = build_movement_graph(findings)
|
||||
|
||||
# Write output
|
||||
output_path = Path(output_dir)
|
||||
output_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(output_path / "lateral_movement_findings.json", "w", encoding="utf-8") as f:
|
||||
json.dump({
|
||||
"hunt_id": f"TH-LATMOV-{datetime.date.today().isoformat()}",
|
||||
"total_events": len(events),
|
||||
"total_findings": len(findings),
|
||||
"statistics": dict(stats),
|
||||
"movement_graph": {src: dict(dsts) for src, dsts in graph.items()},
|
||||
"findings": findings,
|
||||
}, f, indent=2)
|
||||
|
||||
with open(output_path / "hunt_report.md", "w", encoding="utf-8") as f:
|
||||
f.write(f"# Lateral Movement Hunt Report\n\n")
|
||||
f.write(f"**Date**: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
|
||||
f.write(f"**Events**: {len(events)} | **Findings**: {len(findings)}\n\n")
|
||||
f.write("## Movement Graph\n\n")
|
||||
for src, dests in graph.items():
|
||||
for dst, connections in dests.items():
|
||||
f.write(f"- `{src}` -> `{dst}` ({len(connections)} connections)\n")
|
||||
f.write("\n## Velocity Anomalies\n\n")
|
||||
for alert in velocity_alerts:
|
||||
f.write(f"- **{alert['account']}**: {alert['unique_destinations']} hosts in short window\n")
|
||||
|
||||
print(f"[+] {len(findings)} findings, {len(graph)} source nodes in movement graph")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Lateral Movement Detection")
|
||||
subparsers = parser.add_subparsers(dest="command")
|
||||
|
||||
hunt_p = subparsers.add_parser("hunt")
|
||||
hunt_p.add_argument("--input", "-i", required=True)
|
||||
hunt_p.add_argument("--output", "-o", default="./latmov_output")
|
||||
|
||||
subparsers.add_parser("queries", help="Print Splunk SPL queries")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command == "hunt":
|
||||
run_hunt(args.input, args.output)
|
||||
elif args.command == "queries":
|
||||
print("=== Splunk Lateral Movement Queries ===\n")
|
||||
queries = {
|
||||
"Network Logons": 'index=wineventlog EventCode=4624 Logon_Type=3\n| where NOT match(Account_Name, "(?i)(SYSTEM|ANONYMOUS|\\\\$)")\n| stats count dc(Computer) by Account_Name Source_Network_Address\n| where count > 3',
|
||||
"RDP Sessions": 'index=wineventlog EventCode=4624 Logon_Type=10\n| stats count by Account_Name Source_Network_Address Computer',
|
||||
"Admin Shares": 'index=wineventlog EventCode=5140 Share_Name IN ("*ADMIN$","*C$")\n| stats count by Account_Name Source_Address Computer Share_Name',
|
||||
"PsExec Services": 'index=wineventlog EventCode=7045\n| where match(Service_File_Name, "(?i)(psexec|PSEXESVC)")\n| table _time Computer Service_Name Service_File_Name',
|
||||
}
|
||||
for name, query in queries.items():
|
||||
print(f"--- {name} ---")
|
||||
print(query)
|
||||
print()
|
||||
else:
|
||||
parser.print_help()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user