Audit OAuth app permissions, identify over-privileged applications, create policies for risky consent grants, investigate suspicious app behaviour, revoke access from compromised apps, and remediate compromised service accounts.
OAuth applications are the silent attack surface of modern enterprises. Every time a user clicks “Allow” on a consent prompt, they may be granting a third-party application access to corporate data in Exchange, SharePoint, OneDrive, or Teams. Illicit consent grants and overprivileged OAuth apps are increasingly exploited in supply-chain attacks, credential harvesting, and data exfiltration campaigns. Defender for Cloud Apps provides a comprehensive inventory of all OAuth apps connected to your tenant, their permissions, publisher verification status, and data access patterns.
A security operations team discovers that a marketing survey tool granted itself Mail.ReadWrite and Files.ReadWrite.All permissions through a user consent flow. The app, from an unverified publisher, has been accessing 200+ mailboxes and downloaded 8 GB of SharePoint data overnight. Simultaneously, three service accounts with application-level permissions show login activity from unexpected IP addresses. The team must identify all risky OAuth apps, revoke unauthorized access, create preventative policies, and investigate the extent of data exposure.
OAuth-based attacks bypass traditional security controls: they don’t involve malware, don’t trigger endpoint alerts, and use legitimate API calls that blend with normal traffic. A single overprivileged OAuth app can read every email in your organisation without triggering any traditional security alert. In 2024, Microsoft observed a 300% increase in illicit consent grant attacks targeting Microsoft 365 tenants.
The OAuth apps page provides a comprehensive inventory of all third-party applications that have been granted access to your Microsoft 365 tenant through OAuth consent.
Overprivileged apps request more permissions than they need for their stated purpose. Identify these apps and assess whether their permissions are justified.
Mail.ReadWrite, Files.ReadWrite.All, Directory.ReadWrite.All, User.ReadWrite.All# ---------------------------------------------------------------
# PURPOSE: List all OAuth apps in your tenant that hold high-privilege
# delegated permissions (ReadWrite, FullControl, or All scopes).
# WHY: Overprivileged OAuth apps are the silent attack surface of M365.
# An app with Mail.ReadWrite can read/modify every email; one with
# Files.ReadWrite.All can access every file in SharePoint/OneDrive.
# This script identifies those apps for review and potential revocation.
# PREREQUISITES: Install-Module Microsoft.Graph -Scope CurrentUser
# Requires Application.Read.All permission.
# OUTPUT: Table showing AppName, AppId, Publisher, and the high-privilege
# Scopes granted. Apps from unknown publishers with broad scopes
# (e.g. "Mail.ReadWrite User.ReadWrite.All") are highest priority.
# ---------------------------------------------------------------
Connect-MgGraph -Scopes "Application.Read.All"
# Iterate over every service principal (app) in the tenant.
Get-MgServicePrincipal -All | ForEach-Object {
$sp = $_
# Get all OAuth2 delegated permission grants for this app.
$grants = Get-MgServicePrincipalOAuth2PermissionGrant -ServicePrincipalId $sp.Id -All
# Filter to only grants that include dangerous scope keywords.
$highPriv = $grants | Where-Object { $_.Scope -match "ReadWrite|FullControl|All" }
if ($highPriv) {
[PSCustomObject]@{
AppName = $sp.DisplayName # App/service principal display name
AppId = $sp.AppId # Application (client) ID
Publisher = $sp.PublisherName # Publisher - "" means unverified
Scopes = ($highPriv.Scope | Select-Object -Unique) -join ", " # Granted scopes
}
}
} | Format-Table -AutoSizePublisher verification is a critical trust signal. Verified publishers have completed the Microsoft Partner Network attestation process, while unverified publishers have not proven their identity.
Review how much data each app has accessed and whether the access patterns are consistent with the app’s stated purpose.
// ---------------------------------------------------------------
// PURPOSE: Investigate a suspicious OAuth app's activity in Defender XDR
// Advanced Hunting to determine what data it accessed.
// WHY: When you identify a suspicious app in the OAuth dashboard,
// you need to understand: how many API calls it made, how much
// data it accessed, and how many mailboxes/accounts it touched.
// This helps determine if data was exfiltrated.
// HOW TO USE: Replace "SuspiciousAppName" with the app's display name.
// Run in Defender XDR > Hunting > Advanced hunting.
// OUTPUT COLUMNS:
// ActionType - the API operation (e.g. MailItemsAccessed, FileDownloaded)
// Timestamp (1d bins) - shows activity pattern over 30 days
// TotalActivities - number of API calls per day per action type
// DataAccessedMB - total data volume accessed (in megabytes)
// UniqueMailboxes - distinct accounts the app touched
// RED FLAGS: High DataAccessedMB outside business hours, or
// UniqueMailboxes far exceeding the app's stated purpose.
// ---------------------------------------------------------------
CloudAppEvents
| where Timestamp > ago(30d)
| where Application == "SuspiciousAppName"
| summarize TotalActivities = count(),
DataAccessedMB = sum(ActivityObjects.Size) / 1048576,
UniqueMailboxes = dcount(AccountObjectId)
by ActionType, bin(Timestamp, 1d)
| order by Timestamp descCreate automated policies that detect and alert on risky OAuth consent grants as they happen.
Alert on High-Privilege Consent from Unverified PublishersEnable built-in anomaly detection policies that use machine learning to identify unusual app behaviour patterns.
When you identify a malicious or overprivileged app, revoke its permissions and ban it to prevent future consent.
# ---------------------------------------------------------------
# PURPOSE: Revoke all delegated permissions from a malicious OAuth app
# and disable its service principal to prevent further access.
# WHY: When an app is confirmed malicious (e.g. exfiltrating data),
# you must immediately remove its permissions AND disable it.
# Simply revoking permissions isn't enough - the app could
# re-request consent. Disabling the service principal blocks
# all authentication and API access.
# STEPS:
# 1. Remove all OAuth2 delegated permission grants
# 2. Disable the service principal (AccountEnabled = false)
# OUTPUT: Confirmation message with the disabled app's display name.
# WARNING: Verify the app is not used for legitimate automation
# before disabling - this will break any workflows using it.
# ---------------------------------------------------------------
$appId = "malicious-app-client-id" # The Application (client) ID to revoke
# Look up the service principal by its application ID.
$servicePrincipal = Get-MgServicePrincipal -Filter "AppId eq '$appId'"
# Step 1: Remove every delegated permission grant for this app.
# This immediately revokes all user-consented scopes.
Get-MgServicePrincipalOAuth2PermissionGrant -ServicePrincipalId $servicePrincipal.Id -All |
ForEach-Object { Remove-MgOAuth2PermissionGrant -OAuth2PermissionGrantId $_.Id }
# Step 2: Disable the service principal so it cannot authenticate at all.
# Even if permissions are re-granted, the app cannot sign in while disabled.
Update-MgServicePrincipal -ServicePrincipalId $servicePrincipal.Id -AccountEnabled:$false
Write-Host "App revoked and disabled: $($servicePrincipal.DisplayName)"Service accounts with application-level permissions are high-value targets. Investigate service accounts showing suspicious activity.
# ---------------------------------------------------------------
# PURPOSE: Investigate service principal (non-human) sign-ins to
# detect compromised service accounts.
# WHY: Service principals with application-level permissions are
# high-value targets. If an attacker compromises a service
# account's client secret, they can silently access mailboxes,
# files, and directories. Unusual IP addresses or geolocations
# in sign-in logs are strong indicators of compromise.
# PREREQUISITES: AuditLog.Read.All permission in Microsoft Graph.
# OUTPUT: Table of the 50 most recent service principal sign-ins
# showing timestamp, service name, IP address, location
# (city + country), and sign-in status.
# RED FLAGS: Sign-ins from unexpected countries, Tor exit nodes,
# VPN services, or IPs not in your data centre ranges.
# ---------------------------------------------------------------
Connect-MgGraph -Scopes "AuditLog.Read.All"
# Query Entra ID sign-in logs for service principal authentications.
# servicePrincipalId ne null filters out user sign-ins.
Get-MgAuditLogSignIn -Filter "servicePrincipalId ne null" -Top 50 |
Select-Object CreatedDateTime, ServicePrincipalName, IpAddress,
@{N='Location';E={$_.Location.City + ', ' + $_.Location.CountryOrRegion}},
Status |
Format-Table -AutoSizeWhen a service account or OAuth app is confirmed compromised, immediately rotate credentials and remove unauthorized access.
# ---------------------------------------------------------------
# PURPOSE: Rotate (replace) all client secrets for a compromised
# app registration and issue a new short-lived secret.
# WHY: When an app registration's credentials are compromised, the
# attacker holds valid secrets. You must:
# 1. Delete ALL existing secrets (including attacker-added ones)
# 2. Create a fresh secret with a shorter lifetime (6 months)
# 3. Update all legitimate services using this app's credentials
# IMPORTANT: After running this script, immediately update the secret
# in all applications, key vaults, or CI/CD pipelines that use it.
# The old secrets are instantly invalid - dependent systems will break.
# OUTPUT: The new secret value (shown only once). Copy it immediately;
# it cannot be retrieved again from Entra ID.
# ---------------------------------------------------------------
$appObjectId = "your-app-object-id" # The Object ID of the app registration
# Step 1: Remove ALL existing client secrets (passwords).
# This invalidates any secrets the attacker may have stolen or added.
Get-MgApplicationPassword -ApplicationId $appObjectId | ForEach-Object {
Remove-MgApplicationPassword -ApplicationId $appObjectId -KeyId $_.KeyId
}
# Step 2: Create a new secret with a descriptive name and 6-month expiry.
# Shorter lifetimes limit exposure if this secret is also compromised.
$newSecret = Add-MgApplicationPassword -ApplicationId $appObjectId -PasswordCredential @{
DisplayName = "Rotated-$(Get-Date -Format 'yyyyMMdd')" # Date-stamped for audit trail
EndDateTime = (Get-Date).AddMonths(6) # Expires in 6 months
}
# CRITICAL: This is the ONLY time the secret value is visible.
# Store it in Azure Key Vault - never in code or config files.
Write-Host "New secret created. Value: $($newSecret.SecretText)"
Write-Host "Update all applications using this secret immediately."Restrict user consent to prevent future illicit consent grants. Configure Entra ID to limit which apps users can consent to independently.
openid, profile, email, User.ReadCreate a structured approval process for apps requiring permissions beyond user consent limits.
Create comprehensive app governance policies that enforce compliance standards across all OAuth apps in your tenant.
MDA OAuth app alerts automatically correlate with Defender XDR incidents, providing cross-product context for investigations.
Create reports that communicate OAuth app risk posture to leadership and demonstrate security value.
| Resource | Description |
|---|---|
| Manage OAuth apps | Investigate and manage connected OAuth applications |
| OAuth app policies | Create policies to detect risky OAuth consent grants |
| Configure user consent | Restrict user consent settings in Entra ID |
| Admin consent workflow | Set up admin approval for app consent requests |
| Investigate risky OAuth apps | Investigation procedures for suspicious OAuth apps |