← Back to SOC feed Coverage →

Suspicious linking of existing user to external User

kql MEDIUM Azure-Sentinel
T1078.004
AuditLogs
microsoftofficial
This rule was pulled from an open-source repository and enriched with AI. Validate in a test environment before deploying to production.
View original rule at Azure-Sentinel →
Retrieved: 2026-03-25T03:06:09Z · Confidence: medium

Hunt Hypothesis

Adversaries may attempt to link existing users to external identities to establish persistent access or exfiltrate data. SOC teams should proactively hunt for this behavior in Azure Sentinel to identify potential lateral movement or identity compromise attempts.

KQL Query

AuditLogs
| where OperationName=~ "Update user" 
| where Result =~ "success" 
| mv-expand TargetResources 
| mv-expand TargetResources.modifiedProperties 
| extend displayName = tostring(TargetResources_modifiedProperties.displayName), 
TargetUPN_oldValue = tostring(parse_json(tostring(TargetResources_modifiedProperties.oldValue))[0]), 
TargetUPN_newValue = tostring(parse_json(tostring(TargetResources_modifiedProperties.newValue))[0])
| where displayName == "UserPrincipalName" and TargetUPN_oldValue !has "#EXT" and TargetUPN_newValue has "#EXT"
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIPAddress = tostring(InitiatedBy.user.ipAddress)
| extend InitiatedBy = tostring(iff(isnotempty(InitiatingUserPrincipalName),InitiatingUserPrincipalName, InitiatingAppName))
| summarize arg_max(TimeGenerated, *) by CorrelationId
| project-reorder TimeGenerated, InitiatedBy, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIPAddress, TargetUPN_oldValue, TargetUPN_newValue
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
| extend TargetAccountName = tostring(split(TargetUPN_oldValue, "@")[0]), TargetUPNSuffix = tostring(split(TargetUPN_oldValue, "@")[1])

Analytic Rule Definition

id: 22a320c2-e1e5-4c74-a35b-39fc9cdcf859
name: Suspicious linking of existing user to external User
description: |
  ' This query will detect when an attempt is made to update an existing user and link it to an guest or external identity. These activities are unusual and such linking of external 
  identities should be investigated. In some cases you may see internal Entra ID sync accounts (Sync_) do this which may be benign'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - PrivilegeEscalation
relevantTechniques:
  - T1078.004
tags:
  - GuestorExternalIdentities
query: |
  AuditLogs
  | where OperationName=~ "Update user" 
  | where Result =~ "success" 
  | mv-expand TargetResources 
  | mv-expand TargetResources.modifiedProperties 
  | extend displayName = tostring(TargetResources_modifiedProperties.displayName), 
  TargetUPN_oldValue = tostring(parse_json(tostring(TargetResources_modifiedProperties.oldValue))[0]), 
  TargetUPN_newValue = tostring(parse_json(tostring(TargetResources_modifiedProperties.newValue))[0])
  | where displayName == "UserPrincipalName" and TargetUPN_oldValue !has "#EXT" and TargetUPN_newValue has "#EXT"
  | extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
  | extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
  | extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
  | extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
  | extend InitiatingIPAddress = tostring(InitiatedBy.user.ipAddress)
  | extend InitiatedBy = tostring(iff(isnotempty(InitiatingUserPrincipalName),InitiatingUserPrincipalName, InitiatingAppName))
  | summarize arg_max(TimeGenerated, *) by CorrelationId
  | project-reorder TimeGenerated, InitiatedBy, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIPAddress, TargetUPN_oldValue, TargetUPN_newValue
  | extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
  | extend TargetAccountName = tostring(split(TargetUPN_oldValue, "@")[0]), TargetUPNSuffix = tostring(split(TargetUPN_oldValue, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: InitiatingAppName
      - identifier: AadUserId
        columnName: InitiatingAppServicePrincipalId
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: InitiatingUserPrincipalName
      - identifier: Name
        columnName: InitiatingAccountName
      - identifier: UPNSuffix
        columnName: InitiatingAccountUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: InitiatingAadUserId
  - entityType: Account
    fieldMapping

Required Data Sources

Sentinel TableNotes
AuditLogsEnsure this data connector is enabled

MITRE ATT&CK Context

References

False Positive Guidance

Original source: https://github.com/Azure/Azure-Sentinel/blob/main/Detections/AuditLogs/SuspiciousLinkingofExternalIdtoExistingUsers.yaml