In the earlier parts of this series, we explored the fundamentals of the Microsoft Graph Activity Log and then demonstrated how to use it to Investigate Mailbox Breaches and Document Exfiltration. These incidents typically involve direct compromise of user credentials or malicious use of file access. But not all threats require stolen passwords. In this article, we turn our focus to a more subtle and increasingly common attack vector named OAuth application abuse.

Instead of breaking in, attackers now persuade users to invite them in by consenting to malicious applications. This consent grants the app persistent access to Microsoft 365 data such as emails, files, calendars, and directories, often bypassing traditional defenses like multi-factor authentication. These “consent phishing” attacks rely on legitimate platform features, making them harder to detect. Responding effectively requires not only the right visibility but also specialized tools designed to detect and remediate illicit consent grants, which is a primary focus of Microsoft Defender for Office 365.

Suspected Theft of Confidential Information.

Imagine a user receives a convincing email prompt to install a new “productivity-enhancing” application. The app looks legitimate, and the user grants the requested permissions without a second thought. Unbeknownst to them, they have just authorized a malicious application, giving an attacker persistent, delegated access to their mailbox, files, and more. This “consent phishing” attack bypasses traditional defenses like MFA because the user willingly grants access.

This scenario highlights the stealthy nature of OAuth app abuse. Attackers are no longer just after passwords; they’re after permissions.

The Microsoft Graph Activity Log provides essential visibility into this threat, offering a detailed record of every API call made by every application in your tenant. This article serves as a practical playbook for using the Graph Activity Log and Kusto Query Language (KQL) to detect, investigate, and respond to OAuth app abuse in Microsoft 365.

Anatomy of OAuth App Abuse: What to Look For

After a user grants consent, an attacker can use the application’s permissions to access data and services non-interactively, often from background processes without the user’s continued involvement. Their goal is to blend in with legitimate application traffic while exfiltrating data, sending malicious emails, or establishing persistence.

To effectively hunt for this activity, investigators should look for the following Indicators of Compromise (IOCs):

Suspicious Consent to New Applications
Be alert for applications that are unfamiliar, recently consented to by multiple users, or have vague publisher details. These characteristics often suggest consent phishing attempts, where users are tricked into granting access to malicious applications that appear legitimate.

Once consent is granted, these applications may perform seemingly routine user-level actions such as sharing files externally, creating anonymous share links to sensitive documents, or embedding malicious URLs into collaboration content. While these actions may appear harmless at first glance, when performed at scale or targeting unusual recipients, they can indicate OAuth app abuse.

High-Privilege Permission Grants

Watch for cases where applications are granted powerful permissions, such as Mail.ReadWrite.All, Files.ReadWrite.All, or Directory.ReadWrite.All without going through a formal approval process. These elevated permissions can allow attackers to access and manipulate large volumes of sensitive data. One can manage and review their organization’s applications by following the guidelines for enterprise app management.

Illicit Credential Addition
Attackers may try to maintain long-term access by adding secrets or certificates to the service principal of an application. This allows them to reauthenticate and access resources even after the original user access has been revoked or remediated.

Anomalous API Activity

Monitor for application behavior that does not match its intended function. For example, if a document management app is observed accessing user mailboxes or sending emails, this activity is highly suspicious and should be investigated immediately. Beyond mailbox activity, attackers often exploit permissions to share files externally with embedded links, grant additional app access through Entra ID consent prompts, or create unusual collaboration invites to spread malicious access further. These activities stand out because they blend normal collaboration features with subtle persistence or exfiltration tactics. Microsoft provides an official guide on how to investigate the activity of risky OAuth applications to confirm malicious intent.

Investigative Playbook: Core KQL Queries

With these IOCs in mind, we can use KQL queries in Log Analytics to hunt for the digital footprints of OAuth app abuse.

  1. Finding Signals of Recent Application Consent

To find new consents, we must look for the API calls that are part of the OAuth2 authorization flow. A POST request to an endpoint containing oAuth2PermissionGrant is a strong behavioral indicator that a new permission has just been granted to an application, which happens at the moment of user consent.

This KQL query hunts for these specific API calls and then aggregates the results. It is designed to surface applications that are being consented to by multiple users, which helps filter out the noise of single users authorizing legitimate, one-off apps. This matters because a spike in consents for a previously unseen AppId across multiple users is a significant red flag that could indicate a widespread consent phishing campaign.

