Full Disclosure, GraphGhost: Are You Afraid of Failed Logins?

Table of contents
Another year, another vuln…It's that time again.
Last year I disclosed the existence of GraphNinja - a (now fixed) vulnerability in Azure where you could password spray and remain undetected. Well, the Ghost of GraphNinja is here: another logging failure in Entra ID that could be abused by attackers. And, since Microsoft has fixed it, I'm allowed to tell you about it.
TL;DR: There was a logging/reporting problem in Azure where attackers could determine if a password was valid, while showing as a 'failed' login in the logs. It's fixed now.
Sorry, I know - no new hawtness. I hate these types of posts too, but I think it's important for people to know that it existed, because mum's the word from Microsoft.
Background
Entra ID (previously Azure AD) is the identity portion of Azure. It's really at the core of all of Azure, managing users, apps, and permissions. Sign-in logs are generated by users and applications when they connect to Azure services. As an Azure admin, these logs are your only source of information.
While the cloud simplifies various aspects of IT management, it does so at a cost. As an Azure admin, you are entirely dependent upon the logs provided to you by your cloud provider. You get what you are given. So, what happens when the cloud provider makes a mistake in their logic? You are deprived of logs, or have erroneous logs. I'm not sure which is worse, not knowing or being told the wrong answer.
This type of logging logic has happened at least once before. GraphNinja, a complete logging bypass, had existed in Azure, potentially for years. Here is my blog post on it. This was a case of bad back-end logic. It worked like this: when authenticating to Azure via Graph, if you pointed the authentication to another tenant instead of the victim's tenant or the common endpoint, the auth attempt would not be logged to any tenant and would not show up in the Sign-in logs.
The GraphNinja bypass was simple. Change this:

To this (using any GUID other than the tenant you're attacking):

And you had an invisible password spray. Record of your attack would never show up in any Sign-in logs. This has been fixed, and it now shows the failed authentication in the target tenant, wherever it is aimed.
Today I'm going to share GraphGhost, which is similar to GraphNinja, except instead of being invisible, an attacker could identify a valid password while still reporting a failure in the logs.
How it Works
When you make a logon attempt to Graph, you supply a number of fields, depending on what method of authentication you're using. These fields might include:
- Username
- Password
- Resource ID
- Client ID
Different authentication methods have different specific requirements. If you submit the correct values when you make your authentication request, you'll get a token. If you submit incorrect values, you'll receive some type of error message.
And this is where Microsoft's love of verbose error messages comes back YET AGAIN to make life difficult for defenders and to assist hackers.
You see - there is an order of operations in how Microsoft determines if a logon is valid. The general order of operations is something like this:
- Does the target Tenant exist?
- Does the target Domain exist?
- Does the User (UPN) exist?
- Is the password valid?
That's the extent of what most people will encounter in their daily life. Either they have a successful login and go about their business, or the failed login due to a bad username or password.
However, what about the steps AFTER the password validation? What gets checked there?
Well, it turns out, EVERYTHING you submit other than the username and password gets checked AFTER passing password validation. Once it knows you're a valid user with a valid password, it then checks to see if you have a valid Client ID. Then it might check to see if the UPN is in the right tenant, or if the Resource that you're requesting is valid. Below is a flowchart showing the checks that I've mapped out.

The Problem
If any of the post-password checks fail, the entire login is counted as a failure. After all, the login was unsuccessful if it was missing some key information. However, because we know the order of operations, we know which error codes will only appear AFTER the password has been validated.
Here's that flowchart again, but with the post-password checks delineated:

Any error codes below the red line would indicate a valid password had been found.
To make the request is simple, here are two examples of curl requests that perform the logon attempt:

Here's what that failure would look like in the logs:


Or, another example:

When any of the failures occurred AFTER authentication passed, it would indicate a 'failure' status for the logon. Even if you go to the Authentication Details tab, it gives no indication.

Notice how the Succeeded column for the Password authentication indicates false. If you were an admin reviewing this log, there is no indicator of a compromised credential.
Now - I want to be clear - this only lets the attacker know that the password was valid. It did not result in a valid token. The idea with this type of attack is that once an account's password is known to an attacker, they could then be more surgical in the application of that credential. For instance, you could try to log in via a VPN in your target's area or during normal business hours. An attacker could also target Wi-Fi using corporate credentials of the victim, avoiding MFA altogether. At any rate, it's not good for an admin to remain blind to when a password has been disclosed to an attacker.
The Fix
I had really hoped that Microsoft would take this opportunity to reflect upon their usage of verbose error codes. Who do they help? Any admin can already see the actual logs and the error codes being generated. The only people who benefit from these verbose error codes are attackers.
Yet sadly, that is not the route they took.

As of April 11, 2025, this issue has been resolved. In the end, Microsoft simply updated the Authentication Details to indicate that the password had been successful. A small, but critical difference.

Timeline
December 17, 2024 - Reported to Microsoft
December 17, 2024 - Case opened by Microsoft
February 21, 2025 - Confirmation and Bounty awarded
April 11, 2025 - Fixed by Microsoft
Thoughts
As stated earlier, I really hoped that Microsoft would use this as an opportunity to push for better security overall. This issue wasn't something that was hard to find. I'm puzzled how this GraphGhost and the earlier GraphNinja logging issues weren't identified by Microsoft's own security teams. As with GraphNinja, Microsoft has made no mention of this publicly. Since Microsoft is their own CVE Numbering Authority (CNA), they get to decide what is a vulnerability, and they get to decide what the public should know.