Entra ID Actor Token - Undocumented S2S Token → Unsigned Impersonation JWT → Legacy Graph Skips the Tenant Check → Global Admin in Any Tenant
In July 2025 the security researcher Dirk-jan Mollema found that an undocumented Microsoft service-to-service credential called an Actor token, requested from a tenant he controlled, would be accepted by the legacy Azure AD Graph API in any other tenant, because that API never checked which tenant the token came from. The result was impersonation of any user in any Entra ID tenant on Earth, up to and including Global Administrator. CVE-2025-55241 was scored CVSS 10.0. This one belongs on this page for a different reason than the rest: it is not a breach. It was responsibly disclosed, Microsoft shipped a global fix three days after the report, and Microsoft's telemetry found no evidence anyone exploited it beforehand. It earns its place because of what it demonstrates. Every control this site recommends - MFA, Conditional Access, device compliance, least privilege - sat entirely outside this path, and so did nearly all of the logging. There was no customer-side configuration that mitigated it, in any tenant, at any price tier.
The research started somewhere unglamorous: hybrid Exchange. Microsoft's own back-end services need to act on a user's behalf when talking to Azure AD Graph, SharePoint, and Exchange Online, and the mechanism they use is an Actor token issued by the Access Control Service. Read the properties together and the risk is obvious in hindsight. Twenty-four hour validity. A trustedfordelegation claim marking the holder as allowed to act as anyone. No audit log when one is issued. No way to revoke one inside its validity window. And explicitly outside Conditional Access, because Conditional Access evaluates user sign-ins and this is not one. Each property is defensible in isolation as an internal service mechanism. Together they describe a bearer credential with no issuance record, no revocation, and no policy engine in front of it.
Purpose: Service-to-service calls where one Microsoft service acts on a user's behalf
Validity: 24 hours, and not revocable within that window
Key claim:
trustedfordelegation, set true for Microsoft first-party appsTelemetry on issuance: None
This is the step that sets the severity. The attacker does not need a foothold anywhere near the target. They need a tenant, and anyone can create one in minutes for nothing. The token is requested legitimately in the requester's own tenant, where they are already the administrator and entitled to it, and no unusual event is generated on either side because nothing unusual has happened yet. Everything that follows is a matter of what the requester then puts inside the next token, and which API is willing to read it without asking where it came from.
Relationship to target: None required - no guest account, no federation, no prior access
Cost: Effectively zero
Signal generated in the attacker's tenant: A legitimate token request by its own administrator
To impersonate a user, a Microsoft service builds a second JWT that contains the Actor token, the target tenant ID, and the target user's identifier, and it does not sign that outer token. The trust is meant to come from the embedded Actor token rather than from a signature over the whole thing. So the researcher edited it. Changing the tenant GUID to a different organization and the nameid to a different user produced a token he expected to be rejected instantly for the obvious tenant mismatch. Sending it to graph.windows.net returned a valid session instead. The UPN fields in the token turned out to be decorative; Azure AD Graph ignored them and keyed entirely off nameid.
actortoken fieldAttacker-editable fields: Target tenant GUID, target user
nameidIgnored by the API: UPN values, which could be dummies
Expected result: Access denied on tenant mismatch
Actual result: A working session as the impersonated user
The whole vulnerability reduces to one missing comparison. graph.windows.net, the deprecated predecessor to Microsoft Graph, checked that the nameid corresponded to a real user and then served the request. It never asked whether the Actor token authorizing that impersonation had been issued in the same tenant as the user being impersonated. A single boundary check, absent from an API old enough that most of its consumers had already been migrated away, converted a legitimate internal delegation mechanism into cross-tenant impersonation for the entire service. Legacy surfaces are dangerous precisely because they are quiet: nobody is adding features, so nobody is re-reviewing the assumptions, and the traffic is low enough that anomalies do not stand out.
graph.windows.net (the deprecated pre-Microsoft-Graph API)What it validated: That
nameid mapped to a real userWhat it never validated: That the Actor token came from the tenant being accessed
Blast radius: Every Entra ID tenant, since the flaw was in the shared service
CVE: CVE-2025-55241, CVSS 10.0
The one thing standing between an attacker and a given tenant was knowing a user's internal netId, and it turned out not to stand for long. Because netIds increment rather than being randomly assigned, they can be walked systematically, reducing "find a valid user in this tenant" to minutes or hours of automation. If the attacker had ever invited someone as a B2B guest, the guest object stored the home tenant user's netId in its alternativeSecurityIds attribute, readable by whoever issued the invitation. And any token that had ever leaked - expired, redacted in a bug report, pasted into a support ticket - carried a puid claim that yields a working netId. Values that are not secret, because nobody designed them to be secret, become the last remaining control the moment the boundary above them fails.
Method 2: B2B guest objects store the home tenant netId in
alternativeSecurityIds, readable by the inviterMethod 3: Any leaked or expired token with a
puid claim yields a usable netIdDesign assumption broken: That an internal identifier does not need to be treated as sensitive
With a Global Administrator impersonated, the attacker holds the tenant: directory contents, group and role membership, application and service principal configuration, and the ability to grant themselves durable access through more conventional means. What makes this different from every other identity chain on this page is that no customer decision affected the outcome. Conditional Access never evaluated the request because there was no user sign-in to evaluate. MFA was never prompted because no authentication occurred at the user layer. Device compliance, sign-in risk policies, named locations, token protection - none of them sat on this path. As the researcher put it plainly, there was no setting that could have mitigated this, including for tenants that had done everything right.
Conditional Access: Not evaluated - no user sign-in occurs
MFA: Never prompted - no user-layer authentication
Hardened tenants: Equally affected; no configuration mitigated it
Duration of a stolen token's usefulness: 24 hours, un-revocable
Read the telemetry picture as a whole and the honest conclusion is that a victim organization could not have answered the question "were we accessed." Issuing the Actor token: no log. Crafting the impersonation token: no log. Reading the entire directory through Azure AD Graph: no log. Only write operations landed in the audit log, and even those arrived confusingly, recording the impersonated Global Administrator's UPN alongside the display name of Exchange - an artifact pattern that reads as a Microsoft service doing something odd rather than as an intrusion. This is the same failure that made Storm-0558 so painful two years earlier, and it is worth naming the general form: when authentication material is forged rather than stolen, logs are the only remaining detection, and a design that does not log issuance has removed the last one.
Impersonation token creation: No log
Directory reads via Azure AD Graph: No log
Writes: Logged, but showing the impersonated user's UPN with Exchange's display name
Practical consequence: A victim could not determine whether they had been accessed
The disclosure timeline is the encouraging part of this entry, and worth recording accurately. Mollema reported to MSRC on July 14, 2025. Microsoft deployed a production fix globally on July 17, three days later. On August 6 it shipped a second mitigation preventing applications from obtaining Actor tokens for Azure AD Graph using service principal credentials, narrowing the mechanism to Microsoft's own internal services. CVE-2025-55241 was assigned September 4, and the research was published September 17. Microsoft's internal telemetry found no evidence of exploitation before the fix. Nothing here required customer action, which is the point: this was fixed on the provider's side because it could only be fixed on the provider's side.
July 17, 2025: Production fix deployed globally - tenant validation restored
August 6, 2025: Applications blocked from requesting Actor tokens for Azure AD Graph
September 4, 2025: CVE-2025-55241 assigned, CVSS 10.0
September 17, 2025: Public disclosure
🛡 How to Defend Against This Chain
graph.windows.net, migrate them, and then block the legacy endpoint where you can. Every deprecated-but-reachable interface in your estate is carrying assumptions nobody has re-reviewed in years, and this is what that costs.nameid values are not the ones that appear in every guest invitation and shared document.