Adversaries may add guest or external accounts to privileged Entra ID roles to gain unauthorized access and escalate privileges within the environment. Proactively hunting for this behavior helps SOC teams identify potential lateral movement or privilege escalation attempts early, enabling timely mitigation in Azure Sentinel.
KQL Query
let starttime = todatetime('{{StartTimeISO}}');
let endtime = todatetime('{{EndTimeISO}}');
let privilegedRoles = dynamic([
"Global Administrator",
"Privileged Role Administrator",
"Security Administrator",
"Exchange Administrator",
"SharePoint Administrator",
"Application Administrator",
"Cloud Application Administrator",
"Authentication Administrator",
"Conditional Access Administrator",
"Helpdesk Administrator",
"User Administrator"
]);
AuditLogs
| where TimeGenerated between (starttime .. endtime)
| where OperationName =~ "Add member to role."
| where Result =~ "success"
// Extract actor
| extend ActorUpn = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
| extend ActorApp = tostring(parse_json(tostring(InitiatedBy.app)).displayName)
| extend ActorIp = iff(
isnotempty(tostring(parse_json(tostring(InitiatedBy.user)).ipAddress)),
tostring(parse_json(tostring(InitiatedBy.user)).ipAddress),
tostring(parse_json(tostring(InitiatedBy.app)).ipAddress))
| extend Actor = iff(isnotempty(ActorUpn), ActorUpn, ActorApp)
// Expand target resources to find both the user and the role entries
| mv-expand TargetResource = TargetResources
| extend ResourceType = tostring(TargetResource.type)
| extend ResourceUPN = tostring(TargetResource.userPrincipalName)
| extend ResourceName = tostring(TargetResource.displayName)
// Identify the target user (guest/external) by #EXT# marker
| where ResourceType =~ "User" and ResourceUPN has "#EXT#"
| extend GuestUPN = ResourceUPN
| extend GuestId = tostring(TargetResource.id)
// Join back to find the role name in the same event
| join kind=inner (
AuditLogs
| where TimeGenerated between (starttime .. endtime)
| where OperationName =~ "Add member to role."
| where Result =~ "success"
| mv-expand TargetResource = TargetResources
| where tostring(TargetResource.type) =~ "Role"
| extend RoleName = tostring(TargetResource.displayName)
| summarize RoleName = any(RoleName) by CorrelationId
) on CorrelationId
// Keep only privileged roles
| where RoleName in~ (privilegedRoles)
| extend AccountName = tostring(split(GuestUPN, "@")[0])
| extend AccountUPNSuffix = tostring(split(GuestUPN, "@")[1])
| extend ActorName = iff(ActorUpn has "@", tostring(split(ActorUpn, "@")[0]), Actor)
| extend ActorUPNSuffix = iff(ActorUpn has "@", tostring(split(ActorUpn, "@")[1]), "")
| project
TimeGenerated,
GuestUPN,
AccountName,
AccountUPNSuffix,
GuestId,
RoleName,
Actor,
ActorName,
ActorUPNSuffix,
ActorIp,
CorrelationId
| sort by TimeGenerated desc
id: abed6064-9406-4171-a961-5fd38de5f79a
name: Guest or external account added to a privileged Entra ID role
description: |
Hunting query that identifies guest or external accounts being added to privileged
Entra ID directory roles. External accounts are identified by the presence of #EXT#
in the UserPrincipalName, which is the standard suffix assigned by Entra ID to all
guest and B2B invited users. Privileged roles covered include Global Administrator,
Privileged Role Administrator, Security Administrator, Exchange Administrator,
SharePoint Administrator, Application Administrator, Cloud Application Administrator,
Authentication Administrator, Conditional Access Administrator, Helpdesk Administrator,
and User Administrator.
Adding an external identity to a privileged role is an unusual operation in most
tenants and can indicate account compromise, insider threat, or a misconfigured
administrative workflow. Analysts must validate every result. Benign matches include
authorized partner or consultant access with documented approval.
This query uses AuditLogs only and does not require Microsoft Defender XDR or
Entra ID P2 licensing.
References:
- https://learn.microsoft.com/azure/active-directory/roles/permissions-reference
- https://learn.microsoft.com/azure/active-directory/external-identities/user-properties
- https://attack.mitre.org/techniques/T1098/003/
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
tactics:
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1098.003
query: |
let starttime = todatetime('{{StartTimeISO}}');
let endtime = todatetime('{{EndTimeISO}}');
let privilegedRoles = dynamic([
"Global Administrator",
"Privileged Role Administrator",
"Security Administrator",
"Exchange Administrator",
"SharePoint Administrator",
"Application Administrator",
"Cloud Application Administrator",
"Authentication Administrator",
"Conditional Access Administrator",
"Helpdesk Administrator",
"User Administrator"
]);
AuditLogs
| where TimeGenerated between (starttime .. endtime)
| where OperationName =~ "Add member to role."
| where Result =~ "success"
// Extract actor
| extend ActorUpn = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
| extend ActorApp = tostring(parse_json(tostring(InitiatedBy.app)).displayName)
| extend ActorIp = iff(
isnotempty(tostring(parse_json(tostring(InitiatedBy.user)).ipAddress)),
tostring(parse_json(tostring(InitiatedBy.user)).ipAddress),
tostring(parse_json(tostring(InitiatedBy.app)).ipAddress))
| extend Actor = iff(isnotempty(ActorUpn), ActorUpn, ActorApp)
// Expand target resources to find both the user and the role entries
| mv-expand TargetResource = TargetResources
| extend ResourceType = tostring(TargetResource.type)
| extend ResourceUPN = tostring(TargetResource.userPrincipa
| Sentinel Table | Notes |
|---|---|
AuditLogs | Ensure this data connector is enabled |
Scenario: Scheduled job for user provisioning
Description: A scheduled job (e.g., Azure Automation or Power Automate) provisions guest users to a privileged role as part of a regular onboarding process.
Filter/Exclusion: Check for UserPrincipalName containing a known provisioning prefix (e.g., provisioning_) or filter by createdBy to exclude system accounts like AzureADSync.
Scenario: Admin task to add external auditors
Description: An admin manually adds an external auditor to a privileged role for compliance purposes (e.g., using Azure AD admin center or PowerShell).
Filter/Exclusion: Exclude users with UserPrincipalName containing auditor@ or external.auditor@ or filter by createdBy to exclude specific admin accounts.
Scenario: Azure AD Connect synchronization
Description: A guest user is added to a privileged role during directory synchronization (e.g., via Azure AD Connect or Azure AD Connect Health).
Filter/Exclusion: Filter out users with UserPrincipalName containing sync_ or connector_ or exclude changes made by the synchronization service account.
Scenario: Test account for security testing
Description: A test account (e.g., [email protected]) is temporarily added to a privileged role for security testing or penetration testing.
Filter/Exclusion: Exclude users with UserPrincipalName containing test, dev, or qa, or filter by createdBy to exclude test environments or specific test accounts.
Scenario: External collaboration with shared access
Description: An external user is added to a privileged role for collaboration (e.g., via Microsoft Teams or SharePoint) with shared access permissions.
Filter/Exclusion: Exclude users with UserPrincipalName containing `