Bulk admin-initiated password reset across multiple accounts
Description
Identifies a single actor resetting passwords for three or more distinct accounts within one hour. Bulk admin-initiated resets in rapid succession suggest active account takeover before detection.
Query · kql
let timeframe = 1d;
let resetThreshold = 3;
let correlationWindow = 1h;
AuditLogs
| where TimeGenerated >= ago(timeframe)
| where OperationName in~ (
"Reset password (by admin)",
"Reset user password",
"Reset password"
)
| where Result =~ "success"
| extend ActorUpn = tostring(InitiatedBy.user.userPrincipalName)
| extend ActorApp = tostring(InitiatedBy.app.displayName)
| extend ActorIp = iff(
isnotempty(tostring(InitiatedBy.user.ipAddress)),
tostring(InitiatedBy.user.ipAddress),
tostring(InitiatedBy.app.ipAddress))
| extend Actor = iff(isnotempty(ActorUpn), ActorUpn, ActorApp)
| extend TargetUpn = tostring(TargetResources[0].userPrincipalName)
| where isnotempty(Actor) and isnotempty(TargetUpn) and Actor != TargetUpn
| summarize
ResetCount = dcount(TargetUpn),
TargetAccounts = make_set(TargetUpn, 20),
FirstReset = min(TimeGenerated),
LastReset = max(TimeGenerated),
ActorIp = take_any(ActorIp)
by Actor, TimeBucket = bin(TimeGenerated, correlationWindow)
| where ResetCount >= resetThreshold
| extend AccountName = iff(Actor has "@", tostring(split(Actor, "@")[0]), Actor)
| extend AccountUPNSuffix = iff(Actor has "@", tostring(split(Actor, "@")[1]), "")
| project
TimeBucket,
Actor,
AccountName,
AccountUPNSuffix,
ResetCount,
TargetAccounts,
FirstReset,
LastReset,
ActorIp
| sort by ResetCount desc