Skip to main content
VulnerabilityConditional Accessentra-idMFAOAuth

Exploiting the Microsoft 365 Substrate: How First-Party OAuth Clients Became an MFA Bypass (VULN-154544)

Rawson Wade18 min read
Exploiting the Microsoft 365 Substrate: How First-Party OAuth Clients Became an MFA Bypass (VULN-154544)

This is the technical write-up and the disclosure story. If you just need to find and fix the exposure in your own tenant, start with the defender companion: Conditional Access Resource Exclusions: Remediation + SIEM.

TLDR

If a Conditional Access policy targeting All resources excluded even one unrelated application from MFA and device compliance, then a stolen username and password alone could acquire a Microsoft Graph access token with no MFA and no compliant device. The token looked low privilege (scp of just openid), but authorisation on the Microsoft 365 substrate did not follow the token's scopes. Depending on which first-party client you presented as, that same token reached directory data, a user's calendar and mailbox, and the unified search index behind Outlook, OneDrive, SharePoint and Teams. I reported it as VULN-154544. Microsoft rated it Moderate, then called it "by design", before reclassifying it to Important, paying a bounty, and shipping a fix between March and June 2026.

Summary

FieldDetail
Tracking IDVULN-154544 (Microsoft Security Response Centre)
CVENone assigned. Microsoft declined a CVE, citing the Moderate rating it later reversed.
ClassConditional Access policy bypass / authorisation decoupled from token scope
CWECWE-287 (Improper Authentication), CWE-285 (Improper Authorization), CWE-863 (Incorrect Authorization)
Affected controlEntra ID Conditional Access grant controls (require MFA, require compliant device) on an All resources policy carrying an app exclusion
VectorCredentials only. A valid username and password, no second factor, no managed device.
Reported severityModerate, later reversed to Important / Elevation of Privilege
StatusReported 21 May 2025. Fix rolled out in phases March to June 2026.
ResearcherRawson Wade, Modern 42. Initial behaviour surfaced by Michael Bruce.

Where this started

During a Modern 42 Conditional Access assessment, a colleague (Michael Bruce) showed me something that is documented but rarely thought through. The Microsoft Teams client, signed in with only a username and password, slipped past the tenant's Conditional Access policies for MFA and device compliance and could still enumerate the directory, with no second factor and no compliant device.

That is well-trodden ground on its own. Application exclusions in Conditional Access are known to create gaps, and Microsoft documents that certain low privilege scopes ride along with an app exclusion. What nagged at me was the word "low". It felt optimistic. I wanted to know why those tokens were being issued, what they could actually touch, and whether "low privilege" held up once you started poking at the edges.

So I started digging. What I found was less of a gap and more of a motorway.

The control that was supposed to stop this

Conditional Access is the Entra ID policy engine. On each sign-in it evaluates signals (user, device, location, application, risk) and decides to allow, block, or require something like MFA or a compliant device. For most organisations it is the primary authentication control, the thing standing between a leaked password and the tenant.

There is one distinction that the whole finding hangs on. When a user authenticates they tell Microsoft two separate things:

  1. The client: the application they are signing in from (Microsoft Teams, Outlook, a line-of-business app).
  2. The resource: the audience for the access token they want (Microsoft Graph, Exchange Online, SharePoint).

Conditional Access policies evaluate primarily against the resource, not the client. The client is largely the attacker's to choose. Hold that thought.

The documented exclusion, and the word "low"

Microsoft's own documentation is clear that when you exclude an application from an All resources policy, a set of low-privilege scopes are automatically excluded along with it. In Microsoft's words, these exclusions "don't allow data access beyond basic user profile and group information." The stated ceiling is basic profile and group membership, nothing more.

The scopes look harmless enough: openid, profile, email, offline_access, User.Read. Confidential clients pick up a few more (User.Read.All, User.ReadBasic.All, People.Read.All, GroupMember.Read.All, Member.Read.Hidden). The intent was reasonable: stop an administrator from accidentally locking users out of basic sign-in functionality when they exclude, say, a VPN client or an MDM agent that cannot do interactive MFA.

In concrete terms, the dangerous shape is an All resources policy that is strict in every way except for a single carve-out. It looks completely reasonable on review:

{
  "displayName": "Require MFA and compliant device for All resources (one app excluded)",
  "state": "enabled",
  "conditions": {
    "clientAppTypes": ["all"],
    "applications": {
      "includeApplications": ["All"],
      "excludeApplications": ["<one unrelated, legitimately-excluded app>"]
    },
    "users": { "includeUsers": ["All"] }
  },
  "grantControls": {
    "operator": "AND",
    "builtInControls": ["mfa", "compliantDevice"]
  }
}

