Guest accounts initiating privileged Entra ID operations may indicate a compromised account used for lateral movement or privilege escalation. SOC teams should proactively hunt for this behavior in Azure Sentinel to detect potential unauthorized access and mitigate advanced persistent threats.
KQL Query
let timeframe = 1d;
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",
"Add policy.",
"Delete policy."
]);
// Guest accounts with successful sign-ins in the window
let GuestAccounts =
SigninLogs
| where TimeGenerated >= ago(timeframe)
| where UserType =~ "Guest"
| where ResultType == 0
| extend UserUpn = tolower(UserPrincipalName)
| where isnotempty(UserUpn)
| summarize arg_max(TimeGenerated, IPAddress) by UserUpn
| project UserUpn, LastSignIn = TimeGenerated, SignInIp = IPAddress;
// Privileged audit operations initiated by those guest accounts
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)
| join kind=inner GuestAccounts on $left.ActorUpn == $right.UserUpn
| extend AccountName = tostring(split(ActorUpn, "@")[0])
| extend AccountUPNSuffix = tostring(split(ActorUpn, "@")[1])
| mv-expand TargetResource = TargetResources
| extend TargetName = tostring(TargetResource.displayName)
| summarize
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated),
Operations = make_set(OperationName),
Targets = make_set(TargetName),
EventCount = dcount(CorrelationId)
by ActorUpn, AccountName, AccountUPNSuffix, ActorIp, LastSignIn, SignInIp
| sort by FirstSeen desc
id: bb135137-8f33-45b0-8d0f-78437f79d558
name: Guest account initiating privileged Entra ID operation
description: |
Identifies guest accounts (B2B external identities) initiating high-impact Entra ID
operations: role assignments, service principal credentials, policy changes. Guest
as initiator suggests a compromised B2B account for persistence or lateral pivot.
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
- SigninLogs
tactics:
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1098.001
- T1098.003
- T1078.004
query: |
let timeframe = 1d;
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",
"Add policy.",
"Delete policy."
]);
// Guest accounts with successful sign-ins in the window
let GuestAccounts =
SigninLogs
| where TimeGenerated >= ago(timeframe)
| where UserType =~ "Guest"
| where ResultType == 0
| extend UserUpn = tolower(UserPrincipalName)
| where isnotempty(UserUpn)
| summarize arg_max(TimeGenerated, IPAddress) by UserUpn
| project UserUpn, LastSignIn = TimeGenerated, SignInIp = IPAddress;
// Privileged audit operations initiated by those guest accounts
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)
| join kind=inner GuestAccounts on $left.ActorUpn == $right.UserUpn
| extend AccountName = tostring(split(ActorUpn, "@")[0])
| extend AccountUPNSuffix = tostring(split(ActorUpn, "@")[1])
| mv-expand TargetResource = TargetResources
| extend TargetName = tostring(TargetResource.displayName)
| summarize
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated),
Operations = make_set(OperationName),
Targets = make_set(TargetName),
EventCount = dcount(CorrelationId)
by ActorUpn, AccountName, AccountUPNSuffix, ActorIp, LastSignIn, SignInIp
| sort by FirstSeen desc
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: ActorUpn
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ActorIp
version: 1.0.0
metadata:
source:
ki
| Sentinel Table | Notes |
|---|---|
AuditLogs | Ensure this data connector is enabled |
SigninLogs | Ensure this data connector is enabled |
Scenario: Scheduled job for password rotation using Azure Automation
Description: A scheduled Azure Automation job runs to rotate service principal passwords, which triggers the rule as it involves a privileged Entra ID operation.
Filter/Exclusion: AzureActivityName contains "PasswordRotation" or OperationName contains "PasswordRotation"
Scenario: Admin task to assign roles to external partners via Azure AD Privileged Identity Management (PIM)
Description: An admin manually assigns a role to a guest account as part of a compliance or access review process, which is flagged as a potential compromise.
Filter/Exclusion: OperationName contains "AssignRole" and UserPrincipalName contains "[email protected]"
Scenario: Entra ID connector health check using Azure Monitor
Description: A system health check tool (e.g., Azure Monitor) runs a diagnostic script that modifies Entra ID configurations, triggering the rule.
Filter/Exclusion: OperationName contains "HealthCheck" or ActivityName contains "DiagnosticTool"
Scenario: Automated policy update for compliance using Azure Policy
Description: An automated policy update via Azure Policy enforces a new configuration across Entra ID, which is flagged due to the high-impact nature of the operation.
Filter/Exclusion: OperationName contains "PolicyUpdate" or ResourceProvider contains "Microsoft.Entra"
Scenario: Guest account used for temporary access during a project handoff
Description: A guest account is used temporarily to grant access to a project team, and the account performs a role assignment or policy change as part of the handoff.
Filter/Exclusion: UserPrincipalName contains "[email protected]" or UserPrincipalName contains "[email protected]"