Break-glass account credentials or MFA modified
Description
Identifies password resets, security-info registrations, and MFA changes made to an account designated as an emergency break-glass account, which should otherwise remain untouched between scheduled tests.
Query · kql
let starttime = todatetime('{{StartTimeISO}}');
let endtime = todatetime('{{EndTimeISO}}');
let BreakGlassAccounts = (
_GetWatchlist('BreakGlassAccounts')
| project AccountUPN = tolower(tostring(SearchKey))
);
let SecurityInfoOps = dynamic([
"Admin registered security info",
"Admin updated security info",
"Admin deleted security info",
"User registered security info",
"User changed default security info",
"User deleted security info",
"User registered all required security info",
"User started security info registration"
]);
// Password reset/change operation names are not consistent across tenants and Entra ID
// service versions (this repo alone has three different exact-string lists for the same
// event in other hunting queries), so credential resets are matched the same way the
// official "Multiple Password Reset by user" analytic rule does it: any OperationName
// that mentions a password/credential noun together with a change/reset verb, admin or
// self-service alike, rather than an exact-string list that silently misses variants.
let PasswordWords = dynamic(["password", "credential", "credentials"]);
let ChangeActionWords = dynamic(["change", "changed", "reset"]);
AuditLogs
| where TimeGenerated between (starttime .. endtime)
| where Result =~ "success"
| where OperationName in~ (SecurityInfoOps)
or (OperationName has_any (PasswordWords) and OperationName has_any (ChangeActionWords))
// The target user is read from whichever TargetResources entry has type "User" rather
// than a fixed array index, since these operations do not consistently place the target
// first; this matches the extraction used elsewhere in this repository for password
// reset events specifically.
| mv-apply TargetResource = TargetResources on (
where TargetResource.type =~ "User"
| extend TargetUpn = tolower(tostring(TargetResource.userPrincipalName))
)
| where TargetUpn in (BreakGlassAccounts)
| extend ActorUpn = tostring(InitiatedBy.user.userPrincipalName)
| extend ActorApp = tostring(InitiatedBy.app.displayName)
| extend Actor = iff(isnotempty(ActorUpn), ActorUpn, ActorApp)
| extend ActorIp = iff(
isnotempty(tostring(InitiatedBy.user.ipAddress)),
tostring(InitiatedBy.user.ipAddress),
tostring(InitiatedBy.app.ipAddress))
| extend AccountName = tostring(split(TargetUpn, "@")[0])
| extend AccountUPNSuffix = tostring(split(TargetUpn, "@")[1])
| project
TimeGenerated,
OperationName,
TargetUpn,
AccountName,
AccountUPNSuffix,
Actor,
ActorIp,
CorrelationId
| sort by TimeGenerated desc