Detection rules › Kusto
Threat Essentials - User Assigned Privileged Role
Identifies when a new privileged role is assigned to a user. Any account eligible for a role is now being given privileged access. If the assignment is unexpected or into a role that isn't the responsibility of the account holder, investigate. Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-privileged-accounts#things-to-monitor-1
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence | T1078.004 Valid Accounts: Cloud Accounts |
Event coverage
| Provider | Event | Title |
|---|---|---|
| Entra-AuditLogs | _catch_all | Entra ID audit event (any operation) |
Rules detecting the same action
Other rules on this platform that filter on the same API call or operation.
- Admin promotion after Role Management Application Permission Grant (Kusto)
- Bulk Changes to Privileged Account Permissions (Kusto)
- Changes to PIM Settings (Kusto)
- Detect PIM Alert Disabling activity (Kusto)
- Multiple admin membership removals from newly created admin. (Kusto)
- New External User Granted Admin Role (Kusto)
- NRT Privileged Role Assigned Outside PIM (Kusto)
- NRT User added to Microsoft Entra ID Privileged Groups (Kusto)
Rule body kusto
id: b09795c9-8dce-47ab-8f75-5a4afb78ef0c
name: Threat Essentials - User Assigned Privileged Role
description: |
'Identifies when a new privileged role is assigned to a user. Any account eligible for a role is now being given privileged access. If the assignment is unexpected or into a role that isn't the responsibility of the account holder, investigate.
Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-privileged-accounts#things-to-monitor-1'
severity: High
status: Available
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1078.004
tags:
- AADSecOpsGuide
query: |
AuditLogs
| where Category =~ "RoleManagement"
| where AADOperationType in~ ("Assign", "AssignEligibleRole")
| where ActivityDisplayName has_any ("Add eligible member to role", "Add member to role")
| mv-expand TargetResources
| mv-expand TargetResources.modifiedProperties
| extend displayName_ = tostring(TargetResources_modifiedProperties.displayName)
| where displayName_ =~ "Role.DisplayName"
| extend RoleName = tostring(parse_json(tostring(TargetResources_modifiedProperties.newValue)))
| where RoleName contains "Admin"
| extend InitiatingApp = tostring(parse_json(tostring(InitiatedBy.app)).displayName)
| extend Initiator = iif(isnotempty(InitiatingApp), InitiatingApp, tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName))
| extend Target = tostring(TargetResources.userPrincipalName)
| summarize by bin(TimeGenerated, 1h), OperationName, RoleName, Target, Initiator, Result
| extend InitiatorName=split(Initiator, "@")[0], InitiatorUPNSuffix=split(Initiator, "@")[1]
| extend TargetName=split(Target, "@")[0], TargetUPNSuffix=split(Target, "@")[1]
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: InitiatorName
- identifier: UPNSuffix
columnName: InitiatorUPNSuffix
- entityType: Account
fieldMappings:
- identifier: Name
columnName: TargetName
- identifier: UPNSuffix
columnName: TargetUPNSuffix
version: 1.0.3
kind: Scheduled
Stages and Predicates
Stage 1: source
AuditLogs
Stage 2: where
| where Category =~ "RoleManagement"
Stage 3: where
| where AADOperationType in~ ("Assign", "AssignEligibleRole")
Stage 4: where
| where ActivityDisplayName has_any ("Add eligible member to role", "Add member to role")
Stage 5: mv-expand
| mv-expand TargetResources
Stage 6: mv-expand
| mv-expand TargetResources.modifiedProperties
Stage 7: extend
| extend displayName_ = tostring(TargetResources_modifiedProperties.displayName)
Stage 8: where
| where displayName_ =~ "Role.DisplayName"
Stage 9: extend
| extend RoleName = tostring(parse_json(tostring(TargetResources_modifiedProperties.newValue)))
Stage 10: where
| where RoleName contains "Admin"
Stage 11: extend (3 consecutive steps)
| extend InitiatingApp = tostring(parse_json(tostring(InitiatedBy.app)).displayName)
| extend Initiator = iif(isnotempty(InitiatingApp), InitiatingApp, tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName))
| extend Target = tostring(TargetResources.userPrincipalName)
Stage 12: summarize
| summarize by bin(TimeGenerated, 1h), OperationName, RoleName, Target, Initiator, Result
Stage 13: extend
| extend InitiatorName=split(Initiator, "@")[0], InitiatorUPNSuffix=split(Initiator, "@")[1]
Stage 14: extend
| extend TargetName=split(Target, "@")[0], TargetUPNSuffix=split(Target, "@")[1]
Indicators
Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.
| Field | Kind | Values |
|---|---|---|
AADOperationType | in |
|
ActivityDisplayName | match |
|
Category | eq |
|
RoleName | contains |
|
displayName_ | eq |
|
Output fields
Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.
| Field | Source |
|---|---|
Initiator | summarize |
OperationName | summarize |
Result | summarize |
RoleName | summarize |
Target | summarize |
InitiatorName | extend |
InitiatorUPNSuffix | extend |
TargetName | extend |
TargetUPNSuffix | extend |