Skip to content

OAuth Illicit Consent Grant & Application Phishing

In enterprise cloud security, OAuth Illicit Consent Grant attacks (often termed Application Phishing) represent one of the most stealthy and resilient persistence mechanisms available to threat actors. Unlike credential theft or session cookie hijacking, an illicit consent grant does not require stealing the user’s password, does not trigger interactive MFA challenges during operational use, survives user password resets, and remains unaffected by device compliance policies.

By abusing the OAuth 2.0 authorization framework and Microsoft Entra ID’s multi-tenant application model, an adversary tricks a user into granting high-privilege delegated API permissions (Mail.ReadWrite, Files.ReadWrite.All, offline_access) to a malicious cloud application. Once approved, the adversary interacts directly with Microsoft Graph APIs using programmatic bearer tokens minted legitimately by Microsoft’s Security Token Service.

This guide provides an exhaustive forensic examination of illicit consent grants, details the role of high-risk scopes, analyzes audit telemetry across Entra ID and Purview, and outlines production hunting queries and emergency revocation playbooks.


Section titled “1. OAuth 2.0 Consent Architecture in Microsoft Entra ID”

Microsoft Entra ID implements the OAuth 2.0 authorization code grant flow (RFC 6749) to allow third-party applications to access cloud resources on behalf of users:

sequenceDiagram
autonumber
participant Attacker as Adversary Infrastructure
participant Victim as Target User Browser
participant Entra as Microsoft Entra ID (STS)
participant Graph as Microsoft Graph API / M365
Attacker->>Victim: Phishing Lure with OAuth Authorization URL
Note over Victim: Victim clicks link: https://login.microsoftonline.com/common/oauth2/v2.0/authorize...
Victim->>Entra: GET /authorize?client_id=ATTACKER_APP&scope=Mail.ReadWrite+offline_access
Entra-->>Victim: Renders Authentic Microsoft Consent Dialog Screen
Note over Victim: Dialog: "App wants permission to Read/Write your mail"<br/>Victim clicks "Accept"
Entra->>Entra: 1. Instantiates Service Principal in Victim Tenant<br/>2. Writes OAuth2PermissionGrant Record<br/>3. Emits Entra Audit Log: "Consent to application"
Entra-->>Victim: HTTP 302 Redirect to Attacker Redirect URI with ?code=AUTH_CODE
Victim->>Attacker: Forwards Authorization Code
Attacker->>Entra: POST /token (Exchanges code + client_secret)
Entra-->>Attacker: Returns Access Token (JWT) + Refresh Token
Attacker->>Graph: Programmatic API Queries (GET /v1.0/me/messages)
Note over Attacker: Direct exfiltration without victim involvement or MFA!
  1. Attacker Tenant (Home): The adversary registers an application in their own Microsoft 365 tenant, configuring it as “Accounts in any organizational directory (Any Microsoft Entra directory - Multitenant)”.
  2. Victim Tenant (Resource): When the victim clicks “Accept” on the consent prompt, Entra ID creates a local Service Principal object representing the external application within the victim’s tenant directory.
  3. Delegated Grant Object: Entra ID creates an OAuth2PermissionGrant entity binding the victim’s ObjectId to the application’s ObjectId with the approved permission string.

2. High-Risk OAuth Scopes & The Power of offline_access

Section titled “2. High-Risk OAuth Scopes & The Power of offline_access”

When inspecting consent events, investigators must scrutinize the requested scopes. Adversaries specifically target permissions that grant autonomous, unmonitored data access:

OAuth ScopePermission CategoryThreat Actor Exploitation Capability
offline_accessOpenID / CoreIndefinite Autonomous Persistence. Instructs Entra ID to issue an OAuth 2.0 Refresh Token. The attacker can refresh access tokens continuously for months without requiring the victim to be online or re-authenticate.
Mail.Read / Mail.ReadWriteExchange / GraphGrants programmatic access to read, search, modify, and delete all emails, folders, and attachments in the user’s mailbox.
Mail.SendExchange / GraphDispatches emails directly from the victim’s address via Microsoft Graph API, bypassing Outlook client telemetry and local mail logs.
Files.Read.All / Files.ReadWrite.AllSharePoint / OneDriveGrants programmatic access to all files and document libraries accessible to the victim across SharePoint and OneDrive.
Contacts.Read / People.ReadDirectory / GraphScrapes the internal organizational hierarchy, identifying executive targets for secondary BEC fraud.
Directory.Read.AllDirectory (Admin)Full directory reconnaissance; enumerates all users, groups, devices, and roles.

