Entra ID End-User Consent to Application with High-Risk Delegated Scopes


Description

Identifies an end-user (non-admin) consent grant in Microsoft Entra ID to an application requesting delegated scopes commonly abused in illicit consent grant (OAuth phishing) attacks, such as mail, mailbox settings, contacts, files, SharePoint, OneNote, Teams chat and Exchange Web Services. Graph scopes are evaluated together with offline_access, which returns a long-lived refresh token and gives an attacker-controlled application durable, silent access to the victim's data without stealing a password or re-prompting for MFA. The alert classifies the application owner as Microsoft first-party, in-tenant or external to help prioritize review.

Query · esql

FROM logs-azure.auditlogs-* metadata _id, _version, _index
| WHERE (azure.auditlogs.operation_name == "Consent to application"
    OR event.action == "Consent to application")
  AND event.outcome == "success"
  // End-user (non-admin) consent only
  AND TO_LOWER(`azure.auditlogs.properties.target_resources.0.modified_properties.0.new_value`) LIKE "*false*"

// Normalize ConsentAction.Permissions into a lower-cased, space-padded token string so " token " is an exact scope match.
// Raw: "[] => [[Id: .., ResourceId: .., ConsentType: Principal, Scope:  Mail.Read offline_access .., ..]]"
| EVAL Esql.scopes = CONCAT(" ", REPLACE(TO_LOWER(`azure.auditlogs.properties.target_resources.0.modified_properties.4.new_value`), """[\[\],;"]""", " "), " ")

// additional_details.key/value are parallel arrays but ES|QL returns multivalues sorted, so pair via MV_CONTAINS, not position.
// Owner = AppOwnerOrganizationId (Microsoft tenants) OR well-known first-party public-client AppIds (not always stamped with owner org).
| EVAL
    Esql.app_owner_type = CASE(
        MV_CONTAINS(azure.auditlogs.properties.additional_details.value, "f8cdef31-a31e-4b4a-93e4-5f571e91255a")   // Microsoft Services
          OR MV_CONTAINS(azure.auditlogs.properties.additional_details.value, "72f988bf-86f1-41af-91ab-2d7cd011db47") // Microsoft corp
          OR MV_CONTAINS(azure.auditlogs.properties.additional_details.value, "14d82eec-204b-4c2f-b7e8-296a70dab67e") // Microsoft Graph Command Line Tools
          OR MV_CONTAINS(azure.auditlogs.properties.additional_details.value, "de8bc8b5-d9f9-48b1-a8ad-b748da725064") // Graph Explorer
          OR MV_CONTAINS(azure.auditlogs.properties.additional_details.value, "04b07795-8ddb-461a-bbee-02f9e1bf7b46") // Azure CLI
          OR MV_CONTAINS(azure.auditlogs.properties.additional_details.value, "1950a258-227b-4e31-a9cf-717495945fc2") // Azure PowerShell
          OR MV_CONTAINS(azure.auditlogs.properties.additional_details.value, "c44b4083-3bb0-49c1-b47d-974e53cbdf3c") // Azure Portal
          OR MV_CONTAINS(azure.auditlogs.properties.additional_details.value, "aebc6443-996d-45c2-90f0-388ff96faa56"), "microsoft", // VS Code
        MV_CONTAINS(azure.auditlogs.properties.additional_details.value, azure.tenant_id), "tenant",
        "external"),
    // "Consent" = service principal was created by this consent, i.e. first time the app appears in the tenant
    Esql.sp_provisioned_by_consent = MV_CONTAINS(azure.auditlogs.properties.additional_details.value, "Consent")

