Chapter 11

Hunting for Secondary Compromise

How attackers move from the initially identified scope to hosts you haven't found yet — lateral movement artifact hunting, credential reuse detection, and how to bound the true blast radius before declaring eradication complete.

Scenario

You've scoped the incident to 5 confirmed compromised hosts. You've contained them, removed persistence, and reset the associated credentials. Eradication is declared at day 4. On day 6, your SOC gets an alert: suspicious PowerShell execution on a host in a completely different subnet — one you didn't examine during IR. The EDR telemetry shows the attacker used credentials from the original compromise to move laterally to a host that wasn't in your original blast radius. You missed it because you didn't hunt secondary compromise from the foothold hosts — you only investigated hosts that had alerts. This chapter explains how to bound the true scope from an attacker's perspective, not just the alert perspective.

How Lateral Movement Leaves Evidence

Lateral movement is not silent. Every jump leaves artifacts on both the source host (outbound) and the destination host (inbound), and typically in Active Directory logs too.

  Lateral Movement Artifact Map
  ═══════════════════════════════════════════════════════════════════

  SOURCE HOST (host attacker is moving FROM):
    Windows Security Log:
      4648 — Explicit credential logon (using /netonly or runas)
      4688 + cmdline — net use, psexec, wmic, winrm, mmc
    Sysmon:
      Event 1 (Process Create): psexec.exe, wmic.exe, winrm.exe, net.exe
      Event 3 (Network Connect): outbound to new internal IP on ports
                                  445, 5985, 135, 139, 3389
    Registry: NTUSER.DAT\Network — mapped drives

  DESTINATION HOST (host attacker is moving TO):
    Windows Security Log:
      4624 Type 3 — Network logon (SMB/WMI/file access)
      4624 Type 10 — RemoteInteractive (RDP)
      4624 Type 2 — Interactive logon (physical/console)
      4672 — Special privileges assigned (if using admin account)
      7045 — New service installed (if psexec / service deployment used)
    Sysmon:
      Event 1: process spawned by psexec parent, WMI provider host, etc.

  DOMAIN CONTROLLER:
    Event 4769 — Kerberos Service Ticket requested for destination host
    Event 4776 — NTLM authentication to destination host
    (Both show source IP + account + destination)

Hunting Outbound from the Compromised Host

PowerShelllateral-move-hunt.ps1
# On the compromised host (via EDR live response or direct PowerShell):
# Find all internal IPs this host connected to during the incident window

$startTime = [DateTime]"2026-08-01T00:00:00"
$endTime   = [DateTime]"2026-08-29T23:59:59"

# Event 4648 — Explicit credential use (attacker using alternate creds)
Get-WinEvent -FilterHashtable @{
    LogName   = "Security"
    Id        = 4648
    StartTime = $startTime
    EndTime   = $endTime
} -ErrorAction SilentlyContinue | ForEach-Object {
    $xml = [xml]$_.ToXml()
    $data = $xml.Event.EventData.Data
    [PSCustomObject]@{
        Time        = $_.TimeCreated
        AccountUsed = $data | Where-Object Name -eq "SubjectUserName" | Select -Expand "#text"
        TargetServer= $data | Where-Object Name -eq "TargetServerName" | Select -Expand "#text"
        ProcessName = $data | Where-Object Name -eq "ProcessName" | Select -Expand "#text"
    }
} | Where-Object { $_.TargetServer -ne "localhost" } | Format-Table

Hunting Lateral Movement from Domain Controller Logs

Domain controller logs are the single best source for lateral movement telemetry — every Kerberos ticket request identifies both the source and destination. This hunt requires pulling logs from all domain controllers, not just the one you happen to query.

PowerShelldc-lateral-hunt.ps1
# Hunt for Kerberos ticket requests from compromised accounts
# Run on domain controller or via remote event log query

$compromisedAccounts = @("jsmith","mwilliams","svc_deploy")
$startTime = [DateTime]"2026-08-01"

# Event 4769 — Kerberos Service Ticket requested
# ServiceName = the host the ticket was requested for (lateral move destination)
$dcs = (Get-ADDomainController -Filter *).HostName

foreach ($dc in $dcs) {
    Write-Host "`n== DC: $dc ==" -ForegroundColor Cyan
    Get-WinEvent -ComputerName $dc -FilterHashtable @{
        LogName   = "Security"
        Id        = 4769
        StartTime = $startTime
    } -ErrorAction SilentlyContinue | ForEach-Object {
        $xml  = [xml]$_.ToXml()
        $d    = $xml.Event.EventData.Data
        $acct = ($d | Where-Object Name -eq "TargetUserName")."#text"
        $svc  = ($d | Where-Object Name -eq "ServiceName")."#text"
        $ip   = ($d | Where-Object Name -eq "IpAddress")."#text"

        if ($compromisedAccounts -contains $acct.Split("@")[0]) {
            [PSCustomObject]@{
                Time    = $_.TimeCreated
                Account = $acct
                Service = $svc     # host being accessed
                SourceIP= $ip
            }
        }
    } | Where-Object { $_ } | Format-Table
}

