Entra ID account performs privileged operation shortly after admin password reset
Description
Identifies accounts initiating high-impact Entra ID operations within 30 minutes of having their password reset by a different actor. Cross-actor correlation (ResetActorUpn != ResetTargetUpn) separates this from self-service and helpdesk reset flows.
Query · kql
let timeframe = 1d;
let correlationWindow = 30m;
let PrivilegedOps = dynamic([
"Add member to role.",
"Add member to role",
"Add service principal credentials.",
"Add service principal credentials",
"Update application - Certificates and secrets management",
"Add owner to service principal.",
"Add owner to service principal",
"Set domain authentication.",
"Set domain authentication"
]);
// Admin-initiated password resets (different actor from target)
let PasswordResets =
AuditLogs
| where TimeGenerated >= ago(timeframe)
| where OperationName in~ ("Reset user password.", "Reset user password")
| where Result =~ "success"
| extend ResetActorUpn = tolower(tostring(InitiatedBy.user.userPrincipalName))
| extend ResetActorApp = tostring(InitiatedBy.app.displayName)
| mv-expand TargetResource = TargetResources
| where tostring(TargetResource.type) =~ "User"
| extend ResetTargetUpn = tolower(tostring(TargetResource.userPrincipalName))
| where isnotempty(ResetTargetUpn)
| where ResetActorUpn != ResetTargetUpn
| project ResetTime = TimeGenerated, ResetActorUpn, ResetActorApp, ResetTargetUpn;
// Privileged operations initiated by the reset target within the correlation window
let FollowOnOps =
AuditLogs
| where TimeGenerated >= ago(timeframe)
| where OperationName in~ (PrivilegedOps)
| where Result =~ "success"
| extend ActorUpn = tolower(tostring(InitiatedBy.user.userPrincipalName))
| extend ActorIp = iff(
isnotempty(tostring(InitiatedBy.user.ipAddress)),
tostring(InitiatedBy.user.ipAddress),
tostring(InitiatedBy.app.ipAddress))
| where isnotempty(ActorUpn)
| project OpTime = TimeGenerated, ActorUpn, ActorIp, OperationName;
PasswordResets
| join kind=inner FollowOnOps on $left.ResetTargetUpn == $right.ActorUpn
| where OpTime between (ResetTime .. (ResetTime + correlationWindow))
| extend AccountName = tostring(split(ResetTargetUpn, "@")[0])
| extend AccountUPNSuffix = tostring(split(ResetTargetUpn, "@")[1])
| project
ResetTime,
ResetTargetUpn,
AccountName,
AccountUPNSuffix,
ResetActorUpn,
ResetActorApp,
OpTime,
OperationName,
ActorIp
| sort by ResetTime desc