>
av.Amit VijayanCYBERSECURITY & RESEARCHLet’s connect
Back to the library

Cyber Playbooks

Hunting Rogue OAuth Consent Grants in Entra ID

Thumbnail for: Hunting Rogue OAuth Consent Grants in Entra ID

Deep-dive — October 7, 2026 · Threat Hunting

The user did everything right. They spotted the phish, they didn't type their password anywhere, and their MFA never got fatigued. Then they clicked "Accept" on a consent prompt for an app called "Microsoft 365 Backup" — a legitimate dialog, from Microsoft, asking for mail access. That one click handed an attacker's application a refresh token to their mailbox that survives password resets, is never challenged by MFA again, and generates no user sign-in events worth alerting on.

This is the illicit consent grant: OAuth abused the way the protocol was designed to work. It has been in the wild for years — APT29/Midnight Blizzard used malicious OAuth applications to get at Microsoft executive mailboxes in January 2024 — and the last few months have made it worse. In September 2026, Varonis published "TrustSink," showing a rogue External Authentication Method injected into the Entra sign-in flow could harvest plaintext passwords while completing the login normally. In July 2026, Proofpoint detailed OAuth client ID spoofing campaigns — two independent clusters, over 3 million users across thousands of Entra tenants, still active — enumerating accounts and validating stolen passwords without leaving a usable sign-in event. And just days ago, analysis of the UAT-11587 "Antino" campaign showed Microsoft 365 itself being turned into a C2 channel on the back of over-privileged app consents.

Identity is where the action is right now. This deep-dive is a working hunt: how the technique works, the telemetry to look at, and a walkthrough you can run in Sentinel or Splunk this afternoon.

How the attack works

The basic shape is consent phishing. The adversary registers a multi-tenant application in Entra ID with a boring, trustworthy name — "DocuSign Integration," "IT Security Scanner," "Teams Meeting Notes." They request OAuth scopes that give real access: Mail.Read or Mail.ReadWrite for the mailbox, Files.ReadWrite.All for SharePoint and OneDrive, Directory.Read.All for enumeration, and offline_access so the refresh token keeps working after the session ends. Then they get the target to open a crafted authorization URL. The consent screen is served by the identity provider itself — no spoofed login page, no credential capture, nothing for your email gateway's URL detonation to flag. The victim authorizes it, because it looks like a normal app-permission request.

After consent, the application holds its own tokens. Resetting the user's password does nothing to them. MFA never fires again, because the app authenticates with its token, not the user. And in many SOCs, nobody is watching service-principal activity at all — the team monitors user sign-ins, and the app is not a user.

There is a quieter variant worth naming separately: instead of registering a new app, the attacker adds a credential (secret or certificate) to an existing, already-consented service principal they can modify. No consent prompt, no new application, no new enterprise-app object — just one audit event that most SOCs do not alert on. If you only hunt for new app registrations, you miss this entirely. In MITRE ATT&CK terms, consent abuse maps to T1528 (Steal Application Access Token); the credential-addition variant maps to T1098.001 (Account Manipulation: Additional Cloud Credentials). Detection strategies DET0515 and DET0531 cover this space if you use ATT&CK's detection guidance.

Where the telemetry lives

Four audit operations are the core of this hunt, all in Entra ID AuditLogs under the ApplicationManagement category:

  1. Consent to application — a user or admin consented to an app's permissions.
  2. Add delegated permission grant — delegated OAuth scopes were granted.
  3. Add app role assignment to service principal — application (app-only) permissions assigned; this is how an app gets access without any user in the loop.
  4. Add service principal — a new enterprise application appeared in the tenant. (Also watch for credential additions to existing apps/service principals — the operation names vary by tenant and API version, so confirm yours before building the rule.)

Supporting telemetry, in order of usefulness: SigninLogs for service-principal sign-ins (what the app actually did after consent), the Microsoft 365 unified audit log for mailbox and file access attributed to the app, and Identity Protection risk detections for the consenting user. If you have E5 licensing, MailItemsAccessed is the only reliable way to see what mail an implant actually read.