Credential Reuse Detection

Password reuse across the fleet is an attacker force multiplier. If the attacker cracked a local admin password, they may have reused it to access other machines where the same password is set — which is typically every machine where that local admin account was set up with the same base password.

PowerShellcredential-reuse-hunt.ps1
# Identify machines where compromised accounts had successful logons
# beyond the machines you already know about

$compromisedAccounts = @("jsmith","mwilliams")
$startTime = [DateTime]"2026-08-01"

# Query all machines' Security logs for successful logons by compromised accounts
$computers = Get-ADComputer -Filter * | Select-Object -ExpandProperty Name

$results = Invoke-Command -ComputerName $computers -ScriptBlock {
    param($accounts, $start)
    Get-WinEvent -FilterHashtable @{
        LogName   = "Security"
        Id        = @(4624, 4625)  # success and failure
        StartTime = $start
    } -ErrorAction SilentlyContinue | ForEach-Object {
        $xml  = [xml]$_.ToXml()
        $d    = $xml.Event.EventData.Data
        $acct = ($d | Where-Object Name -eq "TargetUserName")."#text"
        $type = ($d | Where-Object Name -eq "LogonType")."#text"
        $ip   = ($d | Where-Object Name -eq "IpAddress")."#text"
        if ($accounts -contains $acct) {
            [PSCustomObject]@{
                Computer = $env:COMPUTERNAME
                Time     = $_.TimeCreated
                EventId  = $_.Id
                Account  = $acct
                LogonType= $type
                SourceIP = $ip
            }
        }
    }
} -ArgumentList $compromisedAccounts, $startTime -ErrorAction SilentlyContinue

$results | Where-Object { $_ } | Sort-Object Time | Format-Table

Determining When Scope Is Bounded

You can never prove a negative — that there are zero unidentified compromised hosts. But you can reach a defensible confidence threshold by applying the following criteria.

CriterionHow to verify
All Kerberos/NTLM authentications from compromised accounts are accounted forDC log query shows all authentication events; each destination host has been examined
All destination hosts from lateral movement artifacts have been examinedEvery IP from the lateral movement hunt has been checked for persistence and cleared
Network flow analysis shows no unaccounted east-west connections from compromised hostsNetFlow/firewall logs show all internal connections during incident window from compromised IPs
No new persistence mechanisms created since containmentEDR telemetry shows no new scheduled task/service/registry changes on any monitored host since containment
48-hour monitoring window post-eradication shows no attacker activitySIEM hunting, EDR behavior monitoring, new accounts, authentication anomalies — all quiet
Mental model: the blast radius grows with dwell time

Every day an attacker spends in the environment is another day of potential lateral movement. A dwell time of 1 day means the attacker probably touched only a few hosts. A dwell time of 60 days means they've had time to map the entire environment, extract all valuable credentials, and establish persistence on every high-value system. Before starting the secondary compromise hunt, multiply your initial confirmed scope by the dwell time in days. If you found 3 compromised hosts after 45 days of dwell, expect the real scope to be substantially larger — and design the hunt accordingly.

Q & A

Q: DC logs only go back 30 days due to log size limits. But the compromise started 60 days ago. What do you do?

Check your SIEM first — if Windows Security events are forwarded to the SIEM, the DC log retention in the SIEM may be longer than the on-host retention. If no SIEM has the events: check NetFlow/firewall logs for network-level evidence of lateral movement — they often have longer retention than Windows event logs. Accept that your scope analysis will have a blind spot for the first 30 days and document this limitation in your incident report. For the remaining window, do the best analysis you can with available data. Post-incident: increase DC Security log size and SIEM forwarding to at least 90 days to close this gap for future incidents.

Q: You find a host that shows inbound 4624 type 3 from a compromised account, but the host itself is clean — no persistence, no malware. Does it go on the compromised list?

Yes — but with qualification. The host is "accessed" rather than necessarily "compromised." Document it as: "HOSTNAME — accessed by compromised account [account] on [date]; no persistent tooling found; requires credential review and monitoring." Add it to the eradication scope for credential rotation (any local or service accounts on this host that the attacker could have harvested while logged in) and to the post-incident monitoring list. A "clean" machine that was accessed with compromised credentials is not the same as a confirmed-never-touched machine. The attacker may have accessed it to exfiltrate data, or may have simply connected and found nothing worth persisting on. Either way it counts as within the blast radius.