MicrosoftGraphActivityLogs  
| where TimeGenerated > ago(7d)
| where RequestMethod == "POST" and RequestUri has "oAuth2PermissionGrant"  
| project TimeGenerated, AppId, UserId, IPAddress  
| summarize 
    RequestCount = count(), 
    Users = dcount(UserId), 
    FirstSeen = min(TimeGenerated), 
    LastSeen = max(TimeGenerated) 
    by AppId  
| where Users > 1
| order by Users desc

Review this output for new or unfamiliar AppIds that are suddenly being granted permissions by multiple users in a short period.

2. Detecting High-Privilege Permission Grants

This query helps uncover when users grant powerful delegated or application permissions to an app through the OAuth consent flow. These actions are logged as POST requests to endpoints containing oAuth2PermissionGrant.

Attackers often request elevated permissions (like Mail.ReadWrite.All or Directory.ReadWrite.All) under the guise of a legitimate app. If granted, these permissions can be abused to access mailboxes, files, and directories—silently and at scale.

The following KQL query finds these grant events, showing who authorized which app, when, and from where. It’s an effective way to detect suspicious privilege elevation and cross-reference with legitimate change requests.

MicrosoftGraphActivityLogs
| where TimeGenerated > ago(30d)
| where RequestUri has "oAuth2PermissionGrant" and RequestMethod == "POST"
| project TimeGenerated, AppId, UserId, IPAddress, RequestUri
| sort by TimeGenerated desc

Any results from this query, especially those initiated by non-administrator accounts (UserId), should be considered highly suspicious and cross-referenced with your organization’s app deployment records.

3. Detecting Illicit Credential Addition to an Application

Advanced attackers often aim to maintain access by adding their own credentials (passwords or keys) to an app’s service principal. Once added, they can reauthenticate even if the original access token or session is revoked.

This query detects those stealthy credential additions. It hunts for PATCH requests to sensitive endpoints like /applications/ or /servicePrincipals/ that also include suspicious operations such as addPassword or addKey.

MicrosoftGraphActivityLogs
| where TimeGenerated > ago(30d)
| where RequestMethod == "PATCH" and (RequestUri has "/applications/" or    RequestUri has "/servicePrincipals/")
| where RequestUri has "addPassword" or RequestUri has "addKey"
| project TimeGenerated, AppId, UserId, IPAddress, RequestUri
| sort by TimeGenerated desc

An alert on this activity should be treated as a critical incident, as it indicates an attacker is attempting to create a persistent backdoor.

4. Profiling API Activity for a Specific Application

After identifying a suspicious application’s AppId using the queries above, this is the crucial next step to understand what the application actually did. This query acts as a powerful lens, filtering the millions of entries in the MicrosoftGraphActivityLogs to show every single API call made by that one specific application over the past week.

This KQL query will return a detailed, chronological log of the application’s activity. Each row provides the exact timestamp, the user account the action was performed on behalf of (UserId), the source IPAddress, the HTTP method used, such as GET for reading, POST for creating, the specific API endpoint targeted (RequestUri), and the ResponseStatusCode to see if the action was successful.

MicrosoftGraphActivityLogs 
| where TimeGenerated > ago(7d) 
| where AppId == "<AppId_Goes_Here>" 
| project TimeGenerated, UserId, IPAddress, RequestMethod, RequestUri, ResponseStatusCode 
| sort by TimeGenerated desc

Automating the Hunt: A PowerShell Playbook

To standardize these checks, you can use a PowerShell script. The script below automates the four queries, prompts the analyst for input, and exports the results to a CSV file for reporting or further analysis.

# Ensure you are connected to your Azure account
Connect-AzAccount

# Define Workspace ID and output file path
$workspaceId = "replace_this_with_your_workspace_id"
$csvFilePath = ".\oauth_abuse_investigation_mgal.csv"

# Prompt for user input
Write-Host "`nSelect the query to run (using only MicrosoftGraphActivityLogs):"
Write-Host "1. Find Signals of Recent Application Consent (last 7 days)"
Write-Host "2. Detect High-Privilege Permission Grants (last 30 days)"
Write-Host "3. Detect Illicit Credential Addition (last 30 days)"
Write-Host "4. Profile API Activity for a Specific App (last 7 days)"
$selection = Read-Host "Enter the query number (1-4)"

