soc operations

Performing Lateral Movement Detection

Detects lateral movement techniques including Pass-the-Hash, PsExec, WMI execution, RDP pivoting, and SMB-based spreading using SIEM correlation of Windows event logs, network flow data, and endpoint telemetry mapped to MITRE ATT&CK Lateral Movement (TA0008) techniques.

detectionlateral-movementmitre-attackpass-the-hashpsexecrdpsmbsoc
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

When to Use

Use this skill when:

  • SOC teams need to detect attackers pivoting between systems after initial compromise
  • Incident investigations require tracking an attacker's movement path through the network
  • Detection engineering needs lateral movement rules mapped to ATT&CK TA0008 techniques
  • Red/purple team exercises identify lateral movement detection gaps

Do not use for detecting initial access or external attacks — lateral movement detection focuses on internal host-to-host pivot activity.

Prerequisites

  • Windows Security Event Logs (EventCode 4624, 4625, 4648, 4672) from all endpoints and servers
  • Sysmon deployed with process creation (EventCode 1), network connections (EventCode 3), and named pipe (EventCode 17/18)
  • Network flow data (NetFlow/sFlow, Zeek connection logs) for internal traffic analysis
  • SIEM with cross-source correlation capability
  • Baseline of normal internal authentication patterns

Workflow

Step 1: Detect Pass-the-Hash / Pass-the-Ticket (T1550)

Pass-the-Hash Detection (EventCode 4624 with NTLM):

index=wineventlog sourcetype="WinEventLog:Security" EventCode=4624 Logon_Type=3
AuthenticationPackageName="NTLM"
| where TargetUserName!="ANONYMOUS LOGON" AND TargetUserName!="$"
| stats count, dc(ComputerName) AS unique_targets, values(ComputerName) AS targets
  by src_ip, TargetUserName
| where unique_targets > 3
| eval alert = "Possible Pass-the-Hash: NTLM network logon to ".unique_targets." hosts"
| sort - unique_targets
| table src_ip, TargetUserName, unique_targets, count, targets, alert

Overpass-the-Hash Detection (Kerberos with RC4):

index=wineventlog sourcetype="WinEventLog:Security" EventCode=4769
TicketEncryptionType="0x17"
| where ServiceName!="krbtgt" AND ServiceName!="$"
| stats count, dc(ServiceName) AS unique_services by src_ip, TargetUserName
| where count > 5
| eval alert = "Possible Overpass-the-Hash: RC4 Kerberos tickets from ".src_ip
| table _time, src_ip, TargetUserName, unique_services, count, alert

Golden/Silver Ticket Detection (T1558):

index=wineventlog sourcetype="WinEventLog:Security" EventCode=4769
| where TicketOptions="0x40810000" OR TicketOptions="0x40800000"
| eval ticket_lifetime = TicketExpireTime - TicketIssueTime
| where ticket_lifetime > 36000  --- >10 hours (abnormal)
| stats count by src_ip, TargetUserName, ServiceName, TicketEncryptionType, TicketOptions
| eval alert = "Possible Golden/Silver Ticket: Abnormal ticket properties"

Step 2: Detect Remote Service Exploitation (T1021)

PsExec Detection (T1021.002):

--- Via Sysmon process creation
index=sysmon EventCode=1
(Image="*\\psexec.exe" OR Image="*\\psexesvc.exe"
 OR OriginalFileName="psexec.c" OR OriginalFileName="psexesvc.exe"
 OR ParentImage="*\\psexesvc.exe")
| table _time, Computer, User, ParentImage, Image, CommandLine, Hashes
 
--- Via named pipe creation (Sysmon EventCode 17)
index=sysmon EventCode=17
PipeName IN ("\\PSEXESVC*", "\\RemCom*", "\\csexec*")
| table _time, Computer, User, Image, PipeName
 
--- Via Windows service creation (EventCode 7045)
index=wineventlog sourcetype="WinEventLog:System" EventCode=7045
ServiceName="PSEXESVC" OR ServiceFileName="*PSEXESVC*"
| table _time, Computer, ServiceName, ServiceFileName, AccountName

WMI Remote Execution (T1047):

index=sysmon EventCode=1
(Image="*\\wmic.exe" AND CommandLine="*/node:*")
OR (ParentImage="*\\WmiPrvSE.exe" AND Image IN ("*\\cmd.exe", "*\\powershell.exe"))
| eval execution_type = case(
    match(Image, "wmic"), "WMI Command Line",
    match(ParentImage, "WmiPrvSE"), "WMI Provider Host (remote execution)"
  )
| table _time, Computer, User, execution_type, ParentImage, Image, CommandLine

WinRM/PowerShell Remoting (T1021.006):