3. Forensic Artifacts Across Entra Audit & Purview UAL

Section titled “3. Forensic Artifacts Across Entra Audit & Purview UAL”

An illicit consent grant generates conspicuous audit events in both Microsoft Entra ID and the Purview Unified Audit Log.

graph TD
CONSENT[User Accepts Illicit Consent Prompt] --> AUDIT_ENTRA[Entra ID AuditLogs Table]
CONSENT --> AUDIT_UAL[Purview Unified Audit Log (UAL)]
AUDIT_ENTRA --> EVT1[Activity: Consent to application<br/>InitiatedBy: victim@target.com<br/>Target: ServicePrincipal Name & AppId]
AUDIT_ENTRA --> EVT2[Activity: Add service principal<br/>TargetResources: AppId, ServicePrincipalId]
AUDIT_ENTRA --> EVT3[Activity: Add OAuth2PermissionGrant<br/>Scope: Mail.ReadWrite offline_access]
AUDIT_UAL --> UAL_REC[RecordType: AzureActiveDirectory (15)<br/>Operation: Consent to application]
ATT_USE[Attacker Uses Token via Graph API] --> UAL_GRAPH[Workload: Exchange / SharePoint<br/>AuditData.AppId = Malicious AppId<br/>AuditData.UserId = victim@target.com]
Section titled “3.1 Entra ID Audit Log Payload (Consent to application)”

When parsed from the Microsoft Graph Audit API or Sentinel AuditLogs, the payload contains:

{
"activityDateTime": "2026-03-22T10:15:30Z",
"activityDisplayName": "Consent to application",
"category": "ApplicationManagement",
"result": "success",
"initiatedBy": {
"user": {
"id": "11111111-2222-3333-4444-555555555555",
"userPrincipalName": "victim@target.com",
"ipAddress": "198.51.100.45"
}
},
"targetResources": [
{
"id": "77777777-8888-9999-aaaa-bbbbbbbbbbbb",
"displayName": "eSignature Cloud Validator",
"type": "ServicePrincipal",
"modifiedProperties": [
{
"displayName": "ConsentAction.Permissions",
"oldValue": "[]",
"newValue": "[\"Mail.ReadWrite\",\"Files.ReadWrite.All\",\"offline_access\"]"
},
{
"displayName": "TargetId.ServicePrincipalNames",
"oldValue": "[]",
"newValue": "[\"a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d\"]"
}
]
}
]
}

Key fields for analysis:

  • initiatedBy.user.userPrincipalName: The identity that granted the permissions.
  • targetResources[0].displayName: The deceptive display name chosen by the attacker (e.g., "eSignature Cloud Validator", "Zoom Meeting Integration").
  • modifiedProperties[ConsentAction.Permissions]: The exact array of API scopes surrendered to the threat actor.
  • modifiedProperties[TargetId.ServicePrincipalNames]: The multi-tenant AppId (GUID).

Section titled “4.1 Detecting Illicit Consent Grants with High-Risk Scopes”

Identify any consent event where an end-user granted sensitive mailbox, file, or offline access scopes:

let SensitiveScopes = dynamic(["mail.read", "mail.readwrite", "mail.send", "files.read.all", "files.readwrite.all", "offline_access"]);
AuditLogs
| where TimeGenerated >= ago(30d)
| where OperationName == "Consent to application"
| extend InitiatedByUser = tostring(InitiatedBy.user.userPrincipalName),
InitiatedByIP = tostring(InitiatedBy.user.ipAddress)
| extend TargetApp = tostring(TargetResources[0].displayName),
AppId = tostring(TargetResources[0].id)
| extend ModifiedProps = TargetResources[0].modifiedProperties
| mv-expand ModifiedProps
| where ModifiedProps.displayName == "ConsentAction.Permissions"
| extend GrantedPermissions = tostring(ModifiedProps.newValue)
| where GrantedPermissions has_any (SensitiveScopes)
| project TimeGenerated, InitiatedByUser, InitiatedByIP, TargetApp, AppId, GrantedPermissions
| sort by TimeGenerated desc

