OAuth application consent to high-risk permission scope


Description

Identifies OAuth application consent events where high-risk permissions such as Directory.ReadWrite.All or RoleManagement.ReadWrite.Directory were granted to apps with no prior tenant consent history in the preceding 90 days.

Query · kql

let timeframe = 1d;
let lookback = 90d;
let HighRiskScopes = dynamic([
    "RoleManagement.ReadWrite.Directory",
    "Application.ReadWrite.All",
    "AppRoleAssignment.ReadWrite.All",
    "Directory.ReadWrite.All",
    "User.ReadWrite.All",
    "Mail.ReadWrite",
    "Mail.Send",
    "Files.ReadWrite.All",
    "full_access_as_app"
]);
let KnownApps =
    AuditLogs
    | where TimeGenerated >= ago(timeframe + lookback) and TimeGenerated < ago(timeframe)
    | where OperationName =~ "Consent to application"
    | extend AppId = tostring(TargetResources[0].id)
    | distinct AppId;
AuditLogs
| where TimeGenerated >= ago(timeframe)
| where OperationName =~ "Consent to application"
| where Result =~ "success"
| extend AppId    = tostring(TargetResources[0].id)
| extend AppName  = tostring(TargetResources[0].displayName)
| extend ActorUpn = tostring(InitiatedBy.user.userPrincipalName)
| extend ActorApp = tostring(InitiatedBy.app.displayName)
| extend Actor    = iff(isnotempty(ActorUpn), ActorUpn, ActorApp)
| extend ActorIp  = iff(
      isnotempty(tostring(InitiatedBy.user.ipAddress)),
      tostring(InitiatedBy.user.ipAddress),
      tostring(InitiatedBy.app.ipAddress))
| mv-expand ModProp = TargetResources[0].modifiedProperties
| where tostring(ModProp.displayName) =~ "ConsentAction.Permissions"
| extend GrantedPermissions = tostring(ModProp.newValue)
| where GrantedPermissions has_any (HighRiskScopes)
| join kind=leftanti KnownApps on AppId
| extend AccountName      = iff(Actor has "@", tostring(split(Actor, "@")[0]), Actor)
| extend AccountUPNSuffix = iff(Actor has "@", tostring(split(Actor, "@")[1]), "")
| project TimeGenerated, AppName, AppId, GrantedPermissions, Actor,
          AccountName, AccountUPNSuffix, ActorIp, CorrelationId
| sort by TimeGenerated desc
Raw source OAuth application consent to high-risk permission scope · KQL
Esc
Published by Azure/Azure-Sentinel ↗, licensed under MIT ↗. Reproduced here unmodified.
id: 2a166359-a104-4d72-93ae-643ae69bf801
name: OAuth application consent to high-risk permission scope
description: |
  Identifies OAuth application consent events where high-risk permissions such as
  Directory.ReadWrite.All or RoleManagement.ReadWrite.Directory were granted to apps
  with no prior tenant consent history in the preceding 90 days.
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
tactics:
  - Persistence
  - CredentialAccess
relevantTechniques:
  - T1528
query: |
  let timeframe = 1d;
  let lookback = 90d;
  let HighRiskScopes = dynamic([
      "RoleManagement.ReadWrite.Directory",
      "Application.ReadWrite.All",
      "AppRoleAssignment.ReadWrite.All",
      "Directory.ReadWrite.All",
      "User.ReadWrite.All",
      "Mail.ReadWrite",
      "Mail.Send",
      "Files.ReadWrite.All",
      "full_access_as_app"
  ]);
  let KnownApps =
      AuditLogs
      | where TimeGenerated >= ago(timeframe + lookback) and TimeGenerated < ago(timeframe)
      | where OperationName =~ "Consent to application"
      | extend AppId = tostring(TargetResources[0].id)
      | distinct AppId;
  AuditLogs
  | where TimeGenerated >= ago(timeframe)
  | where OperationName =~ "Consent to application"
  | where Result =~ "success"
  | extend AppId    = tostring(TargetResources[0].id)
  | extend AppName  = tostring(TargetResources[0].displayName)
  | extend ActorUpn = tostring(InitiatedBy.user.userPrincipalName)
  | extend ActorApp = tostring(InitiatedBy.app.displayName)
  | extend Actor    = iff(isnotempty(ActorUpn), ActorUpn, ActorApp)
  | extend ActorIp  = iff(
        isnotempty(tostring(InitiatedBy.user.ipAddress)),
        tostring(InitiatedBy.user.ipAddress),
        tostring(InitiatedBy.app.ipAddress))
  | mv-expand ModProp = TargetResources[0].modifiedProperties
  | where tostring(ModProp.displayName) =~ "ConsentAction.Permissions"
  | extend GrantedPermissions = tostring(ModProp.newValue)
  | where GrantedPermissions has_any (HighRiskScopes)
  | join kind=leftanti KnownApps on AppId
  | extend AccountName      = iff(Actor has "@", tostring(split(Actor, "@")[0]), Actor)
  | extend AccountUPNSuffix = iff(Actor has "@", tostring(split(Actor, "@")[1]), "")
  | project TimeGenerated, AppName, AppId, GrantedPermissions, Actor,
            AccountName, AccountUPNSuffix, ActorIp, CorrelationId
  | sort by TimeGenerated desc
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Actor
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ActorIp
version: 1.0.0
metadata:
    source:
        kind: Community
    author:
        name: descambiado
    support:
        tier: Community
    categories:
        domains: [ "Security - Threat Protection", "Identity" ]

Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.