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
Raw source Entra ID account performs privileged operation shortly after admin password reset · KQL
Esc
Published by Azure/Azure-Sentinel ↗, licensed under MIT ↗. Reproduced here unmodified.
id: a7589e25-ff97-48ab-aa2f-8de1df7ed9c0
name: 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.
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
tactics:
  - Persistence
  - PrivilegeEscalation
relevantTechniques:
  - T1098
  - T1098.003
  - T1078.004
query: |
  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
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: ResetTargetUpn
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ActorIp
version: 1.0.0
metadata:
    source:
        kind: Community
    author:
        name: descambiado
    support:
        tier: Community
    categories:
        domains: [ "Security - Threat Protection", "Identity" ]

Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.