A guest account being converted to a member could indicate an adversary attempting to escalate privileges or establish persistent access within the tenant. SOC teams should proactively hunt for this behavior to identify potential lateral movement or unauthorized access expansion in their Azure Sentinel environment.
KQL Query
AuditLogs
| where OperationName =~ "Update user"
| where Result =~ "success"
| mv-expand TargetResources
| mv-expand TargetResources.modifiedProperties
| where TargetResources_modifiedProperties.displayName =~ "TargetId.UserType"
| extend UpdatingAppName = tostring(parse_json(tostring(InitiatedBy.app)).displayName)
| extend UpdatingServicePrincipalId = tostring(parse_json(tostring(InitiatedBy.app)).servicePrincipalId)
| extend UpdatingUserPrincipalName = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
| extend UpdatingUserAadUserId = tostring(parse_json(tostring(InitiatedBy.user)).id)
| extend UpdatingUserIPAddress = tostring(parse_json(tostring(InitiatedBy.user)).ipAddress)
| extend UpdatingUser = iif(isnotempty(UpdatingServicePrincipalId), UpdatingServicePrincipalId, UpdatingUserPrincipalName)
| extend UpdatedUserPrincipalName = tostring(TargetResources.userPrincipalName)
| project-reorder TimeGenerated, UpdatedUserPrincipalName, UpdatingUser
| where parse_json(tostring(TargetResources_modifiedProperties.newValue)) =~ "\"Member\"" and parse_json(tostring(TargetResources_modifiedProperties.oldValue)) =~ "\"Guest\""
| extend InitiatingAccountName = tostring(split(UpdatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(UpdatingUserPrincipalName, "@")[1])
| extend TargetAccountName = tostring(split(UpdatedUserPrincipalName, "@")[0]), TargetAccountUPNSuffix = tostring(split(UpdatedUserPrincipalName, "@")[1])
id: a09a0b8e-30fe-4ebf-94a0-cffe50f579cd
name: User State changed from Guest to Member
description: |
'Detects when a guest account in a tenant is converted to a member of the tenant.
Monitoring guest accounts and the access they are provided is important to detect potential account abuse.
Accounts converted to members should be investigated to ensure the activity was legitimate.
Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-user-accounts#monitoring-for-failed-unusual-sign-ins'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1098
tags:
- AADSecOpsGuide
query: |
AuditLogs
| where OperationName =~ "Update user"
| where Result =~ "success"
| mv-expand TargetResources
| mv-expand TargetResources.modifiedProperties
| where TargetResources_modifiedProperties.displayName =~ "TargetId.UserType"
| extend UpdatingAppName = tostring(parse_json(tostring(InitiatedBy.app)).displayName)
| extend UpdatingServicePrincipalId = tostring(parse_json(tostring(InitiatedBy.app)).servicePrincipalId)
| extend UpdatingUserPrincipalName = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
| extend UpdatingUserAadUserId = tostring(parse_json(tostring(InitiatedBy.user)).id)
| extend UpdatingUserIPAddress = tostring(parse_json(tostring(InitiatedBy.user)).ipAddress)
| extend UpdatingUser = iif(isnotempty(UpdatingServicePrincipalId), UpdatingServicePrincipalId, UpdatingUserPrincipalName)
| extend UpdatedUserPrincipalName = tostring(TargetResources.userPrincipalName)
| project-reorder TimeGenerated, UpdatedUserPrincipalName, UpdatingUser
| where parse_json(tostring(TargetResources_modifiedProperties.newValue)) =~ "\"Member\"" and parse_json(tostring(TargetResources_modifiedProperties.oldValue)) =~ "\"Guest\""
| extend InitiatingAccountName = tostring(split(UpdatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(UpdatingUserPrincipalName, "@")[1])
| extend TargetAccountName = tostring(split(UpdatedUserPrincipalName, "@")[0]), TargetAccountUPNSuffix = tostring(split(UpdatedUserPrincipalName, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: UpdatingAppName
- identifier: AadUserId
columnName: UpdatingServicePrincipalId
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: UpdatingUserPrincipalName
- identifier: Name
columnName: InitiatingAccountName
- identifier: UPNSuffix
columnName: InitiatingAccountUPNSuffix
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: UpdatingUserAadUserId
- entityType: Account
fieldMappings:
- identifier: FullName
| Sentinel Table | Notes |
|---|---|
AuditLogs | Ensure this data connector is enabled |
Scenario: Scheduled Job Converts Guest to Member
Description: A scheduled job or automation process (e.g., Azure AD Connect, Microsoft 365 automation script) is configured to convert guest accounts to members as part of a user provisioning workflow.
Filter/Exclusion: Check for userPrincipalName or objectId matches against known automation accounts or service accounts. Use a filter like:
(userPrincipalName NOT IN ('[email protected]', '[email protected]'))
Scenario: Admin Manually Promotes Guest Account to Member
Description: An admin manually changes a guest account to a member via the Azure AD portal or PowerShell. This is a legitimate administrative task.
Filter/Exclusion: Filter out accounts associated with admin roles (e.g., userPrincipalName like [email protected], or userType = Member). Use:
(userType == "Guest" AND NOT (userPrincipalName LIKE "%[email protected]%"))
Scenario: User Accepts Invitation to Join Tenant
Description: A guest user accepts an invitation to join the tenant, which automatically converts their account to a member.
Filter/Exclusion: Filter out users who have accepted invitations by checking for invitationStatus = “Accepted” or userType = “Member” in the event logs. Use:
(userType == "Guest" AND invitationStatus != "Accepted")
Scenario: User Migration from Another Tenant
Description: A user account is migrated from another tenant (e.g., via Azure AD Connect or a migration tool), which may result in a guest-to-member transition.
Filter/Exclusion: Filter out users that are part of a known migration process