That one excluded application is all it takes. It does not need to be related to anything sensitive, and it does not need to be the app you then sign in as.

I set up a test tenant with a configuration I see in a lot of production environments: require MFA and a compliant device for All resources, exclude one unrelated application. Then I acquired a token the way an attacker holding only a password would, and confirmed the documented part: a token came back, no MFA challenge, no compliance check, Conditional Access not applied. Expected so far.

A valid username and password alone return a Microsoft Graph access token: no MFA prompt, no compliant device, Conditional Access never applied.

The real question was what that token could actually do.

The token said one thing. Graph did another.

The issued access token was, on inspection, almost boring. Audience was Microsoft Graph. The scope claim (scp) was effectively just openid. On paper that is close to nothing: enough to prove who you are, not enough to read anyone's data.

So I pointed it at Microsoft Graph and watched what came back. Files, chats, and most endpoints returned 403 Forbidden, which is what the scope claim would lead you to expect. A boundary clearly existed. But a handful of calls did not behave. Reading the signed-in user's calendar returned 200 OK.

That should not have been possible. Microsoft's own API reference says reading a user's calendar needs a scope like Calendars.ReadBasic in the token. My token had openid. The authorisation decision was not being made from the token's scopes. Something else was deciding what this token could do, and it was saying yes to things the scope said no to.

Calendar access then turned out to be more than read. In the configurations I tested it allowed creating events as the user, sending calendar invites to internal and external recipients with full HTML bodies and attachments, modifying existing invites, and reaching shared mailboxes the user had access to. A trusted internal sender delivering an attachment by calendar invite, with no mailbox permission in the token, is a tidy business email compromise primitive. I want to be careful not to oversell it, but that is a meaningful step up from "basic profile and group information."

Why the calendar, of all things

I almost moved on, because there is an innocent explanation and it is half right. Scheduling a meeting is exactly what Teams does. You create a meeting in Teams, Exchange sends the invite and the event lands on the attendee's calendar. I would bet there was once a backlog ticket somewhere to make that work, because the client has to organise meetings on the user's behalf for the core product to function.

But that is the point. A token whose scope is openid should not be able to send invites at all. The client was granted reach that the token never reflected, and only for the things that particular client happened to need. That is when I stopped thinking about scopes and started thinking about clients.

Controlling the client

My working hypothesis went like this. The access token is scoped to the resource, and Conditional Access evaluates on the resource, so that part of the system behaves. But the client is arbitrary. The attacker chooses which first-party application to present as. Each first-party client carries its own authorisation reach on the back end, granted through mechanisms that leave little or no trace in the token's scp claim.

Microsoft's access token documentation even says the quiet part out loud: claims such as scp "are not an exhaustive representation of all permissions that a caller may be authorized for." In practice, authorisation on the substrate came from several places at once: OAuth scopes, app roles, directory roles, and other mechanisms that left no claim in the token at all. For first-party clients, that left a minimal token scope sitting on top of generous back-end authorisation.

So the same credentials-only, no-MFA token means different things depending on which client you present as.

Put end to end, the whole bypass fits in one picture:

The same low-privilege token, replayed as different first-party clients, reaches different data. The client ID, not the token scope, set the privilege boundary.

The Microsoft 365 substrate

To understand the reach, it helps to know what sits behind Microsoft Graph. The substrate is Microsoft 365's shared data layer. It routes, authorises, and transforms data from the individual services (Exchange, SharePoint, OneDrive, Teams, Entra ID, and third-party connectors) into one uniform API surface. One front door, many rooms behind it. If you have ever noticed that errors look subtly different between Teams, OneDrive, and Exchange, you have seen the seams of the services behind that single API.

So once a client's authorisation lets you onto the substrate, you are not limited to one product's data.

Capability by client

I built a simple harness that replayed the same low-privilege token against a long list of Graph endpoints, once per first-party client identity, and recorded which calls returned data. Do not read too much into any single row. What matters is how much the same token's reach changes with the client you borrow.

First-party client (scope = openid)DirectoryDevicesAppsDirectory adminCalendarSubstrate
Microsoft Teams●–––●–
Microsoft Bing Search●●●●––
Bing Search for Edge●●●●––
Windows Search●––––●
Office 365 Management●–––––
~10 other clients (baseline directory only)●–––––

