Detection rules › Kusto
[Entra ID] Application Granted Administrative Permission to Assign Microsoft Entra ID Roles
Detects when an application receives permission to assign Microsoft Entra ID roles. This can enable directory privilege escalation and should be reviewed immediately.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation |
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)
- 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)
- Application ID URI Changed (Kusto)
Rule body
id: ef34b272-930c-41c8-a682-8c2093cd2024
name: "[Entra ID] Application Granted Administrative Permission to Assign Microsoft Entra ID Roles"
version: 1.0.0
kind: Scheduled
description: |
Detects when an application receives permission to assign Microsoft Entra ID roles. This can enable directory privilege escalation and should be reviewed immediately.
severity: High
status: Available
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
- Impact
- PrivilegeEscalation
relevantTechniques:
- T1098.003
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))
| extend TargetAppDisplayName = tostring(iff(isnotempty(TargetAppDisplayName), TargetAppDisplayName, TargetAppServicePrincipalId))
| extend
InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]),
InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
| extend Initiator = InitiatingAccountName, Target = TargetAppDisplayName
| extend
Source_Network_IPLocation = ""
| project
Alert_Category_en = "Entra ID",
Alert_SubCategory_en = "Anomaly Identity Privilege Modification",
Alert_Name_en = "Application Granted Administrative Permission to Assign Microsoft Entra ID Roles",
Alert_Description_en = strcat("At Taiwan time", format_datetime(datetime_utc_to_local(TimeGenerated, "Asia/Taipei"), "yyyy-MM-dd HH:mm:ss"), ". ", "EntraID:", "", ". ", "detected source user/service principal ", iff(isnotempty(InitiatingUserPrincipalName), InitiatingUserPrincipalName, InitiatingAppName), " granted application: ", TargetAppDisplayName, ".", " administrative permission to assign Microsoft Entra ID roles: ", ActivityDisplayName, ". Source IP: ", InitiatingIpAddress, ", source location: ", Source_Network_IPLocation),
Alert_TriageStep_en = strcat(
"1. Confirm whether the operating user/service principal: ",
iff(isnotempty(InitiatingUserPrincipalName), InitiatingUserPrincipalName, InitiatingAppName),
", source IP: ",
InitiatingIpAddress,
" is abnormal. ",
"2. Review the authorized target application ",
TargetAppDisplayName,
" authorization content: ",
ActivityDisplayName,
" to determine whether it is an expected operation or maintenance task."
),
Alert_Containment_en = strcat(
"1. If determined to be unauthorized or suspicious privilege escalation, immediately remove the permissions from target application: ",
TargetAppDisplayName,
" and disable it.",
"2. Perform risk handling for the initiator ",
iff(isnotempty(InitiatingUserPrincipalName), InitiatingUserPrincipalName, InitiatingAppName),
" (revoke sessions/force re-authentication, and suspend directory management permissions if necessary)."
),
Alert_Remediation_en =
"1. Strengthen application and permission governance: apply least privilege and administrator approval processes for high-risk Graph/Directory permissions, and include Service Principal credential/permission changes in regular audits. "
"2. Strengthen detection and automation: establish automated response (SOAR) for notification/source blocking/user verification.",
Alert_Time_TW = datetime_utc_to_local(TimeGenerated, "Asia/Taipei"),
Alert_Time_UTC0 = TimeGenerated,
Event_Action = ActivityDisplayName,
Event_Status = Result,
Event_Description = PermissionGrant,
Source_Identity_FullName = iff(isnotempty(InitiatingUserPrincipalName), InitiatingUserPrincipalName, InitiatingAppName),
Source_Identity_ID = iff(isnotempty(InitiatingAadUserId), InitiatingAadUserId, InitiatingAppServicePrincipalId),
Source_Identity_Type = iff(isnotempty(InitiatingUserPrincipalName), "User", "Service"),
Source_Network_IPAddress = InitiatingIpAddress,
Source_Network_IPLocation = Source_Network_IPLocation,
Target_Identity_FullName = TargetAppDisplayName,
Target_Identity_ID = TargetAppServicePrincipalId,
Target_Identity_Type = "Service",
Target_Resource_ID = "",
Target_Resource_Name = "Microsoft Entra ID",
Target_Resource_Type = "Microsoft Entra ID"
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Source_Identity_FullName
- entityType: IP
fieldMappings:
- identifier: Address
columnName: Source_Network_IPAddress
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: Target_Identity_FullName
alertDetailsOverride:
alertDynamicProperties: []
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 (9 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))
| extend TargetAppDisplayName = tostring(iff(isnotempty(TargetAppDisplayName), TargetAppDisplayName, TargetAppServicePrincipalId))
| extend
InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]),
InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
| extend Initiator = InitiatingAccountName, Target = TargetAppDisplayName
| extend
Source_Network_IPLocation = ""
Stage 9: project
| project
Alert_Category_en = "Entra ID",
Alert_SubCategory_en = "Anomaly Identity Privilege Modification",
Alert_Name_en = "Application Granted Administrative Permission to Assign Microsoft Entra ID Roles",
Alert_Description_en = strcat("At Taiwan time", format_datetime(datetime_utc_to_local(TimeGenerated, "Asia/Taipei"), "yyyy-MM-dd HH:mm:ss"), ". ", "EntraID:", "", ". ", "detected source user/service principal ", iff(isnotempty(InitiatingUserPrincipalName), InitiatingUserPrincipalName, InitiatingAppName), " granted application: ", TargetAppDisplayName, ".", " administrative permission to assign Microsoft Entra ID roles: ", ActivityDisplayName, ". Source IP: ", InitiatingIpAddress, ", source location: ", Source_Network_IPLocation),
Alert_TriageStep_en = strcat(
"1. Confirm whether the operating user/service principal: ",
iff(isnotempty(InitiatingUserPrincipalName), InitiatingUserPrincipalName, InitiatingAppName),
", source IP: ",
InitiatingIpAddress,
" is abnormal. ",
"2. Review the authorized target application ",
TargetAppDisplayName,
" authorization content: ",
ActivityDisplayName,
" to determine whether it is an expected operation or maintenance task."
),
Alert_Containment_en = strcat(
"1. If determined to be unauthorized or suspicious privilege escalation, immediately remove the permissions from target application: ",
TargetAppDisplayName,
" and disable it.",
"2. Perform risk handling for the initiator ",
iff(isnotempty(InitiatingUserPrincipalName), InitiatingUserPrincipalName, InitiatingAppName),
" (revoke sessions/force re-authentication, and suspend directory management permissions if necessary)."
),
Alert_Remediation_en =
"1. Strengthen application and permission governance: apply least privilege and administrator approval processes for high-risk Graph/Directory permissions, and include Service Principal credential/permission changes in regular audits. "
"2. Strengthen detection and automation: establish automated response (SOAR) for notification/source blocking/user verification.",
Alert_Time_TW = datetime_utc_to_local(TimeGenerated, "Asia/Taipei"),
Alert_Time_UTC0 = TimeGenerated,
Event_Action = ActivityDisplayName,
Event_Status = Result,
Event_Description = PermissionGrant,
Source_Identity_FullName = iff(isnotempty(InitiatingUserPrincipalName), InitiatingUserPrincipalName, InitiatingAppName),
Source_Identity_ID = iff(isnotempty(InitiatingAadUserId), InitiatingAadUserId, InitiatingAppServicePrincipalId),
Source_Identity_Type = iff(isnotempty(InitiatingUserPrincipalName), "User", "Service"),
Source_Network_IPAddress = InitiatingIpAddress,
Source_Network_IPLocation = Source_Network_IPLocation,
Target_Identity_FullName = TargetAppDisplayName,
Target_Identity_ID = TargetAppServicePrincipalId,
Target_Identity_Type = "Service",
Target_Resource_ID = "",
Target_Resource_Name = "Microsoft Entra ID",
Target_Resource_Type = "Microsoft Entra ID"
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 |
|---|---|
Alert_Category_en | project |
Alert_Containment_en | project |
Alert_Description_en | project |
Alert_Name_en | project |
Alert_Remediation_en | project |
Alert_SubCategory_en | project |
Alert_Time_TW | project |
Alert_Time_UTC0 | project |
Alert_TriageStep_en | project |
Event_Action | project |
Event_Description | project |
Event_Status | project |
Source_Identity_FullName | project |
Source_Identity_ID | project |
Source_Identity_Type | project |
Source_Network_IPAddress | project |
Source_Network_IPLocation | project |
Target_Identity_FullName | project |
Target_Identity_ID | project |
Target_Identity_Type | project |
Target_Resource_ID | project |
Target_Resource_Name | project |
Target_Resource_Type | project |