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_scopesandEsql.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_scopesandEsql.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) orexternal(multi-tenant application owned by another tenant, or owner not recorded on this event).Esql.sp_provisioned_by_consent:truewhen 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_nameandazure.auditlogs.properties.target_resources.0.id(the service principal) to determine which application was granted access. Pivot on the AppId inazure.auditlogs.properties.additional_details.valueto the Enterprise Applications blade in the Entra portal and check publisher verification and the home tenant. - Treat
Esql.app_owner_type: externaltogether withEsql.sp_provisioned_by_consent: trueas 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.userPrincipalNameandazure.auditlogs.properties.initiated_by.user.ipAddressto 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_valueto 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_idto 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: microsoftcan 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.