Legend: ● reach observed, – none observed in testing.

Directory enumeration alone, available to the baseline clients, gave up more than the "basic profile and group information" label suggests. In testing it surfaced users and groups, service principals and applications with their delegated and application permissions, redirect URIs and SSO configuration, device objects and their owners, SharePoint and OneDrive drive references, and administrator role assignments including the privileged and break-glass accounts. That last one matters: a credentials-only sign-in mapping out exactly who the global admins are is a reconnaissance gift.

Directory reconnaissance from a credentials-only token: users, groups, devices, service principals, and the administrator role assignments that reveal the privileged accounts.

Taking it to the limit

The uncomfortable version of the impact is what you can chain together once you accept that the resource keeps saying yes to this token.

Land and expand through calendar. As covered above, a trusted internal sender can deliver a malicious attachment or a doctored meeting link by calendar invite, with no mailbox permission in the token. Internal phishing from a real colleague's identity converts well.

Exfiltrate through the substrate's search. The unified search index behind Microsoft 365 is reachable through the substrate. It spans Outlook, OneDrive, SharePoint, Teams, SharePoint lists, and the growing list of third-party connectors organisations wire in (ticketing, chat, cloud databases, HR systems, source control, on-prem file servers). It supports fuzzy matching, and it understands sensitivity labels. An actor can effectively ask the index to surface the most sensitive material first rather than crawling blindly. I am deliberately describing the capability rather than handing over a recipe, but the shape of it is: one credentials-only token, a lot of other people's data, ranked by how secret it is.

Reaching the unified search index behind Microsoft 365. The index is not a door, so scoping the client did not scope what the search could reach.

Compromise privileged accounts. Combine the directory reconnaissance (who holds which admin role) with the phishing primitive (a trusted internal invite) and you have a targeting list and a delivery channel from the same stolen password.

The enabling conditions were mundane, which is what makes it worth caring about:

  • A valid username and password for any user. This is the normal output of an adversary-in-the-middle phishing kit, a credential-stuffing hit, or an infostealer log.
  • A Conditional Access policy targeting All resources that excludes at least one arbitrary application from MFA or device compliance. From what I have seen in the field, this is extremely common in enterprise tenants.

Why it happened: two account types, one trust model

Fourteen months after I reported it, Microsoft shared a root cause that finally made sense of it. Consumer Microsoft Account (MSA) token logic had been applied to Entra ID tokens.

Microsoft operates two account types with two different trust models that happen to share issuance code:

  • MSA (consumer): personal accounts for Outlook.com, Xbox, and the like. Their tokens use a simpler model with coarse, non-granular permissions.
  • Entra ID (organisation): work and school accounts. Their tokens are meant to carry granular per-scope permissions and are governed by Conditional Access.

An assumption that is perfectly safe for a consumer token, roughly "a first-party client is allowed to do its basic job," is not safe for an enterprise token that Conditional Access is supposed to be gating. The coarse consumer-style authorisation path let first-party clients reach substrate data that the enterprise token's scope never authorised, and Conditional Access had already been skipped at the exclusion. Two reasonable designs, glued together, produced an unreasonable result.

This is also why Microsoft's early written response did not land for me. The argument was that "the conditional access protections for resources during token acquisition are orthogonal to how these apps have permissions granted and what they use for authorization." That is true as a statement about how the pieces are built. It just was not the finding. The finding was that a stolen password alone bypassed MFA and device compliance across the tenant, and for weeks that part was the bit being called "by design."

Detection

The fix is Microsoft's and it has shipped, so the highest-value action for most readers is to check whether this was ever used against you while the gap was open, and to keep watching for the same shape. The tell is a sign-in to a first-party client, against Microsoft Graph, completing with single-factor authentication where a policy would normally have required more.

The first-party clients I observed carrying reach beyond basic profile make a useful watchlist. These application IDs are public and well known:

