Microsoft Authenticator Android Flaw Shows Why Apps Must Verify Who Is Calling Them
A zero-permission Android app could ask Microsoft Authenticator for a work account’s tokens, say it was Teams, and get them. Mail, files, calendars, chat. The bug is CVE-2026-80097, Microsoft scores it 8.6, and it is fixed in Authenticator for Android 6.2606.4246 and later. Microsoft published the advisory on September 8. The researchers at Enclave, who call it AuthMask, put out the full write-up on October 23, and that is the part app developers should read.
Most Android teams will never build an identity broker. Plenty of them ship an exported activity, a bound service or a content provider that hands something valuable back to whoever asks. The mistake in Authenticator is one any of those can make.
How the impersonation worked
Authenticator acts as a broker for single sign-on. Microsoft apps on the phone ask it for tokens instead of signing the user in again. One of the entry points was an activity, com.microsoft.identity.client.AccountChooserActivity, marked exported="true". Any installed app could start it with startActivityForResult.
The request arrived as intent extras. The caller filled in its own package name, client ID, scopes, redirect URI and even a UID field. Authenticator then ran a check that looked strict. It took the claimed package, looked up that app’s signing certificate on the device, built the redirect URI it expected from it and compared that with the one in the request.
The trouble is that the real Teams app was installed. Its signing certificate is no secret, and another app on the same phone can look it up. So the attacker computed the same redirect URI and the check passed every time.
Authenticator did know who the real caller was. It read the calling package from the platform, then used it only for bookkeeping on parallel requests. It never compared it with the package the request claimed.
On success the activity returned access, refresh and ID tokens in plain text inside the result bundle. A request posing as Teams came back with delegated Microsoft Graph scopes to read and write mail, send mail, read and write files and edit calendars. Enclave’s proof of concept needed a work account signed into Authenticator and an attacker app with no permissions at all. Microsoft’s rating assumes some user interaction, which is the one point where the two accounts differ.
The rule it broke
Anything the caller puts in an intent is a claim. Package names, UIDs, redirect URIs, client IDs: all of it is just data the other app chose to send. Checking a claim against more data derived from the same claim proves nothing.
Android gives you the caller’s identity from outside the intent. In an activity started for a result, getCallingPackage() comes from the system. In a bound service, Binder.getCallingUid() does. Then check that the package is signed by the certificate you expect, which PackageManager.hasSigningCertificate has done since Android 9 (API 28).
// In an exported Activity that returns something sensitive
fun isTrustedCaller(
activity: Activity,
expectedPackage: String,
expectedCertSha256: ByteArray
): Boolean {
// Set by the system, and only when started with startActivityForResult
val caller = activity.callingPackage ?: return false
if (caller != expectedPackage) return false
return activity.packageManager.hasSigningCertificate(
caller, expectedCertSha256, PackageManager.CERT_INPUT_SHA256
)
}
Pin the certificate digest in your own code or config. Don’t trust a digest that came in the request, and don’t build the expected value from whatever package name the caller gave you, since that is exactly the loop Authenticator fell into. For a service, take the UID from Binder.getCallingUid() and use the overload of hasSigningCertificate that takes a UID.
What to change in your own app
Start with the manifest. List every component with exported="true" and ask what it returns or does for a caller. An activity that only shows a screen is low risk. One that returns a result, writes data, triggers a payment or hands back a token needs a real caller check, or it shouldn’t be exported.
Where the only legitimate callers are your own apps, a signature-level custom permission on the component is simpler than code checks. The system enforces it before your code runs.
Don’t return long-lived secrets through intents if you can avoid it. A refresh token in a result bundle is valuable to whoever receives it, and you’ve just seen how easy it is to be the wrong receiver.
Test it the way an attacker would. A small throwaway app that launches your exported components with forged extras takes an afternoon to write, and it will tell you more than a code review. We covered other common gaps in what mobile developers get wrong about security.
If your company runs Authenticator on managed Android phones, push the update through your MDM and confirm the version on each device. Make sure nothing older than 6.2606.4246 is still out there.