// High-risk scope families: in-the-wild consent phishing (MS IR playbook), 365-Stealer/GraphRunner defaults, Midnight Blizzard EWS.
// Positive enumeration excludes low-risk variants (Mail.ReadBasic, Chat.ReadBasic, Files.*.Selected) without lookarounds.
| EVAL
    Esql.high_risk_scopes = TRIM(CONCAT(
        CASE(Esql.scopes RLIKE """.* mail\.(read|readwrite|send)(\.shared)? .*""", "mail ", ""),
        CASE(Esql.scopes RLIKE """.* mailbox(settings|item|folder)\.[a-z]+ .*""", "mailbox_settings ", ""),
        CASE(Esql.scopes RLIKE """.* contacts\.[a-z.]+ .*""", "contacts ", ""),
        CASE(Esql.scopes RLIKE """.* files\.(read|readwrite)(\.all|\.appfolder)? .*""", "files ", ""),
        CASE(Esql.scopes RLIKE """.* sites\.[a-z]+\.all .*""", "sites ", ""),
        CASE(Esql.scopes RLIKE """.* notes\.[a-z.]+ .*""", "notes ", ""),
        CASE(Esql.scopes RLIKE """.* (chat\.(read|readwrite|create)(\.all)?|chatmessage\.[a-z]+) .*""", "teams_chat ", ""),
        CASE(Esql.scopes RLIKE """.* (allsites\.[a-z]+|myfiles\.[a-z]+) .*""", "sharepoint_spo ", ""),
        CASE(Esql.scopes RLIKE """.* (ews\.accessasuser\.all|full_access_as_user) .*""", "exchange_ews ", ""))),
    // Low-risk scope families: recon / lower impact. ARM user_impersonation and Directory.AccessAsUser.All only count for
    // non-Microsoft apps (Azure CLI / PowerShell / Portal / VS Code consent noise).
    Esql.low_risk_scopes = TRIM(CONCAT(
        CASE(Esql.scopes RLIKE """.* people\.read(\.all)? .*""", "people ", ""),
        CASE(Esql.scopes RLIKE """.* calendars\.[a-z.]+ .*""", "calendars ", ""),
        CASE(Esql.scopes RLIKE """.* onlinemeetings\.[a-z]+ .*""", "online_meetings ", ""),
        CASE(Esql.scopes RLIKE """.* tasks\.[a-z.]+ .*""", "tasks ", ""),
        CASE(Esql.scopes RLIKE """.* user\.(read\.all|readbasic\.all) .*""", "user_directory ", ""),
        CASE(Esql.app_owner_type != "microsoft" AND Esql.scopes RLIKE """.* (user_impersonation|directory\.accessasuser\.all) .*""", "impersonation ", "")))
| EVAL
    Esql.high_risk_scope_count = CASE(Esql.high_risk_scopes == "", 0, MV_COUNT(SPLIT(Esql.high_risk_scopes, " "))),
    Esql.low_risk_scope_count = CASE(Esql.low_risk_scopes == "", 0, MV_COUNT(SPLIT(Esql.low_risk_scopes, " ")))

// Graph families require offline_access in the same event (durable access). SharePoint/EWS resource scopes are logged in a
// separate event without offline_access, so they alert on their own. External apps stacking 2+ low-risk scopes also alert.
| WHERE (Esql.scopes LIKE "* offline_access *" AND Esql.high_risk_scope_count >= 1)
    OR Esql.high_risk_scopes LIKE "*sharepoint_spo*"
    OR Esql.high_risk_scopes LIKE "*exchange_ews*"
    OR (Esql.scopes LIKE "* offline_access *" AND Esql.low_risk_scope_count >= 2 AND Esql.app_owner_type == "external")
| EVAL Esql.target_service_principal_id = `azure.auditlogs.properties.target_resources.0.id`
| DROP Esql.scopes
| KEEP
    _id,
    _version,
    _index,
    @timestamp,
    event.action,
    event.outcome,
    azure.tenant_id,
    azure.auditlogs.operation_name,
    azure.auditlogs.properties.correlation_id,
    azure.auditlogs.properties.initiated_by.user.userPrincipalName,
    azure.auditlogs.properties.initiated_by.user.id,
    azure.auditlogs.properties.initiated_by.user.ipAddress,
    `azure.auditlogs.properties.target_resources.0.display_name`,
    `azure.auditlogs.properties.target_resources.0.id`,
    `azure.auditlogs.properties.target_resources.0.modified_properties.4.new_value`,
    azure.auditlogs.properties.additional_details.key,
    azure.auditlogs.properties.additional_details.value,
    Esql.*

