Query Details

13 AAD Prov Match Anchor Privileged Takeover

Query

id: 9b1a000d-100d-410d-910d-aadprov0000d
name: Match-Anchor Modification on Privileged Cloud User
version: 1.0.0
kind: Scheduled
description: |
  Detects modification of identity matching anchors (`userPrincipalName`,
  `mail`, `proxyAddresses`, `onPremisesImmutableId`, `mailNickname`) on an
  Entra ID user that holds a directory role. Modifying these attributes on a
  cloud admin enables the documented soft/hard-match takeover - the attacker
  forces a matching on-premises object to bind to the privileged cloud
  account on the next sync cycle. Covers both `AADProvisioningLogs`
  (provisioning channel) and `AuditLogs` (direct Graph writes).
  Complements RULE-07 (DirSync feature toggle): even with BlockSoftMatch
  enabled, the cloud-side anchor change is the same TTP signature.
  MITRE ATT&CK: T1098 (Account Manipulation), T1556.006 (Modify
  Authentication Process: Multi-Factor Authentication), T1078.004
  (Valid Accounts: Cloud Accounts).
severity: High
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AADProvisioningLogs
      - AuditLogs
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - PrivilegeEscalation
  - Persistence
  - DefenseEvasion
relevantTechniques:
  - T1098
  - T1556
query: |
  // High-fidelity anchors only. proxyAddresses / otherMails removed:
  // they churn constantly through Exchange mail-flow and shared-mailbox
  // lifecycle and generate FPs on any cloud admin who also has a mailbox.
  let AnchorAttrs = dynamic([
      "userPrincipalName","UserPrincipalName",
      "onPremisesImmutableId","ImmutableId","sourceAnchor",
      "mailNickname","MailNickname"
  ]);
  let PrivilegedUsers =
      IdentityInfo
      | where TimeGenerated > ago(14d)
      | summarize arg_max(TimeGenerated, *) by AccountUPN
      | where isnotempty(AssignedRoles) and AssignedRoles != "[]"
      | project AccountUPN, AccountObjectId, AssignedRoles;
  let FromProvisioning =
      AADProvisioningLogs
      | where TimeGenerated > ago(1h)
      | where ResultType =~ "Success"
      | extend TargetUpn = tostring(parse_json(TargetIdentity).userPrincipalName),
               SPName    = tostring(parse_json(ServicePrincipal).Name)
      | mv-expand Mod = todynamic(ModifiedProperties)
      | extend PropName = tostring(Mod.displayName),
               OldValue = tostring(Mod.oldValue),
               NewValue = tostring(Mod.newValue)
      | where PropName in~ (AnchorAttrs)
      | project TimeGenerated, Channel = "Provisioning", Actor = SPName,
                TargetUpn, PropName, OldValue, NewValue, SourceIP = "";
  let FromAudit =
      AuditLogs
      | where TimeGenerated > ago(1h)
      | where OperationName has_any ("Update user","Change user UPN","Set user properties","Update User")
      | mv-expand TargetResources
      | extend TargetUpn  = tostring(TargetResources.userPrincipalName)
      | extend Actor      = coalesce(tostring(InitiatedBy.user.userPrincipalName),
                                      tostring(InitiatedBy.app.displayName))
      | extend SourceIP   = tostring(InitiatedBy.user.ipAddress)
      | mv-expand Mod = todynamic(TargetResources.modifiedProperties)
      | extend PropName = tostring(Mod.displayName),
               OldValue = tostring(Mod.oldValue),
               NewValue = tostring(Mod.newValue)
      | where PropName in~ (AnchorAttrs)
      | project TimeGenerated, Channel = "AuditLogs", Actor,
                TargetUpn, PropName, OldValue, NewValue, SourceIP;
  union FromProvisioning, FromAudit
  | join kind=inner (PrivilegedUsers) on $left.TargetUpn == $right.AccountUPN
  | project TimeGenerated, Channel, Actor, SourceIP, TargetUpn, AssignedRoles,
            PropName, OldValue, NewValue
  | order by TimeGenerated desc
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: TargetUpn
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Actor
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP
customDetails:
  Channel: Channel
  PropName: PropName
  AssignedRoles: AssignedRoles
alertDetailsOverride:
  alertDisplayNameFormat: "Match-anchor {{PropName}} modified on privileged user {{TargetUpn}}"
  alertDescriptionFormat: "{{Actor}} modified matching anchor {{PropName}} on privileged user {{TargetUpn}}. Soft/Hard-match takeover indicator - see Channel custom detail."
  alertSeverityColumnName: ""
  alertTacticsColumnName: ""
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: PT24H
    matchingMethod: AnyAlert
    groupByEntities:
      - Account
    groupByAlertDetails: []
    groupByCustomDetails: []

Explanation

This query is designed to detect suspicious modifications to certain key attributes (known as "matching anchors") of privileged cloud user accounts in Microsoft Entra ID (formerly Azure Active Directory). These attributes include userPrincipalName, mail, proxyAddresses, onPremisesImmutableId, and mailNickname. Changes to these attributes can enable an attacker to take over a privileged cloud account by binding it to a matching on-premises object during synchronization.

Here's a simplified breakdown of the query:

  1. Purpose: The query aims to identify changes to specific attributes of privileged users in the cloud directory, which could indicate an account takeover attempt.

  2. Data Sources: It uses two main data sources:

    • AADProvisioningLogs: Logs related to provisioning activities.
    • AuditLogs: Logs related to direct modifications via Graph API.
  3. Detection Logic:

    • It first identifies privileged users by checking for assigned directory roles.
    • It then looks for successful modifications to the specified attributes within the last hour from both provisioning and audit logs.
    • The query filters out frequent changes to certain attributes like proxyAddresses to reduce false positives.
  4. Alerting:

    • If a modification is detected, it generates an alert with details about the change, including who made the change (Actor), the target user (TargetUpn), and the old and new values of the modified attribute.
    • The alert is categorized under tactics like Privilege Escalation, Persistence, and Defense Evasion, aligning with MITRE ATT&CK techniques.
  5. Incident Management:

    • The query is scheduled to run every hour and will create an incident if any suspicious activity is detected.
    • It groups related alerts into incidents based on the affected account to streamline investigation.

Overall, this query helps security teams monitor and respond to potential account manipulation attempts on privileged cloud users.

Details

David Alonso profile picture

David Alonso

Released: June 1, 2026

Tables

AADProvisioningLogsAuditLogsIdentityInfo

Keywords

IdentityEntraIDUserDirectoryRoleCloudAdminAADProvisioningLogsAuditLogsAccountPrivilegedUsersAzureActiveDirectoryIdentityInfoAccountUPNAccountObjectIdAssignedRolesTargetUpnServicePrincipalModifiedPropertiesTimeGeneratedChannelActorSourceIPTargetResourcesInitiatedByUserPrincipalNameImmutableIdMailNickname

Operators

letdynamicwhereagosummarizearg_maxisnotemptyprojectextendtostringparse_jsonmv-expandtodynamicin~has_anycoalesceunionjoinonorder by

Actions