index=wineventlog sourcetype="WinEventLog:Security" EventCode=4624
Logon_Type=3 AuthenticationPackageName="Kerberos"
| where ProcessName="*\\wsmprovhost.exe" OR ProcessName="*\\powershell.exe"
| stats count, dc(ComputerName) AS unique_targets by src_ip, TargetUserName
| where unique_targets > 2
| eval alert = "PowerShell Remoting to ".unique_targets." hosts from ".src_ip
 
--- Sysmon variant
index=sysmon EventCode=1
ParentImage="*\\wsmprovhost.exe"
Image IN ("*\\cmd.exe", "*\\powershell.exe", "*\\csc.exe")
| table _time, Computer, User, Image, CommandLine

RDP Lateral Movement (T1021.001):

index=wineventlog sourcetype="WinEventLog:Security" EventCode=4624 Logon_Type=10
| stats count, dc(ComputerName) AS rdp_targets, values(ComputerName) AS destinations,
        earliest(_time) AS first_rdp, latest(_time) AS last_rdp
  by src_ip, TargetUserName
| where rdp_targets > 2
| eval duration_hours = round((last_rdp - first_rdp) / 3600, 1)
| eval alert = TargetUserName." RDP'd to ".rdp_targets." hosts in ".duration_hours." hours"
| sort - rdp_targets

Step 3: Detect SMB-Based Lateral Movement

Anomalous SMB Traffic Patterns:

index=firewall OR index=zeek sourcetype IN ("pan:traffic", "bro:conn:json")
dest_port=445 action=allowed
| where src_ip!=dest_ip
| stats count AS smb_sessions, dc(dest_ip) AS unique_targets,
        sum(bytes_out) AS total_bytes
  by src_ip
| where unique_targets > 10
| eval alert = case(
    unique_targets > 50, "CRITICAL: Mass SMB enumeration from ".src_ip,
    unique_targets > 20, "HIGH: Significant SMB lateral movement",
    unique_targets > 10, "MEDIUM: Elevated SMB connections"
  )
| sort - unique_targets

Admin Share Access (C$, ADMIN$):

index=wineventlog sourcetype="WinEventLog:Security" EventCode=5140
ShareName IN ("\\\\*\\C$", "\\\\*\\ADMIN$", "\\\\*\\IPC$")
| where SubjectUserName!="SYSTEM" AND SubjectUserName!="$"
| stats count, dc(ComputerName) AS unique_hosts by SubjectUserName, ShareName, src_ip
| where unique_hosts > 3
| eval alert = "Admin share access to ".unique_hosts." hosts by ".SubjectUserName
| sort - unique_hosts

Step 4: Build Lateral Movement Graph

Visualize the attack path:

--- Build source->destination graph for authentication events
index=wineventlog EventCode=4624 Logon_Type IN (3, 10)
earliest=-24h
| stats count AS connections, latest(_time) AS last_connection
  by src_ip, ComputerName, TargetUserName, Logon_Type
| eval edge = src_ip." -> ".ComputerName." (User: ".TargetUserName.", Type: ".Logon_Type.")"
| sort - connections
| table edge, connections, last_connection
 
--- Network flow correlation
index=netflow earliest=-24h
dest_port IN (445, 135, 3389, 5985, 5986)
| stats sum(bytes) AS total_bytes, count AS flow_count,
        dc(dest_ip) AS targets by src_ip, dest_port
| where targets > 5
| eval service = case(
    dest_port=445, "SMB",
    dest_port=135, "RPC/WMI",
    dest_port=3389, "RDP",
    dest_port IN (5985, 5986), "WinRM"
  )
| sort - targets
| table src_ip, service, targets, flow_count, total_bytes

Step 5: Detect DCOM and Scheduled Task-Based Movement

DCOM Lateral Execution (T1021.003):

index=sysmon EventCode=1
ParentImage IN ("*\\mmc.exe", "*\\excel.exe", "*\\outlook.exe")
Image IN ("*\\cmd.exe", "*\\powershell.exe", "*\\mshta.exe")
| where ParentCommandLine="*-Embedding*"
| eval alert = "DCOM-based lateral movement: ".ParentImage." spawned ".Image
| table _time, Computer, User, ParentImage, Image, CommandLine, alert

Remote Scheduled Task Creation (T1053.005):

index=wineventlog EventCode=4698
| where SubjectUserName!="SYSTEM"
| eval task_xml = TaskContent
| search task_xml="*http*" OR task_xml="*powershell*" OR task_xml="*cmd*" OR task_xml="*\\Temp\\*"
| table _time, Computer, SubjectUserName, TaskName, task_xml

Step 6: Correlate Movement with Kill Chain Phases

Build end-to-end attack chain detection:

--- Detect complete lateral movement sequence
index=wineventlog OR index=sysmon
(EventCode=4625 OR EventCode=4624 OR EventCode=1 OR EventCode=4698 OR EventCode=5140)
| eval phase = case(
    EventCode=4625, "1-Recon/BruteForce",
    EventCode=4624 AND Logon_Type=3, "2-Lateral Movement",
    EventCode=5140 AND match(ShareName, "C\$|ADMIN\$"), "3-Admin Share Access",
    EventCode=1 AND match(ParentImage, "psexesvc|WmiPrvSE|wsmprovhost"), "4-Remote Execution",
    EventCode=4698, "5-Persistence (Scheduled Task)",
    1=1, "other"
  )
