Tools · Identity defense · 05 / 05
Hunting password spray in Entra sign-in logs, then testing a policy in report-only
Write KQL over SigninLogs that separates password spray from one user’s typos, then use Conditional Access report-only results to see who a new policy would block before enforcing it.
What spray looks like in the data
A typo storm is many failures for one account. Password spray is the opposite shape: one or a few source addresses, a handful of attempts per account, across many accounts, often with error 50126 and occasionally a success. Counting distinct accounts per source is the core of the detection.
The hunting query
This query groups failures by source address in one-hour bins and keeps sources that tried many accounts. A second step checks whether any of those sources also produced a success, which turns a noisy hunt into a short list.
let window = 1h;
let spray = SigninLogs
| where TimeGenerated > ago(7d) and ResultType == "50126"
| summarize Accounts = dcount(UserPrincipalName), Attempts = count() by IPAddress, bin(TimeGenerated, window)
| where Accounts >= 15 and Attempts / Accounts <= 3;
spray
| join kind=inner (
SigninLogs | where TimeGenerated > ago(7d) and ResultType == "0"
| project IPAddress, SuccessUser = UserPrincipalName, SuccessTime = TimeGenerated
) on IPAddress
| project IPAddress, Accounts, Attempts, SuccessUser, SuccessTimeReport-only before enforcement
A new policy, “Block legacy authentication”, runs in report-only mode for a week. Sign-in records then include a reportOnlyFailure result for that policy where it would have blocked. Counting those per user and app shows who would break.
SigninLogs | where TimeGenerated > ago(7d) | mv-expand P = ConditionalAccessPolicies | where tostring(P.displayName) == "Block legacy authentication" and tostring(P.result) == "reportOnlyFailure" | summarize Count = count() by UserPrincipalName, ClientAppUsed, AppDisplayName | order by Count desc UserPrincipalName ClientAppUsed AppDisplayName Count scanner@hackblue-lab.org Authenticated SMTP Office 365 Exchange 412 kim@hackblue-lab.org IMAP4 Office 365 Exchange 3
Worked outcome
The hunt finds one source that tried 61 accounts over two nights with no successes. The address is blocked with a named location, and the accounts get no action beyond a note. Report-only shows that a multifunction printer uses SMTP AUTH with a shared account. That printer moves to a connector before the policy is enforced, and kim’s three IMAP attempts match the 03:14 failure from an unfamiliar address, so her password is reset as a precaution.
Diagram
Typo storm versus password spray
Same error code, different shape.
Accounts. One
Attempts per account. Many
Sources. The user’s usual device
Accounts. Many
Attempts per account. One to three
Sources. Few, often unfamiliar addresses
Check yourself
1. Which aggregation best separates spray from typos?
2. What does reportOnlyFailure mean for a policy in a sign-in record?
3. Report-only shows a printer using SMTP AUTH. What should happen before enforcement?