Application registrations and enterprise apps can access corporate resources silently if granted high-privilege OAuth permissions.

Preface

As identity perimeters harden, adversaries target non-human application identities. Service principals using OAuth 2.0 can reach Exchange, SharePoint, and other data without MFA or interactive session controls. Once a grant exists, the application holds its own tokens and refresh capability, so resetting the user's password or revoking their sessions does not remove the access.

Problem

Actors such as Midnight Blizzard and Mango Sandstorm compromise low-privilege or test-tenant accounts, then register or modify multi-tenant apps. Dangerous scopes include Mail.ReadWrite and full_access_as_app. Attackers add their own certificates for persistence so the malicious app continues after the original account is reset or deleted.

How the attack works in practice

There are two broad patterns, and most tenants are exposed to both.

Consent phishing

The user receives a link to a real Microsoft sign-in page. Nothing about the domain or certificate looks wrong, because the page is genuine. After sign-in, Entra displays a consent prompt for an application with a plausible name and logo, requesting delegated scopes such as reading mail, files, or offline access. If user consent is allowed, one click creates a delegated permission grant. The attacker's app now holds a refresh token for that user and can read data through Microsoft Graph from anywhere, with no further prompts. Phishing-resistant MFA does not help here: the user authenticated correctly and then authorized the app.

App registration and credential abuse

The second pattern abuses the tenant's own application objects. An attacker with a foothold registers a new app, or finds an existing app or service principal with powerful application permissions, and adds a client secret or certificate. Application permissions such as Mail.ReadWrite or Exchange full_access_as_app apply to every mailbox in the tenant unless scoped, and they work without any signed-in user. Because the credential belongs to the app, it survives the remediation steps defenders usually take on the compromised human account.

Why common approaches fail

  • MFA and Conditional Access focus on users. Standard user-based Conditional Access does not apply to client-credential sign-ins by service principals.
  • Incident response stops at the account. Password resets and session revocation leave consent grants and app credentials in place.
  • Nobody owns the app inventory. Old test apps, proof-of-concept integrations, and departed developers' registrations accumulate with permissions nobody reviews.
  • "Verified publisher" is treated as safe. Verification proves who published an app, not that the permissions it requests are appropriate.

Solution

Align with CIS Control 16 and NIST SP 800-162:

  1. Disable standard user consent: Require an Admin Consent Workflow.
  2. Restrict app registrations: Stop non-admins from creating applications.
  3. Prefer Workload Identity Federation or Managed Identities: Eliminate long-lived secrets where possible.

Design guidance

Consent model

Either block user consent entirely or allow it only for low-impact delegated permissions from verified publishers, which is the built-in "low impact" permission grant policy. Enable the admin consent workflow so users can request an app instead of working around the block, and route requests to a small reviewer group that checks publisher, requested scopes, reply URLs, and business owner.

Application ownership and credentials

Limit app creation to the Application Developer role or a dedicated group. Every registration should have a named owner and a documented purpose. For workloads in Azure, use managed identities. For CI/CD pipelines and external platforms such as GitHub Actions or Kubernetes, use workload identity federation instead of secrets. Where a credential is unavoidable, prefer certificates over secrets, set short expiry, and enforce this through application management policies. Enable app instance property lock on multi-tenant apps you publish, so credentials cannot be added to their service principals in customer tenants.

Scope reduction

Treat application permissions on mail, files, and directory write as privileged access. Where an app genuinely needs mailbox access, scope it to specific mailboxes using Exchange Online RBAC for Applications rather than granting tenant-wide access.

Implementation checklist

  1. Export all service principals with delegated grants and app role assignments, and flag high-privilege scopes.
  2. Revoke grants for apps with no owner, no recent sign-in, or no business justification.
  3. Configure user consent and the admin consent workflow, then communicate the change to staff.
  4. Disable default app registration for users.
  5. Add owners, expiry dates, and certificate credentials to the remaining apps; migrate pipeline secrets to federation.
  6. Enable app governance in Microsoft Defender for Cloud Apps if licensed, and schedule periodic access reviews of privileged apps.
  7. Deploy the detections below and add OAuth grant review to the compromised-account playbook.

PowerShell: Harden Authorization and App Policies

App creation and user consent are both controlled through defaultUserRolePermissions on the tenant authorization policy. An empty list of assigned permission grant policies disables user consent; assign the low-impact policy instead if you allow limited consent.

Connect-MgGraph -Scopes "Policy.ReadWrite.Authorization"
$params = @{
    DefaultUserRolePermissions = @{
        AllowedToCreateApps = $false
        PermissionGrantPoliciesAssigned = @()
    }
}
Update-MgPolicyAuthorizationPolicy -BodyParameter $params
(Get-MgPolicyAuthorizationPolicy).DefaultUserRolePermissions

Monitoring and detection

Consent grants and credential additions are recorded in Entra audit logs. In Microsoft Sentinel, alert on new grants and on credentials being added to existing apps, which is the persistence step:

AuditLogs
| where TimeGenerated > ago(7d)
| where OperationName in ("Consent to application", "Add delegated permission grant",
    "Add app role assignment to service principal", "Add service principal credentials")
    or OperationName has "Certificates and secrets management"
| extend Actor = coalesce(tostring(InitiatedBy.user.userPrincipalName),
    tostring(InitiatedBy.app.displayName))
| extend Target = tostring(TargetResources[0].displayName)
| project TimeGenerated, OperationName, Actor, Target, Result
| order by TimeGenerated desc

Tune by excluding known automation, and raise severity when the target app holds mail, file, or directory write permissions. Also hunt in AADServicePrincipalSignInLogs for service principals authenticating from unexpected IP ranges, and review apps that suddenly start calling Graph after months of inactivity.

Common mistakes

  • Blocking user consent without an admin consent workflow, which pushes users to shadow IT.
  • Revoking a compromised user's sessions but not the grants their account created.
  • Reviewing app registrations but ignoring enterprise apps (service principals) from other tenants.
  • Leaving long-lived client secrets on apps with tenant-wide application permissions.
  • Granting Directory.ReadWrite.All or RoleManagement.ReadWrite.Directory to automation that only needs read access.

Where to start

This week, inventory every app holding mail, file, or directory permissions and identify its owner. Next, close user consent and app registration with an admin consent workflow in place. Then deploy the audit log detections and make OAuth grant review a standard step in every account compromise investigation.