First-party clientApplication ID
Microsoft Teams1fec8e78-bce4-4aaf-ab1b-5451cc387264
Windows Search26a7ee05-5602-4d76-a7ba-eae8b7b67941
Windows Spotlight1b3c667f-cde3-4090-b60b-3d2abd0117f0
Enterprise Roaming and Backup60c8bde5-3167-4f92-8fdb-059f6176dc0f
Microsoft Azure CLI04b07795-8ddb-461a-bbee-02f9e1bf7b46
Microsoft Azure PowerShell1950a258-227b-4e31-a9cf-717495945fc2
Microsoft Bing Searchcf36b471-5b44-428c-9ce7-313bf84528de
Bing Search for Microsoft Edge2d7f3606-b07d-41d1-b9d2-0d0c9296a6e8
Exchange REST API Based PowerShellfb78d390-0c51-40cd-8e17-fdbfab77341b
Microsoft Intune Windows Agentfc0f3af4-6835-4174-b806-f7db311fd2f3
Microsoft Outlook5d661950-3475-41cd-a2c3-d671a3162bc1
Office 365 Management00b41c95-dab0-4487-9791-b9d2c32c80f2
Outlook Mobile27922004-5251-4030-b22d-91ecd9a37ea4
Universal Store Native Client268761a2-03f3-40df-8a8b-c3db24145b6b

This Sentinel query surfaces those clients acquiring a Microsoft Graph or legacy Azure AD Graph token with single-factor auth, across both interactive and non-interactive sign-ins:

// VULN-154544 signature: a first-party client acquiring a Microsoft Graph
// (or legacy Azure AD Graph) token with single-factor auth, where an
// All-resources policy would normally have required MFA or a compliant device.
union SigninLogs, AADNonInteractiveUserSignInLogs
| where TimeGenerated > ago(90d)
| where ResourceId in (
    "00000003-0000-0000-c000-000000000000", // Microsoft Graph
    "00000002-0000-0000-c000-000000000000"  // Azure AD Graph (legacy)
  )
| where AppId in (
    "1fec8e78-bce4-4aaf-ab1b-5451cc387264", // Microsoft Teams
    "26a7ee05-5602-4d76-a7ba-eae8b7b67941", // Windows Search
    "1b3c667f-cde3-4090-b60b-3d2abd0117f0", // Windows Spotlight
    "60c8bde5-3167-4f92-8fdb-059f6176dc0f", // Enterprise Roaming and Backup
    "04b07795-8ddb-461a-bbee-02f9e1bf7b46", // Microsoft Azure CLI
    "1950a258-227b-4e31-a9cf-717495945fc2", // Microsoft Azure PowerShell
    "cf36b471-5b44-428c-9ce7-313bf84528de", // Microsoft Bing Search
    "2d7f3606-b07d-41d1-b9d2-0d0c9296a6e8", // Bing Search for Microsoft Edge
    "fb78d390-0c51-40cd-8e17-fdbfab77341b", // Exchange REST API Based PowerShell
    "fc0f3af4-6835-4174-b806-f7db311fd2f3", // Microsoft Intune Windows Agent
    "5d661950-3475-41cd-a2c3-d671a3162bc1", // Microsoft Outlook
    "00b41c95-dab0-4487-9791-b9d2c32c80f2", // Office 365 Management
    "27922004-5251-4030-b22d-91ecd9a37ea4", // Outlook Mobile
    "268761a2-03f3-40df-8a8b-c3db24145b6b"  // Universal Store Native Client
  )
| where AuthenticationRequirement == "singleFactorAuthentication"
| where ConditionalAccessStatus in ("notApplied", "success")
| summarize SignInCount = count(),
            DistinctUsers = dcount(UserPrincipalName),
            LastSeen = max(TimeGenerated),
            SampleIPs = make_set(IPAddress, 5)
    by AppDisplayName, AppId, ResourceDisplayName, AuthenticationRequirement
| sort by SignInCount desc

Treat the results as leads, not verdicts: some of these clients are busy and legitimate in a healthy tenant, so triage on source IP and geography, volume, and whether the user has a device-compliant baseline elsewhere. ROPC sign-ins are already detectable with existing content in Sentinel and Elastic. The newer signal here is the client-to-resource mapping arriving with single-factor auth where you expected a challenge. A fuller detection set, including the scope-extraction queries for Sentinel KQL and Splunk SPL and a PowerShell audit of which service principals hold the affected low-privilege grants, is in the defender companion.

Remediation

On the Entra ID side of this specific issue there is not much left for you to do, because it is fixed. Microsoft shipped the change (MC1223829) in phases from March through June 2026, mapping the affected low-privilege scope requests to the Azure AD Graph resource so that All resources policies now enforce against them even when an app exclusion is present. There is no opt-out, and it is being treated as part of the Secure Future Initiative. Microsoft's follow-up control, the "baseline scopes" enforcement change, is default-on and configurable on the Entra side (see aka.ms/BaselineScopesSettingsUX).