Two practical notes before you write anything. First, audit log latency: entries can take 30 minutes to 24 hours to appear in search results after the event, and retention depends on the licenses assigned to your users. Second, operation names and field shapes differ between tenants, connectors, and the Microsoft Graph audit-log API — validate every query below against your own data before promoting it to an analytic rule. Treat these as hunt scaffolding, not paste-and-forget detections.

The walkthrough

Step 1: Baseline who consents to what

Pull 30 to 90 days of consent and grant activity and build the picture: which applications are normally approved, by whom, from where, and how often. You cannot spot the outlier without the baseline.

// Baseline: all consent and grant activity, last 90 days
AuditLogs
| where TimeGenerated > ago(90d)
| where Category == "ApplicationManagement"
| where ActivityDisplayName in (
    "Consent to application",
    "Add delegated permission grant",
    "Add app role assignment to service principal"
)
| extend Initiator = tostring(InitiatedBy.user.userPrincipalName)
| extend AppName  = tostring(TargetResources[0].displayName)
| extend AppId    = tostring(TargetResources[0].id)
| summarize
    FirstSeen = min(TimeGenerated),
    LastSeen  = max(TimeGenerated),
    Count     = count(),
    Actors    = make_set(Initiator)
    by AppName, AppId, ActivityDisplayName
| order by Count desc

Read this table like a defender: the top rows should be your sanctioned line-of-business apps, consented by IT staff or through the admin consent workflow. Note them down. This list becomes your allowlist, and you will diff against it.

Step 2: Find first-seen applications and unusual consentors

The highest-value signal is a first-seen application consented outside your normal pattern — a new app ID, a consent from a user who has never consented before, or consent at an unusual hour.

// First-seen apps in the last 14 days
AuditLogs
| where TimeGenerated > ago(90d)
| where Category == "ApplicationManagement"
| where ActivityDisplayName in (
    "Consent to application",
    "Add delegated permission grant",
    "Add app role assignment to service principal"
)
| extend Initiator = tostring(InitiatedBy.user.userPrincipalName)
| extend AppName  = tostring(TargetResources[0].displayName)
| extend AppId    = tostring(TargetResources[0].id)
| summarize FirstSeen = min(TimeGenerated), Actors = make_set(Initiator), Count = count() by AppName, AppId
| where FirstSeen > ago(14d)
| order by FirstSeen desc

Cross-check each hit against three questions: Is the publisher verified? (Unverified publisher on a mail-scoped app is a strong triage signal.) Did an admin consent, or a regular user — and if a user, were they recently phished? Does the app name impersonate something legitimate, with a lookalike spelling or a generic "Backup"/"Scanner"/"Integration" suffix?

Step 3: Look at the actual scopes granted

Consent to an app is noise; consent to Mail.ReadWrite plus offline_access is signal. Extract the granted scopes from the audit event and filter on the high-risk ones:

// Consent events touching high-risk scopes
let HighRiskScopes = dynamic(["Mail.Read", "Mail.ReadWrite", "Mail.Send",
    "Files.ReadWrite.All", "MailboxSettings.ReadWrite",
    "Directory.ReadWrite.All", "offline_access"]);
AuditLogs
| where TimeGenerated > ago(30d)
| where Category == "ApplicationManagement"
| where ActivityDisplayName has_any ("Consent", "permission grant")
| extend Initiator = tostring(InitiatedBy.user.userPrincipalName)
| extend AppName  = tostring(TargetResources[0].displayName)
| extend AppId    = tostring(TargetResources[0].id)
| extend RawEvent = tostring(TargetResources[0])
| where RawEvent has_any (HighRiskScopes)
| project TimeGenerated, Initiator, AppName, AppId, ActivityDisplayName
| order by TimeGenerated desc

