A single exported activity handed out tokens for the victim's work account.
We found that an unprivileged Android app with zero permissions could impersonate any installed Microsoft first-party app and have the Microsoft Authenticator broker hand it that app's tokens.
The setup is a device with Authenticator installed and one work account signed into it. The caller's identity was never used for any trust decision, so a single exported activity handed out tokens for the work account.
In our tests the tokens gave full delegated mailbox, files and calendar access. There was no user interaction beyond having the broker present with a signed-in work account.
Microsoft has patched the issue as CVE-2026-80097. If you use Microsoft Authenticator on Android, update it now.
An Activity Anyone Can Launch
Authenticator exposes an activity called com.microsoft.identity.client.AccountChooserActivity. It runs in the :auth process and is marked android:exported="true", which means any app on the device can launch it.
Everything in the Request Comes From the Caller
The only gate on the activity is getCallingActivity() != null. Any app that calls startActivityForResult passes it.
The activity then reads the intent extras straight into AccountChooser, and MsalAndroidBrokerCommandParameterAdapter builds the broker request from them. The caller's UID comes from caller.info.uid in the bundle. The app name, package name, client ID, redirect URI and scopes all come from the broker_request_v2 JSON. We wrote every one of them.
A Signature Check That Checks Itself
Then comes validation. BrokerInteractiveTokenCommandParameters.validate() calls:
platformUtil.isValidCallingApp(getRedirectUri(), getCallerPackageName());And isValidCallingApp does this:
String brokerRedirectUri = PackageHelper.getBrokerRedirectUri(this.mContext, claimedPackage);
return StringUtil.equalsIgnoreCase(claimedRedirect, brokerRedirectUri);It takes the package we claim to be, looks up that package's signature on the device, builds the redirect URI that package should use, and compares it to the redirect URI we sent.
That sounds like a real check. But the impersonated app is installed on the device, so its genuine signature produces the genuine redirect URI. We read it off PackageManager, built the same redirect URI, and put it in the request. The check always passes.
So Authenticator checked: Is there a calling activity? Yes. Does the redirect URI match the claimed package's signature? Yes.
It never asked: Is the app making this request actually Teams?
So We Told Authenticator We Were Teams
We launched it from an app with no permissions at all and told it we were Teams:
int uid = getPackageManager().getApplicationInfo("com.microsoft.teams", 0).uid; // read Teams' uid
String redirect = "msauth://com.microsoft.teams/" + urlEncode(sha1OfInstalledSig("com.microsoft.teams"));
JSONObject req = new JSONObject(); // broker_request_v2, all attacker-controlled
req.put("client_app_name", "com.microsoft.teams");
req.put("client_id", "1fec8e78-bce4-4aaf-ab1b-5451cc387264");
req.put("redirect_uri", redirect);
req.put("scopes", "<https://graph.microsoft.com/.default>");
req.put("authority", "<https://login.microsoftonline.com/organizations>");
req.put("username", "<victim-upn>");
Intent i = new Intent();
i.setClassName("com.azure.authenticator", "com.microsoft.identity.client.AccountChooserActivity");
i.putExtra("caller.info.uid", uid); // spoofed caller uid
i.putExtra("common.broker.protocol.version.name", "20.0");
i.putExtra("broker_request_v2", req.toString());
startActivityForResult(i, 1); // tokens come back to onActivityResultThe tokens came back.
Back Came the Tokens, in Plaintext
On success, the activity returns the broker result to whoever launched it:
intent.putExtras(bundleFromAuthenticationResult);
setResult(2004, intent);That bundle, broker_result_v2_compressed, holds access_token, refresh_token, id_token and client_info in plaintext our app could parse.
Three Disguises, Three Sets of Scopes
We impersonated three callers and got three sets of scopes.
Impersonating Teams, requesting Microsoft Graph (
graph.microsoft.com): Mail.ReadWrite, Mail.Send, Files.ReadWrite.All, Calendars.ReadWrite, ChatMessage.Send, Sites.ReadWrite.All, and moreImpersonating the Teams chat backend (
api.spaces.skype.com): user_impersonationImpersonating Authenticator's own client: UserAuthenticationMethod.ReadWrite, Passkey.Create, Passkey.Delete
Impersonating Teams and requesting Graph gives full delegated mailbox, files and calendar access. Any malicious app on the device can carry out the chain.
Authenticator Knew Who Was Calling and Didn't Use It
Authenticator did know who the real caller was. AccountChooserActivity reads getCallingActivity().getPackageName().
It just wasn't used for any trust decision. The code only used it for parallel-request bookkeeping.
So the activity had two answers to "who is asking": the real caller, and the one the caller supplied. The validation used the second.
Responsible Disclosure
We reported the issue to the Microsoft Security Response Center. Microsoft confirmed it, fixed it in Microsoft Authenticator for Android, and published CVE-2026-80097 on September 8, 2026. Versions before 6.2606.4246 are affected.
This research was completed as part of the Enclave Fellowship Program with researcher Ofek Levin.
CVE-2026-80097 Microsoft Authenticator Elevation of Privilege Vulnerability | CVSS: 8.6 (High) | CWE-287: Improper Authentication https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-80097