What is worth doing regardless of any single patch:

  • Audit your Conditional Access policies for application exclusions. Every exclusion on an All resources policy is a place this class of problem lives. If the original reason for an exclusion no longer holds, remove it rather than working around it.
  • Build a baseline MFA policy targeting all users and all resources with no resource exclusions, keeping only tightly scoped user exclusions for break-glass accounts. Microsoft recommends this and it closes the whole class. The step-by-step impact assessment is in the companion post.
  • Push controls down to the data and app layer. Zero trust means not trusting the client, network, or device by default. The direction of travel is app protection and data-layer controls so that a stolen password on its own reaches nothing. Note the caveats in our Intune MAM research: a parallel control is only as strong as its weakest branch.
  • Pressure-test your policies. Semi-regular penetration testing of your Conditional Access framework, especially after major policy changes, catches this kind of interrelationship gap before an attacker does. Tooling like our SuiteAuth Toolkit is built for exactly this.

The disclosure: fourteen months, in their words

The technical story is only half of it. The other half is what it is like to report this kind of thing, and it is worth telling honestly.

  • 21 May 2025: Reported. A credentials-only bypass of MFA and device compliance, kept confidential.
  • 13 Jun 2025: Rated Moderate. "Service teams will fix as required."
  • 01 Jul 2025: "This is by design." In writing, the bypass was classified as intended behaviour. The finding was dismissed for weeks, even after pushback.
  • 06 Aug 2025: Confirmed Moderate, no patch timeline. "As this is Moderate severity, we would not be releasing a CVE."
  • 10 to 15 Oct 2025: With a conference talk about this very issue three days out, MSRC asked to review my slides. Over a single day it escalated from "redact the attack capabilities" to "remove all references to the issue," reclassified the bug to Important in the same breath, and confirmed bounty eligibility about eight hours before I went on stage. I rewrote the talk overnight, swapped the demo to a generic adversary-in-the-middle, and stripped every mention of scope and app exclusion down to "circumvented due to [redacted]."
  • 16 Oct 2025: Talk delivered, redacted.
  • 17 Oct 2025: Recognised. A five-figure bounty, logged as Important / Elevation of Privilege.
  • Mar to Jun 2026: The fix ships. With no opt-out it also locked out some legitimate apps that depended on the old behaviour, and I spent time helping the customers it stranded.
  • 07 Jul 2026: Root cause shared. Consumer MSA-token logic had been applied to Entra tokens.
  • 27 Jul 2026: "Please give the talk." Microsoft asked me to present the finding properly. This write-up is the un-redacted version of that talk.

What I would do differently

A few lessons, mostly for anyone newer to vulnerability research than me.

Trust your instincts. It felt wrong, and it was. "Moderate" and "by design" made me second-guess myself for longer than I should have. Next time I hold the line.

Submit your slides early. Even when you think you have lost the fight, get them in with room to spare. A thirty-four-hour turnaround to rewrite a talk is brutal and avoidable.

Record your evidence before the fix lands. If the vulnerability lives on a cloud provider, you cannot go back and capture the proof once they have patched it. Get your screenshots and recordings while the behaviour is live.

And the one I keep coming back to: not every vulnerability is deeply technical. This one started with a colleague noticing something odd and me refusing to let the word "low" go. "That's weird" is a finding waiting to happen. Be curious, and be willing to fail.

References and further reading

About Modern 42

Modern 42 is an Australian cybersecurity consultancy specialising in Identity and Access Management architecture. We help organisations across banking, government, and regulated industries build identity solutions that hold up in the real world. If you want a hand pressure-testing your own Entra ID posture, book in a session with one of our engineers.

Rawson Wade

Written by

Rawson Wade

Labs Lead, Modern 42

Rory leads Modern 42's Labs team with deep expertise in Microsoft Entra ID and Azure identity platforms. He has delivered countless IAM and PAM implementations for Australian government and largest enterprise.

© Posts are provided 'as is' under the AGPL 3.0 license unless otherwise stated

Topics

  • VULN-154544
  • entra id mfa bypass
  • conditional access bypass
  • oauth client confusion
  • microsoft 365 substrate
  • low privilege scopes
  • resource exclusion conditional access
  • ropc conditional access bypass
  • microsoft graph authorization
  • MSRC disclosure

We use cookies

We use cookies and similar technologies to help personalise content, measure the performance of our site, and provide a better experience. By clicking Accept, you consent to the use of all cookies.
Learn more.