# Dynamically build the KQL query based on selection
switch ($selection) {
    '1' {
        $query = @"
        MicrosoftGraphActivityLogs
        | where TimeGenerated > ago(7d)
        | where RequestMethod == 'POST' and RequestUri has 'oAuth2PermissionGrant'
        | project TimeGenerated, AppId, UserId, IPAddress
        | summarize
            RequestCount = count(),
            Users = dcount(UserId),
            FirstSeen = min(TimeGenerated),
            LastSeen = max(TimeGenerated)
            by AppId
        | where Users > 1
        | order by Users desc
"@
    }
    '2' {
        $query = @"
        MicrosoftGraphActivityLogs
        | where TimeGenerated > ago(30d)
        | where RequestUri has 'oAuth2PermissionGrant' and RequestMethod == 'POST'
        | project TimeGenerated, AppId, UserId, IPAddress, RequestUri
        | sort by TimeGenerated desc
"@
    }
    '3' {
        $query = @"
        MicrosoftGraphActivityLogs
        | where TimeGenerated > ago(30d)
        | where RequestMethod == 'PATCH' and (RequestUri has '/applications/' or RequestUri has '/servicePrincipals/')
        | where RequestUri has 'addPassword' or RequestUri has 'addKey'
        | project TimeGenerated, AppId, UserId, IPAddress, RequestUri
        | sort by TimeGenerated desc
"@
    }
    '4' {
        $appId = Read-Host "Enter the Application ID (AppId) to investigate"
        $query = @"
        MicrosoftGraphActivityLogs
        | where TimeGenerated > ago(7d)
        | where AppId == '$appId'
        | project TimeGenerated, UserId, IPAddress, RequestMethod, RequestUri, ResponseStatusCode
        | sort by TimeGenerated desc
"@
    }
    default {
        Write-Error "Invalid selection. Please run the script again and choose between 1-4."
        exit
    }
}

Write-Host "`nRunning selected query..."
try {
    # Ensure the Az.OperationalInsights module is installed
    # Install-Module -Name Az.OperationalInsights -Scope CurrentUser -Force
    
    $result = Invoke-AzOperationalInsightsQuery -WorkspaceId $workspaceId -Query $query -ErrorAction Stop
    if ($result.Results) {
        Write-Host "`nQuery successful. Displaying results:"
        $result.Results | Format-Table -AutoSize
        Write-Host "`nSaving results to '$csvFilePath'..."
        $result.Results | Export-Csv -Path $csvFilePath -NoTypeInformation -Encoding UTF8
    } else {
        Write-Host "Query ran successfully but returned no results."
    }
}
catch {
    Write-Error "Query failed: $($_.Exception.Message)"
}

Write-Host "`nInvestigation complete."

OAuth application abuse poses a serious and often overlooked threat in Microsoft 365 environments. These attacks do not rely on traditional methods like stolen passwords but instead exploit legitimate platform features by gaining user consent through deceptive applications. Once access is granted, attackers can operate with elevated privileges, often without triggering standard security alerts.

While the Microsoft Graph Activity Log, combined with Kusto Query Language (KQL), offers the foundational visibility needed to uncover these threats, a comprehensive defense strategy also involves dedicated security platforms. For instance, security teams should investigate risky OAuth apps using the capabilities within Microsoft Defender for Cloud Apps to gain insights into app permissions and community trust levels. By running the targeted KQL queries outlined in this playbook and correlating the findings with alerts from these Defender products, teams can form a much clearer picture of potential abuse.

Automating these investigations using PowerShell enables teams to standardize detection efforts and respond faster. Monitoring OAuth activity is a critical part of protecting any Microsoft 365 environment. Gaining insight into how applications are behaving and who is granting them access empowers organizations to defend against persistent, identity-based threats and maintain a strong security posture.

About the Author

Mezba Uddin

Mezba Uddin is a Microsoft MVP, Technical Architect, and Microsoft Certified Trainer (MCT) with deep expertise in Microsoft 365, Exchange Online, Azure, Windows 365 and automation. With a strong background in IT infrastructure and cloud management, Mezba specializes in automating Microsoft 365 administration using PowerShell and Microsoft Graph API, helping organizations streamline operations and enhance security. As a recognized thought leader in the Microsoft ecosystem, Mezba actively contributes to the MVP community, mentors IT professionals, and shares insights through technical writing, public speaking, and Meetup groups. He runs MrMicrosoft.com, a platform dedicated to Microsoft 365 automation, PowerShell, and enterprise IT solutions, where he publishes in-depth guides and tools for IT professionals. Follow his latest insights on MrMicrosoft.com, connect on LinkedIn, or join his Meetup groups for live discussions on Microsoft technologies.

Leave a Reply