The has_any on the serialized event is deliberately crude — it catches scope strings wherever they land in the event's JSON across tenant variations. In triage, open the full event and read the actual granted scope list before you judge it.

Step 4: Correlate — did the app do anything?

Consent alone is a lead, not a verdict. Join to SigninLogs for the service principal's activity after consent, and to the unified audit log for data access:

// Service-principal sign-ins for recently consented apps
let SuspectApps = AuditLogs
    | where TimeGenerated > ago(14d)
    | where Category == "ApplicationManagement"
    | where ActivityDisplayName has_any ("Consent", "permission grant")
    | extend AppId = tostring(TargetResources[0].id)
    | distinct AppId;
SigninLogs
| where TimeGenerated > ago(14d)
| where AppId in (SuspectApps)
| summarize Signins = count(), IPs = make_set(IPAddress),
    FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated)
    by AppDisplayName, AppId, ServicePrincipalId
| order by Signins desc

What you are looking for: a brand-new app that starts calling Graph immediately after consent, sign-ins from IPs or ASNs that do not match your estate, and mailbox or file operations (MailItemsAccessed, FileDownloaded) attributed to the app. A consent followed within minutes by bulk mail reads is the strongest confirmation you will get short of the attacker's confession.

For Splunk shops ingesting the M365 unified audit log, the same hunt in SPL:

index=o365 Operation="Consent to application" earliest=-30d
| stats earliest(_time) as first_seen, latest(_time) as last_seen,
    values(UserId) as consenting_users by ObjectId
| eval first_seen=strftime(first_seen, "%Y-%m-%d %H:%M")
| where first_seen > relative_time(now(), "-7d")
| sort - first_seen

Field names vary by add-on — validate Operation, ObjectId, and UserId against your ingestion before trusting the output.

Step 5: Pivot with OSINT — investigate the app from the outside

Your telemetry tells you what the app did inside your tenant. OSINT tells you what the app is outside of it — and a rogue app's external footprint is often where the story falls apart. Every Entra application registration carries public-facing artifacts: a publisher domain, a homepage URL, redirect URIs, and terms-of-service and privacy-statement links. Pull them with Graph or PowerShell:

# Pull the app's external footprint
$sp = Get-MgServicePrincipal -Filter "appId eq '<appId>'"
$app = Get-MgApplication -Filter "appId eq '<appId>'"
$app.PublisherDomain
$app.Info.Homepage
$app.Web.RedirectUris
$app.Info.TermsOfServiceUrl

Then work each artifact like an investigator, not an auditor:

Domain age and registration. Run the publisher domain and every redirect-URI host through WHOIS. A domain registered two weeks ago attached to an app requesting Mail.ReadWrite is a strong signal on its own. Check passive DNS and certificate transparency (crt.sh) for the same: lookalike domains minted in bulk around the same date — micorsoft365-backup.com next to m365-backup-portal.com — are how consent-phishing kits operate at scale.

Reputation and detonation. Submit the homepage and redirect URIs to VirusTotal and urlscan.io. Attackers reuse infrastructure across tenants, so an app that is brand-new to you may already be flagged elsewhere. urlscan is especially useful: its screenshot and DOM capture of the redirect target will show you whether the "integration portal" is a cloned Microsoft login page or a dead placeholder.

Name-squatting and campaign correlation. Search the app's display name in quotes alongside "oauth" or "consent" — security vendors publish IOCs from consent-phishing waves, and Proofpoint's July 2026 client-ID spoofing clusters are a good example of campaigns with public indicators you can match against. Compare the display name character-by-character against the legitimate app it mimics; homoglyphs and transposed letters ("DocuSIgn", "Microsft Teams") are the oldest trick here and still work.

Score it. None of these checks convicts on its own, but they stack: unverified publisher plus a young domain plus mail scopes plus a redirect URI already flagged on VirusTotal is a verdict, not a lead. Fold the OSINT findings back into your triage queue — Step 6 — and let the outside-in view rank what your inside-out telemetry surfaced.