Known false positives

  • Onboarding of a sanctioned mail client, add-in, backup, archiving, CRM, calendar or SharePoint application that legitimately requires delegated access with a refresh token. Validate publisher verification, application ownership (Esql.app_owner_type) and whether the scopes align with an approved business use case, then add an exception for the reviewed application. Microsoft first-party developer tooling such as Graph Explorer or Microsoft Graph Command Line Tools may legitimately request mailbox or file scopes when used by IT staff. Restricting end-user consent to verified publishers and low-risk permissions reduces this residual set to unsanctioned applications only.

Analyst notes

Investigating Entra ID End-User Consent to Application with High-Risk Delegated Scopes

Adversaries trick a user into granting OAuth consent to an application that requests delegated scopes giving access to mail, files, SharePoint, OneNote, contacts or Teams chats, typically together with offline_access. Because offline_access yields a long-lived refresh token, the application keeps access silently, without re-authentication or MFA. This is a common precursor to business email compromise, mailbox and file exfiltration, and internal phishing.

This rule inspects Entra ID audit logs for successful Consent to application events where ConsentContext.IsAdminConsent is false and the granted permissions (ConsentAction.Permissions) contain a high-risk delegated scope family. Entra logs one consent event per resource API, so Microsoft Graph scopes and offline_access share an event, while SharePoint Online (AllSites.*, MyFiles.*) and Exchange Web Services (EWS.AccessAsUser.All, full_access_as_user) scopes arrive in a separate event and are alerted on independently.

The alert includes the following derived fields:

  • Esql.high_risk_scopes and Esql.high_risk_scope_count: matched high-risk scope families (mail, mailbox_settings, contacts, files, sites, notes, teams_chat, sharepoint_spo, exchange_ews).
  • Esql.low_risk_scopes and Esql.low_risk_scope_count: matched reconnaissance scope families (people, calendars, online_meetings, tasks, user_directory, impersonation). impersonation (user_impersonation, Directory.AccessAsUser.All) is ignored for Microsoft first-party applications such as Azure CLI and Azure PowerShell.
  • Esql.app_owner_type: microsoft (Microsoft-owned or well-known first-party public client), tenant (application registered in this tenant) or external (multi-tenant application owned by another tenant, or owner not recorded on this event).
  • Esql.sp_provisioned_by_consent: true when the service principal was created by this consent, meaning the application had never been seen in the tenant before.

Possible investigation steps

  • Review azure.auditlogs.properties.target_resources.0.display_name and azure.auditlogs.properties.target_resources.0.id (the service principal) to determine which application was granted access. Pivot on the AppId in azure.auditlogs.properties.additional_details.value to the Enterprise Applications blade in the Entra portal and check publisher verification and the home tenant.
  • Treat Esql.app_owner_type: external together with Esql.sp_provisioned_by_consent: true as the highest priority combination; this is the classic pattern of a newly introduced third-party application obtaining mailbox or file access through a single user.
  • Review azure.auditlogs.properties.initiated_by.user.userPrincipalName and azure.auditlogs.properties.initiated_by.user.ipAddress to identify the consenting user and the source of the consent. Investigate their recent activity for signs of phishing or account compromise.
  • Inspect azure.auditlogs.properties.target_resources.0.modified_properties.4.new_value to confirm the exact delegated scopes granted and whether write or send capability (Mail.ReadWrite, Mail.Send, Files.ReadWrite.All, AllSites.FullControl) was included.
  • Pivot on azure.auditlogs.properties.correlation_id to find sibling consent events for other resource APIs granted in the same flow, and to related sign-in and token-usage activity for the same application.
  • Search for follow-on activity by the application such as inbox rule creation, mail forwarding, bulk mail or file downloads, and Teams chat reads.

False positive analysis

  • Sanctioned mail clients, add-ins, backup, archiving, CRM, calendar or SharePoint tools may legitimately request delegated access with a refresh token. Validate publisher verification, ownership and scope alignment, then add an exception for the reviewed application.
  • Microsoft first-party developer tooling (Graph Explorer, Microsoft Graph Command Line Tools) classified as Esql.app_owner_type: microsoft can legitimately request mailbox or file scopes when used by IT or development staff. Confirm the consenting user has a reason to use the tool; unexpected users consenting to these clients can also indicate device code phishing.
  • Line-of-business applications registered in the tenant (Esql.app_owner_type: tenant) are expected in development-heavy organizations, but attackers with a compromised account can also register applications in the victim tenant.

