Query Details

HUNT 11 AD Pass The Hash NTLM Lateral Movement Pattern 30d

Query

// =========================================================
// HUNT-11 | AD-PassTheHash-NTLM-LateralMovement-Pattern-30d
// Description : Hunts for Pass-the-Hash indicators using
//               EventID 4624 (Type 3, NTLM) combined with
//               the absence of an interactive logon baseline
//               for the same account→target host pair.
//               Also correlates 4648 (explicit credential
//               use) and 4776 (NTLM credential validation)
//               to increase fidelity.
// Period      : 30 days
// Use Case    : Lateral movement hunting, credential reuse,
//               NTLM relay and pass-the-hash detection
// Tables      : SecurityEvent
// =========================================================

let Period      = 30d;
let BaselineDays = 21d;   // baseline = earlier 21 days
let HuntDays     = 7d;    // hunt = most recent 7 days

// Establish interactive logon baseline (Type 2 = console, Type 10 = RDP)
let InteractiveBaseline = SecurityEvent
    | where TimeGenerated between (ago(BaselineDays) .. ago(HuntDays))
    | where EventID == 4624
    | where LogonType in (2, 10)
    | where TargetUserName !endswith "$"
    | summarize
        BaselineHosts   = make_set(WorkstationName, 50),
        BaselineSourceIP = make_set(IpAddress, 50)
      by AccountNorm = tolower(TargetUserName);

// NTLM Type3 logons in hunt window
let NTLMLogons = SecurityEvent
    | where TimeGenerated > ago(HuntDays)
    | where EventID == 4624
    | where LogonType == 3
    | where AuthenticationPackageName == "NTLM"
    | where TargetUserName !endswith "$"
    | where TargetUserName !in~ ("ANONYMOUS LOGON", "IUSR")
    | extend
        AccountNorm   = tolower(TargetUserName),
        SourceHost    = WorkstationName,
        TargetHost    = Computer,
        SourceIP      = IpAddress,
        IsOffHours    = hourofday(TimeGenerated) < 7 or hourofday(TimeGenerated) >= 20;

// 4648 explicit credential use (used during PTH/PTT)
let ExplicitCreds = SecurityEvent
    | where TimeGenerated > ago(HuntDays)
    | where EventID == 4648
    | where TargetUserName !endswith "$"
    | summarize ExplicitCredCount = count(),
                ExplicitCredTargets = make_set(TargetServerName, 10)
      by AccountNorm4648 = tolower(SubjectUserName);

// 4776 NTLM validation failures — may indicate hash reuse failure
let NTLMValidation = SecurityEvent
    | where TimeGenerated > ago(HuntDays)
    | where EventID == 4776
    | where Status != "0x0"   // not success
    | summarize FailedNTLMValidations = count()
      by AccountNorm4776 = tolower(TargetUserName);

// Join baseline to hunt to identify new/unexpected NTLM lateral movement
NTLMLogons
| join kind=leftouter (InteractiveBaseline) on AccountNorm
// New = host the account has never interactively logged into
| extend IsNewTargetHost = not(BaselineHosts has TargetHost)
| join kind=leftouter (ExplicitCreds) on $left.AccountNorm == $right.AccountNorm4648
| join kind=leftouter (NTLMValidation) on $left.AccountNorm == $right.AccountNorm4776
| where IsNewTargetHost or ExplicitCredCount > 0
| summarize
    NTLMLogonCount     = count(),
    UniqueTargets      = dcount(TargetHost),
    UniqueSourceIPs    = dcount(SourceIP),
    OffHoursLogons     = countif(IsOffHours),
    NewTargetHosts     = make_set_if(TargetHost, IsNewTargetHost, 20),
    AllTargets         = make_set(TargetHost, 20),
    SourceHosts        = make_set(SourceHost, 10),
    SourceIPs          = make_set(SourceIP, 10),
    ExplicitCreds      = take_any(ExplicitCredCount),
    FailedNTLMV        = take_any(FailedNTLMValidations)
  by AccountNorm
| extend
    RiskScore = (array_length(NewTargetHosts) * 25)
              + (OffHoursLogons * 5)
              + (iff(isnotnull(ExplicitCreds) and ExplicitCreds > 0, 15, 0))
              + (UniqueTargets * 5),
    RiskLevel = case(
        array_length(NewTargetHosts) >= 4, "Critical",
        array_length(NewTargetHosts) >= 2, "High",
        array_length(NewTargetHosts) >= 1 and OffHoursLogons > 0, "High",
        array_length(NewTargetHosts) >= 1, "Medium",
        "Low"
    ),
    AttackIndicator = case(
        array_length(NewTargetHosts) >= 3 and ExplicitCreds > 0,
            "HighConfidence_PassTheHash_LateralMovement",
        array_length(NewTargetHosts) >= 2,
            "Probable_PassTheHash_NewHostSpread",
        array_length(NewTargetHosts) >= 1,
            "Possible_PassTheHash_NewHost",
        ExplicitCreds > 0,
            "ExplicitCredentialUse_WithNTLM",
        "Anomalous_NTLM_Pattern"
    )
| project
    AccountNorm,
    RiskLevel,
    RiskScore,
    AttackIndicator,
    NTLMLogonCount,
    UniqueTargets,
    NewTargetHosts,
    OffHoursLogons,
    ExplicitCreds,
    SourceHosts,
    SourceIPs
| order by RiskScore desc

Explanation

This query is designed to detect potential "Pass-the-Hash" attacks and other suspicious activities related to NTLM authentication within a 30-day period. Here's a simplified breakdown of what the query does:

  1. Baseline Establishment:

    • It first establishes a baseline of normal interactive logons (like console or RDP logons) for each user over a 21-day period. This helps identify usual login patterns and hosts.
  2. NTLM Logon Detection:

    • It then looks for NTLM logons (EventID 4624, LogonType 3) in the most recent 7 days. These are network logons that might indicate lateral movement if they occur on new or unexpected hosts.
  3. Credential Use and Validation:

    • The query checks for explicit credential use (EventID 4648) and failed NTLM validation attempts (EventID 4776) in the same 7-day period. These events can indicate attempts to use or validate credentials, possibly hinting at credential misuse.
  4. Correlation and Analysis:

    • It correlates the NTLM logons with the baseline to identify new target hosts that the account has not logged into interactively before.
    • It also checks for off-hours logons, which might be suspicious.
    • The query joins data from explicit credential use and NTLM validation failures to enhance detection fidelity.
  5. Risk Assessment:

    • For each account, it calculates a risk score based on factors like the number of new target hosts, off-hours logons, and explicit credential use.
    • It assigns a risk level (Critical, High, Medium, Low) and an attack indicator based on the observed patterns.
  6. Output:

    • The results include the account name, risk level, risk score, attack indicator, and details about the logon activity, such as the number of unique targets, new target hosts, off-hours logons, and source hosts/IPs.

The query is essentially a hunting tool for identifying potential lateral movement and credential misuse in a network by analyzing NTLM authentication patterns.

Details

David Alonso profile picture

David Alonso

Released: March 24, 2026

Tables

SecurityEvent

Keywords

SecurityEvent

Operators

letbetweenagoin!endswithsummarizemake_setbytolower>==!in~extendorcountmake_set_iftake_anyarray_length+iffisnotnullcaseprojectorder bydesc..!=hasjoinkindleftoutercountif

Actions