| where phase!="other"
| stats count by phase, src_ip, ComputerName, TargetUserName
| sort phase, _time
| table phase, src_ip, ComputerName, TargetUserName, count

Key Concepts

Term Definition
Lateral Movement Post-compromise technique where attackers pivot between systems to reach targets
Pass-the-Hash Using stolen NTLM hash for authentication without knowing the plaintext password
Pass-the-Ticket Using stolen Kerberos TGT/TGS tickets for authentication across the domain
PsExec Sysinternals tool (and attack technique) for remote process execution via SMB and named pipes
WMI Execution Using Windows Management Instrumentation for remote command execution via DCOM or WinRM
Admin Share Default Windows administrative shares (C$, ADMIN$, IPC$) used for remote system management

Tools & Systems

  • Splunk Enterprise Security: SIEM platform for correlating Windows events, Sysmon, and network flows
  • Microsoft Defender for Identity: Cloud service detecting lateral movement via domain controller monitoring
  • BloodHound: Active Directory attack path analysis tool for identifying lateral movement opportunities
  • CrowdStrike Falcon: EDR platform with lateral movement detection and automated containment
  • Zeek (Bro): Network monitor generating connection logs for SMB, RDP, and WinRM traffic analysis

Common Scenarios

  • PsExec Spread: Attacker uses PsExec to execute malware across 20 workstations — detect via service creation events
  • RDP Pivoting: Compromised VPN account used to RDP through multiple internal hosts — detect via Logon_Type 10 chains
  • WMI Recon and Execution: Attacker uses WMI for discovery then execution — detect via WmiPrvSE child processes
  • Pass-the-Hash Campaign: Stolen local admin hash used across subnet — detect via NTLM Logon_Type 3 to multiple hosts
  • Scheduled Task Persistence: Remote scheduled task created on domain controller — detect via EventCode 4698 from non-admin source

Output Format

LATERAL MOVEMENT DETECTION REPORT
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Period:       2024-03-15 14:00 to 18:00 UTC
Source:       192.168.1.105 (WORKSTATION-042)
 
Movement Path:
  14:23  192.168.1.105 → 10.0.5.20  (DC-PRIMARY) — PtH via NTLM Type 3
  14:25  10.0.5.20 → 10.0.5.21     (DC-BACKUP)  — Kerberos ticket reuse
  14:28  10.0.5.20 → 10.0.10.15    (FILESERVER-01) — PsExec service creation
  14:32  10.0.10.15 → 10.0.10.20   (DB-PRIMARY) — WMI remote execution
  14:35  10.0.10.20 → 10.0.10.25   (DB-BACKUP)  — SMB admin share access
 
Techniques Detected:
  T1550.002 — Pass-the-Hash (NTLM authentication to DC)
  T1021.002 — PsExec (remote service installation)
  T1047     — WMI Execution (WmiPrvSE child process)
  T1021.002 — SMB Admin Share (C$ access on DB-BACKUP)
 
Affected Systems: 5 hosts across 2 network segments
User Account:     admin_compromised (Domain Admin)
Containment:      All 5 hosts isolated at 14:45 UTC
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 1

api-reference.md1.8 KB

API Reference: Lateral Movement Detection

Windows Event Log IDs

Event ID Source Description
4624 Security Successful logon (Logon_Type 3=network, 10=RDP)
4625 Security Failed logon attempt
4648 Security Explicit credential logon (runas)
4672 Security Special privileges assigned (admin logon)
4769 Security Kerberos TGS request (Pass-the-Ticket)
5140 Security Network share access (C$, ADMIN$, IPC$)
7045 System New service installed (PsExec)

Sysmon Event Codes

Event Code Description
1 Process creation with command line
3 Network connection
10 Process access (LSASS credential dumping)
17/18 Named pipe created/connected (PsExec)

MITRE ATT&CK Techniques (TA0008)

Technique ID Detection Signal
Pass-the-Hash T1550.002 NTLM Type 3 logon to multiple hosts
PsExec T1021.002 PSEXESVC service creation + named pipe
WMI Execution T1047 WmiPrvSE spawning cmd/powershell
RDP T1021.001 Logon_Type 10 to multiple targets
SMB Admin Share T1021.002 EventCode 5140 on C$/ADMIN$

Python Libraries

Library Version Purpose
csv stdlib Parse exported Windows event logs
json stdlib Report output generation
collections stdlib Event aggregation and counting

References

Scripts 1

agent.py8.8 KB
Display-only source. This catalog never executes bundled scripts.
#!/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()
Keep exploring