Detection rules › Kusto

Microsoft Entra ID Role Management Permission Grant

Status
available
Severity
high
Time window
2h
Source
github.com/Azure/Azure-Sentinel

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

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

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.

FieldKindValuesSearch
Categoryeq
  • ApplicationManagement
field:"Category" kind:eq value:"ApplicationManagement"
LoggedByServiceeq
  • Core Directory
field:"azure_ad::logged_by_service" kind:eq value:"Core Directory"
OperationNamein
  • Add app role assignment to service principal
  • Add delegated permission grant
field:"OperationName" kind:in
PermissionGrantmatch
  • RoleManagement.ReadWrite.Directory transforms: term
field:"PermissionGrant" kind:match value:"RoleManagement.ReadWrite.Directory"
displayNameeq
  • ServicePrincipal.DisplayName
  • ServicePrincipal.ObjectID
field:"displayName" kind:eq
displayNamein
  • AppRole.Value
  • DelegatedPermissionGrant.Scope
field:"displayName" kind:in
displayNameis_not_null
  • (no value, null check)
field:"displayName" kind:is_not_null
typeeq
  • ServicePrincipal
field:"type" kind:eq value:"ServicePrincipal"

Output fields

These fields are emitted when the rule matches.

FieldSource
AdditionalDetailsproject
CorrelationIdproject
InitiatingAadUserIdproject
InitiatingAppNameproject
InitiatingAppServicePrincipalIdproject
InitiatingIpAddressproject
InitiatingUserPrincipalNameproject
OperationNameproject
PermissionGrantproject
Resultproject
TargetAppDisplayNameproject
TargetAppServicePrincipalIdproject
TargetResourcesproject
TimeGeneratedproject
InitiatingAccountNameextend
InitiatingAccountUPNSuffixextend