↳ GitHub sourceAnalytics ruleMedium
Threat Essentials - NRT User added to Microsoft Entra ID Privileged Groups
Description
'This will alert when a user is added to any of the Privileged Groups.
For further information on AuditLogs please see https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities.
For Administrator role permissions in Microsoft Entra ID please see https://docs.microsoft.com/azure/active-directory/users-groups-roles/directory-assign-admin-roles'
- Rule type
- NRT
- Version
- 1.0.4
- Declared status
- Available
- Query frequency
- 1d
- Query period
- 14d
- Trigger
- gt 0
Declared MITRE coverage
Declared sources
Metadata from the source file. No dependencies inferred from KQL.
Connectors
Data types
KQL query
Original query, unchanged.
let OperationList = dynamic(["Add member to role","Add member to role in PIM requested (permanent)"]);
let PrivilegedGroups = dynamic(["UserAccountAdmins","PrivilegedRoleAdmins","TenantAdmins"]);
AuditLogs
//| where LoggedByService =~ "Core Directory"
| where Category =~ "RoleManagement"
| where OperationName in~ (OperationList)
| mv-expand TargetResources
| extend modProps = parse_json(TargetResources).modifiedProperties
| mv-expand bagexpansion=array modProps
| evaluate bag_unpack(modProps)
| extend displayName = column_ifexists("displayName", "NotAvailable"), newValue = column_ifexists("newValue", "NotAvailable")
| where displayName =~ "Role.WellKnownObjectName"
| extend DisplayName = displayName, GroupName = replace('"','',newValue)
| extend initByApp = parse_json(InitiatedBy).app, initByUser = parse_json(InitiatedBy).user
| extend AppId = initByApp.appId,
InitiatedByDisplayName = case(isnotempty(initByApp.displayName), initByApp.displayName, isnotempty(initByUser.displayName), initByUser.displayName, "not available"),
ServicePrincipalId = tostring(initByApp.servicePrincipalId),
ServicePrincipalName = tostring(initByApp.servicePrincipalName),
UserId = initByUser.id,
UserIPAddress = initByUser.ipAddress,
UserRoles = initByUser.roles,
UserPrincipalName = tostring(initByUser.userPrincipalName),
TargetUserPrincipalName = tostring(TargetResources.userPrincipalName)
| where GroupName in~ (PrivilegedGroups)
// If you don't want to alert for operations from PIM, remove below filtering for MS-PIM.
//| where InitiatedByDisplayName != "MS-PIM" and InitiatedByDisplayName != "MS-PIM-Fairfax"
| project TimeGenerated, AADOperationType, Category, OperationName, AADTenantId, AppId, InitiatedByDisplayName, ServicePrincipalId, ServicePrincipalName, DisplayName, GroupName, UserId, UserIPAddress, UserRoles, UserPrincipalName, TargetUserPrincipalName
| extend InitiatorAccount = case(isnotempty(ServicePrincipalName), ServicePrincipalName, isnotempty(ServicePrincipalId), ServicePrincipalId, isnotempty(UserPrincipalName), UserPrincipalName, "not available")
| extend InitiatorName = iif(InitiatorAccount has '@',tostring(split(InitiatorAccount,'@',0)[0]),"")
| extend InitiatorUPNSuffix = iif(InitiatorAccount has '@',tostring(split(InitiatorAccount,'@',1)[0]),"")
| extend InitiatorAadUserId = iif(InitiatorAccount !has '@',InitiatorAccount,"")
| extend TargetName = iif(TargetUserPrincipalName has '@',tostring(split(TargetUserPrincipalName,'@',0)[0]),"")
| extend TargetUPNSuffix = iif(TargetUserPrincipalName has '@',tostring(split(TargetUserPrincipalName,'@',1)[0]),"")
Declared entities
Related content
Links established from declared identifiers and solution manifests.
Source provenance
GitHubDisplayed values come from files in Azure/Azure-Sentinel. They describe the published template, not your workspace configuration.
- Commit
9800e51↗- Source identifier
0a627f29-f0dd-4924-be92-c3d6dac84367
GSTEP / CATALOG TRACKING
Added to catalog : 16 Sept 2026 · 05:49 UTC
Last change observed : 16 Sept 2026 · 05:49 UTC