Response and remediation

  • Revoke the application's OAuth grant (for example Remove-MgOauth2PermissionGrant / Remove-AzureADOAuth2PermissionGrant) and disable or delete the associated service principal.
  • Revoke the affected user's refresh tokens and require re-authentication.
  • Block the application via Conditional Access or Defender for Cloud Apps.
  • Enable the admin consent workflow and restrict end-user consent to applications from verified publishers.
Raw source Entra ID End-User Consent to Application with High-Risk Delegated Scopes · Elastic TOML
Esc
Published by elastic/detection-rules ↗, licensed under Elastic License 2.0 ↗. Reproduced here unmodified.
[metadata]
creation_date = "2026/10/02"
integration = ["azure"]
maturity = "production"
min_stack_comments = "MV_CONTAINS was added to ES|QL in 9.2.0 and is not available on 8.19; 9.3.0 is the lowest supported 9.x release."
min_stack_version = "9.3.0"
updated_date = "2026/10/02"

[rule]
author = ["Elastic", "Eduard Arbona"]
description = """
Identifies an end-user (non-admin) consent grant in Microsoft Entra ID to an application requesting delegated scopes
commonly abused in illicit consent grant (OAuth phishing) attacks, such as mail, mailbox settings, contacts, files,
SharePoint, OneNote, Teams chat and Exchange Web Services. Graph scopes are evaluated together with offline_access, which
returns a long-lived refresh token and gives an attacker-controlled application durable, silent access to the victim's
data without stealing a password or re-prompting for MFA. The alert classifies the application owner as Microsoft
first-party, in-tenant or external to help prioritize review.
"""
false_positives = [
    """
    Onboarding of a sanctioned mail client, add-in, backup, archiving, CRM, calendar or SharePoint application that
    legitimately requires delegated access with a refresh token. Validate publisher verification, application ownership
    (Esql.app_owner_type) and whether the scopes align with an approved business use case, then add an exception for the
    reviewed application. Microsoft first-party developer tooling such as Graph Explorer or Microsoft Graph Command Line
    Tools may legitimately request mailbox or file scopes when used by IT staff. Restricting end-user consent to verified
    publishers and low-risk permissions reduces this residual set to unsanctioned applications only.
    """,
]
from = "now-9m"
interval = "8m"
language = "esql"
license = "Elastic License v2"
name = "Entra ID End-User Consent to Application with High-Risk Delegated Scopes"
note = """## Triage and analysis

### Investigating Entra ID End-User Consent to Application with High-Risk Delegated Scopes

Adversaries trick a user into granting OAuth consent to an application that requests delegated scopes giving access to mail, files, SharePoint, OneNote, contacts or Teams chats, typically together with `offline_access`. Because `offline_access` yields a long-lived refresh token, the application keeps access silently, without re-authentication or MFA. This is a common precursor to business email compromise, mailbox and file exfiltration, and internal phishing.

This rule inspects Entra ID audit logs for successful `Consent to application` events where `ConsentContext.IsAdminConsent` is false and the granted permissions (`ConsentAction.Permissions`) contain a high-risk delegated scope family. Entra logs one consent event per resource API, so Microsoft Graph scopes and `offline_access` share an event, while SharePoint Online (`AllSites.*`, `MyFiles.*`) and Exchange Web Services (`EWS.AccessAsUser.All`, `full_access_as_user`) scopes arrive in a separate event and are alerted on independently.

The alert includes the following derived fields:

- `Esql.high_risk_scopes` and `Esql.high_risk_scope_count`: matched high-risk scope families (`mail`, `mailbox_settings`, `contacts`, `files`, `sites`, `notes`, `teams_chat`, `sharepoint_spo`, `exchange_ews`).
- `Esql.low_risk_scopes` and `Esql.low_risk_scope_count`: matched reconnaissance scope families (`people`, `calendars`, `online_meetings`, `tasks`, `user_directory`, `impersonation`). `impersonation` (`user_impersonation`, `Directory.AccessAsUser.All`) is ignored for Microsoft first-party applications such as Azure CLI and Azure PowerShell.
- `Esql.app_owner_type`: `microsoft` (Microsoft-owned or well-known first-party public client), `tenant` (application registered in this tenant) or `external` (multi-tenant application owned by another tenant, or owner not recorded on this event).
- `Esql.sp_provisioned_by_consent`: `true` when the service principal was created by this consent, meaning the application had never been seen in the tenant before.

#### Possible investigation steps

- Review `azure.auditlogs.properties.target_resources.0.display_name` and `azure.auditlogs.properties.target_resources.0.id` (the service principal) to determine which application was granted access. Pivot on the AppId in `azure.auditlogs.properties.additional_details.value` to the Enterprise Applications blade in the Entra portal and check publisher verification and the home tenant.
- Treat `Esql.app_owner_type: external` together with `Esql.sp_provisioned_by_consent: true` as the highest priority combination; this is the classic pattern of a newly introduced third-party application obtaining mailbox or file access through a single user.
- Review `azure.auditlogs.properties.initiated_by.user.userPrincipalName` and `azure.auditlogs.properties.initiated_by.user.ipAddress` to identify the consenting user and the source of the consent. Investigate their recent activity for signs of phishing or account compromise.
- Inspect `azure.auditlogs.properties.target_resources.0.modified_properties.4.new_value` to confirm the exact delegated scopes granted and whether write or send capability (`Mail.ReadWrite`, `Mail.Send`, `Files.ReadWrite.All`, `AllSites.FullControl`) was included.
- Pivot on `azure.auditlogs.properties.correlation_id` to find sibling consent events for other resource APIs granted in the same flow, and to related sign-in and token-usage activity for the same application.
- Search for follow-on activity by the application such as inbox rule creation, mail forwarding, bulk mail or file downloads, and Teams chat reads.

### False positive analysis

- Sanctioned mail clients, add-ins, backup, archiving, CRM, calendar or SharePoint tools may legitimately request delegated access with a refresh token. Validate publisher verification, ownership and scope alignment, then add an exception for the reviewed application.
- Microsoft first-party developer tooling (Graph Explorer, Microsoft Graph Command Line Tools) classified as `Esql.app_owner_type: microsoft` can legitimately request mailbox or file scopes when used by IT or development staff. Confirm the consenting user has a reason to use the tool; unexpected users consenting to these clients can also indicate device code phishing.
- Line-of-business applications registered in the tenant (`Esql.app_owner_type: tenant`) are expected in development-heavy organizations, but attackers with a compromised account can also register applications in the victim tenant.

### Response and remediation

- Revoke the application's OAuth grant (for example `Remove-MgOauth2PermissionGrant` / `Remove-AzureADOAuth2PermissionGrant`) and disable or delete the associated service principal.
- Revoke the affected user's refresh tokens and require re-authentication.
- Block the application via Conditional Access or Defender for Cloud Apps.
- Enable the admin consent workflow and restrict end-user consent to applications from verified publishers.
"""
references = [
    "https://learn.microsoft.com/en-us/entra/architecture/security-operations-applications#end-user-consent",
    "https://learn.microsoft.com/en-us/security/operations/incident-response-playbook-app-consent",
    "https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/manage-app-consent-policies",
    "https://learn.microsoft.com/en-us/defender-cloud-apps/investigate-risky-oauth",
    "https://www.microsoft.com/en-us/security/blog/2024/01/25/midnight-blizzard-guidance-for-responders-on-nation-state-attack/",
    "https://github.com/AlteredSecurity/365-Stealer"
]
risk_score = 47
rule_id = "e907ca08-45f2-4f8b-9854-c66be0e985dd"
severity = "medium"
tags = [
    "Domain: Cloud",
    "Domain: Identity",
    "Data Source: Azure",
    "Data Source: Microsoft Entra ID",
    "Data Source: Microsoft Entra ID Audit Logs",
    "Platform: Entra ID",
    "Profile: Beta",
    "Use Case: Identity and Access Audit",
    "Threat: OAuth App Consent",
    "Resources: Investigation Guide",
    "Tactic: Initial Access",
    "Tactic: Credential Access",
    "Rule Type: ES|QL",
]
type = "esql"