Step 6: Triage and confirm

For each candidate, work the checklist:

  1. Publisher and provenance. Entra admin center → Enterprise applications → find the app. Unverified publisher? Recently created service principal? Typosquatted name?
  2. The consentor's story. Was the user phished recently? Check their sign-in logs for the authorization-URL click — AiTM and consent-phishing campaigns often show a suspicious login or an unfamiliar device just before the consent event.
  3. What it touched. With E5, MailItemsAccessed tells you what mail it read. Without it, use MailItemsAccessed-adjacent signals: message trace for sends, SharePoint FileDownloaded for bulk pulls.
  4. Blast radius. One consented app with Mail.ReadWrite can exfiltrate months of mailbox history in minutes. Scope your incident on data accessed, not on accounts compromised — the user whose password is fine may still be fully breached.

Remediation: revocation is not enough

This is the part teams get wrong. Revoking the consent grant removes future access, but the application's tokens may still be live, and the attacker may have added their own credentials or inbox rules as secondary persistence. The order:

  1. Revoke the grant (Remove-MgOAuth2PermissionGrant), remove app role assignments, and delete the malicious service principal.
  2. Assume refresh tokens and any app secrets are compromised — rotate credentials for every account that consented and revoke their sessions.
  3. Check for secondary persistence: inbox rules, mail forwarding, and additional credentials on nearby service principals.
  4. Then contain the identity that consented: if that account was phished, run your normal account-compromise playbook on it.

Defensive takeaways

  • Kill user consent. In Entra ID, set user consent to "Do not allow user consent," or restrict it to low-risk permissions from verified publishers only. Route everything else through the admin consent workflow. This single control blunts the entire technique.
  • Alert on the four operations. "Consent to application," "Add delegated permission grant," "Add app role assignment to service principal," and new/changed service principals — with first-seen logic, not flat volume thresholds. The credential-addition variant (T1098.001) deserves its own rule; it is the quietest path and the least monitored.
  • Baseline your sanctioned Graph apps. Document which line-of-business applications legitimately call Graph, who owns them, and alert on net-new Graph callers. You cannot spot the rogue app without the inventory.
  • Use Defender for Cloud Apps OAuth governance. Its app policies score OAuth apps on publisher verification, permission level, and community prevalence — a useful second opinion on your triage queue.
  • Constrain tokens with Conditional Access. Require compliant devices and approved client apps for Exchange Online and Graph; block legacy auth at the tenant level; scope service principals with Conditional Access for workload identities where licensed.
  • Review consent grants on a cadence. For a large tenant, a weekly review of new grants is the minimum. Microsoft's own guidance says the same: in a big environment, make it a weekly habit.

The uncomfortable truth of this technique is that nothing fails. The user authenticates properly, the dialog is genuine, the audit event is logged as a normal administrative action. Detection is not about finding the broken thing — it is about knowing what "normal consent" looks like in your tenant so the abnormal one stands out. Build the baseline now, before you need it.

Further reading

  • Varonis "TrustSink" research on rogue External Authentication Methods in Entra ID (Sept 2026) — summarized at dev.to
  • OAuth consent phishing mechanics and the "click Allow" attack path: nohack.net
  • Detection and response facts on OAuth token abuse (audit ops, high-risk scopes, remediation): huntaegis.com
  • TH-023 Entra application consent and credential abuse playbook (ATT&CK T1528 / T1098.001): GitHub
  • Consent-grant attack incident response runbook with KQL: GitHub
  • Detecting OAuth consent phishing in M365 audit logs with KQL: dev.to
  • Microsoft: Detect and remediate illicit consent grants: Microsoft Learn
From the HackInvasion archive

This sample preserves the article’s original text and publication date, restyled for Amit’s personal website. Older material may describe historical tools or techniques.

Read the original article on Blogger

Keep exploring.

All articles