Skip to content
← Rule library
medium KQL Entra ID

Password Spray Against Entra ID

Low-and-slow failures across many accounts from few source IPs - tuned on distinct-user count rather than raw failure volume.

Rule source

password-spray-entra.kql
// Password Spray Against Entra ID
// T1110.003 - Brute Force: Password Spraying
// Tuned on distinct-user count, not raw failure volume. Spraying stays
// shallow per account - that shallowness is the actual signature.

let window     = 30m;
let min_users  = 15;
let max_depth  = 3.0;

SigninLogs
| where TimeGenerated > ago(window)
| where ResultType in ("50126", "50053", "50055")   // bad password / locked / expired
| summarize
      FailedUsers = dcount(UserPrincipalName),
      Attempts    = count(),
      UserSample  = make_set(UserPrincipalName, 12),
      Apps        = make_set(AppDisplayName, 5),
      FirstSeen   = min(TimeGenerated),
      LastSeen    = max(TimeGenerated)
    by IPAddress, Country = tostring(LocationDetails.countryOrRegion)
| where FailedUsers >= min_users
| extend AttemptsPerUser = round(todouble(Attempts) / FailedUsers, 2)
| where AttemptsPerUser <= max_depth
| order by FailedUsers desc

Required telemetry

Entra ID SigninLogs, non-interactive included

Confirm this source is arriving before you judge the rule. A detection built on telemetry you do not collect looks identical to one that simply never fires.

Known false positives

  • A broken service account looping against many mailboxes
  • Post-migration clients holding stale credentials

Validation

Replay a historic spray window and confirm the threshold still fires

Re-run quarterly. Rules decay because the environment moves underneath them - agent upgrades, schema changes and config edits all break detections silently.