query = '''
FROM logs-azure.auditlogs-* metadata _id, _version, _index
| WHERE (azure.auditlogs.operation_name == "Consent to application"
    OR event.action == "Consent to application")
  AND event.outcome == "success"
  // End-user (non-admin) consent only
  AND TO_LOWER(`azure.auditlogs.properties.target_resources.0.modified_properties.0.new_value`) LIKE "*false*"

// Normalize ConsentAction.Permissions into a lower-cased, space-padded token string so " token " is an exact scope match.
// Raw: "[] => [[Id: .., ResourceId: .., ConsentType: Principal, Scope:  Mail.Read offline_access .., ..]]"
| EVAL Esql.scopes = CONCAT(" ", REPLACE(TO_LOWER(`azure.auditlogs.properties.target_resources.0.modified_properties.4.new_value`), """[\[\],;"]""", " "), " ")

// additional_details.key/value are parallel arrays but ES|QL returns multivalues sorted, so pair via MV_CONTAINS, not position.
// Owner = AppOwnerOrganizationId (Microsoft tenants) OR well-known first-party public-client AppIds (not always stamped with owner org).
| EVAL
    Esql.app_owner_type = CASE(
        MV_CONTAINS(azure.auditlogs.properties.additional_details.value, "f8cdef31-a31e-4b4a-93e4-5f571e91255a")   // Microsoft Services
          OR MV_CONTAINS(azure.auditlogs.properties.additional_details.value, "72f988bf-86f1-41af-91ab-2d7cd011db47") // Microsoft corp
          OR MV_CONTAINS(azure.auditlogs.properties.additional_details.value, "14d82eec-204b-4c2f-b7e8-296a70dab67e") // Microsoft Graph Command Line Tools
          OR MV_CONTAINS(azure.auditlogs.properties.additional_details.value, "de8bc8b5-d9f9-48b1-a8ad-b748da725064") // Graph Explorer
          OR MV_CONTAINS(azure.auditlogs.properties.additional_details.value, "04b07795-8ddb-461a-bbee-02f9e1bf7b46") // Azure CLI
          OR MV_CONTAINS(azure.auditlogs.properties.additional_details.value, "1950a258-227b-4e31-a9cf-717495945fc2") // Azure PowerShell
          OR MV_CONTAINS(azure.auditlogs.properties.additional_details.value, "c44b4083-3bb0-49c1-b47d-974e53cbdf3c") // Azure Portal
          OR MV_CONTAINS(azure.auditlogs.properties.additional_details.value, "aebc6443-996d-45c2-90f0-388ff96faa56"), "microsoft", // VS Code
        MV_CONTAINS(azure.auditlogs.properties.additional_details.value, azure.tenant_id), "tenant",
        "external"),
    // "Consent" = service principal was created by this consent, i.e. first time the app appears in the tenant
    Esql.sp_provisioned_by_consent = MV_CONTAINS(azure.auditlogs.properties.additional_details.value, "Consent")

// High-risk scope families: in-the-wild consent phishing (MS IR playbook), 365-Stealer/GraphRunner defaults, Midnight Blizzard EWS.
// Positive enumeration excludes low-risk variants (Mail.ReadBasic, Chat.ReadBasic, Files.*.Selected) without lookarounds.
| EVAL
    Esql.high_risk_scopes = TRIM(CONCAT(
        CASE(Esql.scopes RLIKE """.* mail\.(read|readwrite|send)(\.shared)? .*""", "mail ", ""),
        CASE(Esql.scopes RLIKE """.* mailbox(settings|item|folder)\.[a-z]+ .*""", "mailbox_settings ", ""),
        CASE(Esql.scopes RLIKE """.* contacts\.[a-z.]+ .*""", "contacts ", ""),
        CASE(Esql.scopes RLIKE """.* files\.(read|readwrite)(\.all|\.appfolder)? .*""", "files ", ""),
        CASE(Esql.scopes RLIKE """.* sites\.[a-z]+\.all .*""", "sites ", ""),
        CASE(Esql.scopes RLIKE """.* notes\.[a-z.]+ .*""", "notes ", ""),
        CASE(Esql.scopes RLIKE """.* (chat\.(read|readwrite|create)(\.all)?|chatmessage\.[a-z]+) .*""", "teams_chat ", ""),
        CASE(Esql.scopes RLIKE """.* (allsites\.[a-z]+|myfiles\.[a-z]+) .*""", "sharepoint_spo ", ""),
        CASE(Esql.scopes RLIKE """.* (ews\.accessasuser\.all|full_access_as_user) .*""", "exchange_ews ", ""))),
    // Low-risk scope families: recon / lower impact. ARM user_impersonation and Directory.AccessAsUser.All only count for
    // non-Microsoft apps (Azure CLI / PowerShell / Portal / VS Code consent noise).
    Esql.low_risk_scopes = TRIM(CONCAT(
        CASE(Esql.scopes RLIKE """.* people\.read(\.all)? .*""", "people ", ""),
        CASE(Esql.scopes RLIKE """.* calendars\.[a-z.]+ .*""", "calendars ", ""),
        CASE(Esql.scopes RLIKE """.* onlinemeetings\.[a-z]+ .*""", "online_meetings ", ""),
        CASE(Esql.scopes RLIKE """.* tasks\.[a-z.]+ .*""", "tasks ", ""),
        CASE(Esql.scopes RLIKE """.* user\.(read\.all|readbasic\.all) .*""", "user_directory ", ""),
        CASE(Esql.app_owner_type != "microsoft" AND Esql.scopes RLIKE """.* (user_impersonation|directory\.accessasuser\.all) .*""", "impersonation ", "")))
| EVAL
    Esql.high_risk_scope_count = CASE(Esql.high_risk_scopes == "", 0, MV_COUNT(SPLIT(Esql.high_risk_scopes, " "))),
    Esql.low_risk_scope_count = CASE(Esql.low_risk_scopes == "", 0, MV_COUNT(SPLIT(Esql.low_risk_scopes, " ")))

// Graph families require offline_access in the same event (durable access). SharePoint/EWS resource scopes are logged in a
// separate event without offline_access, so they alert on their own. External apps stacking 2+ low-risk scopes also alert.
| WHERE (Esql.scopes LIKE "* offline_access *" AND Esql.high_risk_scope_count >= 1)
    OR Esql.high_risk_scopes LIKE "*sharepoint_spo*"
    OR Esql.high_risk_scopes LIKE "*exchange_ews*"
    OR (Esql.scopes LIKE "* offline_access *" AND Esql.low_risk_scope_count >= 2 AND Esql.app_owner_type == "external")
| EVAL Esql.target_service_principal_id = `azure.auditlogs.properties.target_resources.0.id`
| DROP Esql.scopes
| KEEP
    _id,
    _version,
    _index,
    @timestamp,
    event.action,
    event.outcome,
    azure.tenant_id,
    azure.auditlogs.operation_name,
    azure.auditlogs.properties.correlation_id,
    azure.auditlogs.properties.initiated_by.user.userPrincipalName,
    azure.auditlogs.properties.initiated_by.user.id,
    azure.auditlogs.properties.initiated_by.user.ipAddress,
    `azure.auditlogs.properties.target_resources.0.display_name`,
    `azure.auditlogs.properties.target_resources.0.id`,
    `azure.auditlogs.properties.target_resources.0.modified_properties.4.new_value`,
    azure.auditlogs.properties.additional_details.key,
    azure.auditlogs.properties.additional_details.value,
    Esql.*
'''

[rule.alert_suppression]
group_by = ["azure.auditlogs.properties.initiated_by.user.userPrincipalName", "Esql.target_service_principal_id"]
missing_fields_strategy = "doNotSuppress"

[rule.alert_suppression.duration]
unit = "h"
value = 6



[[rule.threat]]
framework = "MITRE ATT&CK"

[[rule.threat.technique]]
id = "T1566"
name = "Phishing"
reference = "https://attack.mitre.org/techniques/T1566/"

[[rule.threat.technique.subtechnique]]
id = "T1566.002"
name = "Spearphishing Link"
reference = "https://attack.mitre.org/techniques/T1566/002/"

[rule.threat.tactic]
id = "TA0001"
name = "Initial Access"
reference = "https://attack.mitre.org/tactics/TA0001/"

[[rule.threat]]
framework = "MITRE ATT&CK"

[[rule.threat.technique]]
id = "T1528"
name = "Steal Application Access Token"
reference = "https://attack.mitre.org/techniques/T1528/"

[rule.threat.tactic]
id = "TA0006"
name = "Credential Access"
reference = "https://attack.mitre.org/tactics/TA0006/"

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.