Analytics Rules
Password Spray
detect-wide-low-volume-password-sprays.kql
Wide, low-volume spray: from a single IP, exactly one failed attempt per user in a day, but against many different users.
// Author: Ian D. Hanley (DevSecOpsDad) | linkedin.com/in/ianhanley | devsecopsdad.com | devsecopsdadattack.com
// Sentinel (SigninLogs) — detect “wide, low-volume” password sprays
// Heuristic: from a single IP, exactly one failed attempt per user in a day, but against many different users.
SigninLogs
| where ResultType != 0 // Keep only failed sign-ins (ResultType==0 is success)
| summarize Attempts=count()
by bin(TimeGenerated, 1d), UserPrincipalName, IPAddress, UserAgent // Count failed attempts per day
| where Attempts == 1 // Keep patterns with exactly one failure per targeted user from that IP per day
// (characteristic of “one-try-per-user” low-and-slow sprays)
| summarize
UsersTargeted = dcount(UserPrincipalName), // How many distinct users were hit by this IP that day
UAs = make_set(UserAgent, 20) // Keep up to 20 distinct UAs seen for context
by bin(TimeGenerated, 1d), IPAddress // Roll up to (day × IP), aggregating distinct users and UAs
| where UsersTargeted >= 10 // Threshold: treat as suspicious only if the IP touched many users that day (tune to your baseline)
| order by TimeGenerated desc, UsersTargeted desc // Show the most recent and broadest spray patterns first