← Back to SOC feed Coverage →

OAuth application consent to high-risk permission scope

kql MEDIUM Azure-Sentinel
T1528
AuditLogs
huntingmicrosoftofficial
This rule was pulled from an open-source repository and enriched with AI. Validate in a test environment before deploying to production.
View original rule at Azure-Sentinel →
Retrieved: 2026-05-28T11:00:00Z · Confidence: medium

Hunt Hypothesis

Adversaries may be granting high-risk permissions to unknown applications through OAuth consent to escalate privileges or access sensitive data. SOC teams should proactively hunt for this behavior in Azure Sentinel to identify potential unauthorized access attempts and mitigate risks associated with overly permissive application permissions.

KQL 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

Analytic Rule Definition

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" ]

Required Data Sources

Sentinel TableNotes
AuditLogsEnsure this data connector is enabled

MITRE ATT&CK Context

References

False Positive Guidance

Original source: https://github.com/Azure/Azure-Sentinel/blob/main/Hunting Queries/MultipleDataSources/OAuthConsentToHighRiskPermissionScope.yaml