4.2 Correlating Consented Apps with Graph API Activity

Section titled “4.2 Correlating Consented Apps with Graph API Activity”

Identify instances where a recently consented application executes automated data queries across mail or SharePoint:

let ConsentedApps = AuditLogs
| where TimeGenerated >= ago(14d)
| where OperationName in ("Consent to application", "Add service principal")
| extend AppId = tostring(TargetResources[0].id)
| distinct AppId;
CloudAppEvents
| where TimeGenerated >= ago(14d)
| where ActionType in ("MailItemsAccessed", "FileDownloaded", "SearchQueryInitiatedExchange")
| extend Raw = parse_json(RawEventData)
| extend UsedAppId = tostring(Raw.AppId)
| where UsedAppId in (ConsentedApps)
| project TimeGenerated, AccountDisplayName, ActionType, UsedAppId, IPAddress, Raw
| sort by TimeGenerated desc

5. Emergency Incident Remediation & Hardening Playbook

Section titled “5. Emergency Incident Remediation & Hardening Playbook”

When an illicit consent grant is identified, the response team must completely eliminate the application’s access and prevent similar attacks across the tenant.

graph TD
DISCOVERY[Illicit Consent Identified] --> STEP1[1. Locate & Revoke OAuth2PermissionGrant<br/>Remove-MgOauth2PermissionGrant]
STEP1 --> STEP2[2. Delete Local Service Principal<br/>Remove-MgServicePrincipal]
STEP2 --> STEP3[3. Invalidate User Refresh Tokens<br/>Revoke-MgUserSignSession]
STEP3 --> STEP4[4. Restrict Tenant Consent Settings<br/>Disable user consent via Enterprise Apps policy]
STEP4 --> STEP5[5. Enable Admin Consent Workflow<br/>Require admin approval for all third-party apps]
Terminal window
# Prerequisites: Microsoft.Graph.Applications, Microsoft.Graph.Identity.SignIns
# Connect-MgGraph -Scopes "Application.ReadWrite.All","DelegatedPermissionGrant.ReadWrite.All","User.ReadWrite.All"
$targetAppId = "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d"
$victimUPN = "victim@target.com"
Write-Host "[*] INITIATING REMEDIATION FOR ILLICIT OAUTH APP: $targetAppId" -ForegroundColor Red
# 1. Locate the Service Principal in the tenant
$sp = Get-MgServicePrincipal -Filter "appId eq '$targetAppId'"
if (-not $sp) {
Write-Error "Service Principal not found for AppId: $targetAppId"
return
}
Write-Host "[+] Found Service Principal: $($sp.DisplayName) (Id: $($sp.Id))" -ForegroundColor Yellow
# 2. Identify and delete all delegated OAuth permission grants for this app
$grants = Get-MgOauth2PermissionGrant -Filter "clientId eq '$($sp.Id)'"
foreach ($grant in $grants) {
Write-Host "[!] Revoking OAuth2 Permission Grant: $($grant.Id) (Scopes: $($grant.Scope))" -ForegroundColor Yellow
Remove-MgOauth2PermissionGrant -OAuth2PermissionGrantId $grant.Id
}
# 3. Delete the Service Principal from the tenant
Remove-MgServicePrincipal -ServicePrincipalId $sp.Id
Write-Host "[+] Service Principal successfully deleted from tenant." -ForegroundColor Green
# 4. Revoke user session tokens as an added security measure
Revoke-MgUserSignSession -UserId $victimUPN
Write-Host "[+] Revoked active sessions for victim: $victimUPN" -ForegroundColor Green
Section titled “5.2 Tenant-Wide Hardening: Restricting User Consent”

To prevent end-users from granting permissions to unverified multi-tenant apps, configure the tenant consent policy:

Terminal window
# Update Authorization Policy to disable user self-consent
Update-MgPolicyAuthorizationPolicy -DefaultUserRolePermissions @{
AllowedToCreateApps = $false
PermissionGrantPoliciesAssigned = @("ManagePermissionGrantsForSelf.microsoft-user-default-low")
}
Write-Host "[+] Tenant configured: End users restricted from granting unverified high-privilege app permissions." -ForegroundColor Green

6. Cross-Reference & Investigation Navigation

Section titled “6. Cross-Reference & Investigation Navigation”