Detection rules › Kusto
Microsoft Entra ID Role Management Permission Grant
Identifies when the Microsoft Graph RoleManagement.ReadWrite.Directory (Delegated or Application) permission is granted to a service principal. This permission allows an application to read and manage the role-based access control (RBAC) settings for your company's directory. An adversary could use this permission to add an Microsoft Entra ID object to an Admin directory role and escalate privileges. Ref : https://docs.microsoft.com/graph/permissions-reference#role-management-permissions Ref : https://docs.microsoft.com/graph/api/directoryrole-post-members?view=graph-rest-1.0&tabs=http
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Azure | Add app role assignment to service principal |
| Azure | Add delegated permission grant |
Rules detecting the same action
These rules filter on the same operation.
- [Entra ID] Application Assigned Administrator Permissions Immediately After Obtaining Role Management Permissions (Kusto)
- [Entra ID] Application Granted Administrative Permission to Assign Microsoft Entra ID Roles (Kusto)
- Added Credentials to Existing Application (Sigma)
- Added Owner To Application (Sigma)
- Admin promotion after Role Management Application Permission Grant (Kusto)
- App Granted Microsoft Permissions (Sigma)
- App Granted Privileged Delegated Or App Permissions (Sigma)
- Application AppID Uri Configuration Changes (Sigma)
Rule body
id: 1ff56009-db01-4615-8211-d4fda21da02d
name: Microsoft Entra ID Role Management Permission Grant
description: |
'Identifies when the Microsoft Graph RoleManagement.ReadWrite.Directory (Delegated or Application) permission is granted to a service principal.
This permission allows an application to read and manage the role-based access control (RBAC) settings for your company's directory.
An adversary could use this permission to add an Microsoft Entra ID object to an Admin directory role and escalate privileges.
Ref : https://docs.microsoft.com/graph/permissions-reference#role-management-permissions
Ref : https://docs.microsoft.com/graph/api/directoryrole-post-members?view=graph-rest-1.0&tabs=http'
severity: High
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- Persistence
- Impact
relevantTechniques:
- T1098.003
- T1078.004
tags:
- SimuLand
query: |
AuditLogs
| where Category =~ "ApplicationManagement" and LoggedByService =~ "Core Directory" and OperationName in~ ("Add delegated permission grant", "Add app role assignment to service principal")
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "ServicePrincipal" and array_length(TargetResource.modifiedProperties) > 0 and isnotnull(TargetResource.displayName)
| extend props = TargetResource.modifiedProperties
)
| mv-apply Property = props on
(
where Property.displayName in~ ("AppRole.Value","DelegatedPermissionGrant.Scope")
| extend DisplayName = tostring(Property.displayName), PermissionGrant = trim('"',tostring(Property.newValue))
)
| where PermissionGrant has "RoleManagement.ReadWrite.Directory"
| mv-apply Property = props on
(
where Property.displayName =~ "ServicePrincipal.DisplayName"
| extend TargetAppDisplayName = trim('"',tostring(Property.newValue))
)
| mv-apply Property = props on
(
where Property.displayName =~ "ServicePrincipal.ObjectID"
| extend TargetAppServicePrincipalId = trim('"',tostring(Property.newValue))
)
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
| project TimeGenerated, OperationName, Result, PermissionGrant, TargetAppDisplayName, TargetAppServicePrincipalId, InitiatingAppName, InitiatingAppServicePrincipalId,
InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIpAddress, TargetResources, AdditionalDetails, CorrelationId
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: TargetAppDisplayName
- identifier: AadUserId
columnName: TargetAppServicePrincipalId
- entityType: Account
fieldMappings:
- identifier: Name
columnName: InitiatingAppName
- identifier: AadUserId
columnName: InitiatingAppServicePrincipalId
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: InitiatingUserPrincipalName
- identifier: Name
columnName: InitiatingAccountName
- identifier: UPNSuffix
columnName: InitiatingAccountUPNSuffix
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingAadUserId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: InitiatingIpAddress
version: 1.1.1
kind: Scheduled
Stages and Predicates
Stage 1: source
AuditLogs
Stage 2: where
| where Category =~ "ApplicationManagement" and LoggedByService =~ "Core Directory" and OperationName in~ ("Add delegated permission grant", "Add app role assignment to service principal")
Stage 3: kusto:mv-apply
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "ServicePrincipal" and array_length(TargetResource.modifiedProperties) > 0 and isnotnull(TargetResource.displayName)
| extend props = TargetResource.modifiedProperties
)
Stage 4: kusto:mv-apply
| mv-apply Property = props on
(
where Property.displayName in~ ("AppRole.Value","DelegatedPermissionGrant.Scope")
| extend DisplayName = tostring(Property.displayName), PermissionGrant = trim('"',tostring(Property.newValue))
)
Stage 5: where
| where PermissionGrant has "RoleManagement.ReadWrite.Directory"
Stage 6: kusto:mv-apply
| mv-apply Property = props on
(
where Property.displayName =~ "ServicePrincipal.DisplayName"
| extend TargetAppDisplayName = trim('"',tostring(Property.newValue))
)
Stage 7: kusto:mv-apply
| mv-apply Property = props on
(
where Property.displayName =~ "ServicePrincipal.ObjectID"
| extend TargetAppServicePrincipalId = trim('"',tostring(Property.newValue))
)
Stage 8: extend (5 consecutive steps)
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
Stage 9: project
| project TimeGenerated, OperationName, Result, PermissionGrant, TargetAppDisplayName, TargetAppServicePrincipalId, InitiatingAppName, InitiatingAppServicePrincipalId,
InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIpAddress, TargetResources, AdditionalDetails, CorrelationId
Stage 10: extend
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Category | eq |
| field:"Category" kind:eq value:"ApplicationManagement" |
LoggedByService | eq |
| field:"azure_ad::logged_by_service" kind:eq value:"Core Directory" |
OperationName | in |
| field:"OperationName" kind:in |
PermissionGrant | match |
| field:"PermissionGrant" kind:match value:"RoleManagement.ReadWrite.Directory" |
displayName | eq |
| field:"displayName" kind:eq |
displayName | in |
| field:"displayName" kind:in |
displayName | is_not_null | field:"displayName" kind:is_not_null | |
type | eq |
| field:"type" kind:eq value:"ServicePrincipal" |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AdditionalDetails | project |
CorrelationId | project |
InitiatingAadUserId | project |
InitiatingAppName | project |
InitiatingAppServicePrincipalId | project |
InitiatingIpAddress | project |
InitiatingUserPrincipalName | project |
OperationName | project |
PermissionGrant | project |
Result | project |
TargetAppDisplayName | project |
TargetAppServicePrincipalId | project |
TargetResources | project |
TimeGenerated | project |
InitiatingAccountName | extend |
InitiatingAccountUPNSuffix | extend |