On a recent penetration test, I discovered that manually attempting to log into Office.com would give an indication as to whether an email address exists or not. Both of the techniques I was familiar with for Office365 username enumeration, using the Autodiscover API and ActiveSync, have both been fixed so this was definitely something worth exploring.
I captured a few different requests and responses on the login page, some with valid emails and some with non-existent emails, and I made a few observations about the data Microsoft returns. There was only one field that was changing in the responses, “IfExistsResult”, that was always ‘1’ if the email didn’t exist but it came back with 0, 5, or 6 for emails that did exist. However, it was hard to narrow down what these responses actually meant. I reached out to a coworker, @KnaveSec, to see if he could reproduce the results of my testing, and he said that every email he tried from a test domain returned a 0, regardless of whether the account actually existed.
I dug into the domain configurations using this nifty link and found that our company’s domain was federated, and that the client I was testing had a managed domain. After a bit of research on what these mean, the results started to make sense.
Federated vs Managed Domains
A managed domain is, as the name implies, managed by Microsoft. Microsoft processes the authentication attempt, checks the credentials in Azure Active Directory, and allows those who successfully authenticate to access Office365. When a user enters their email on the Office.com login page for a managed domain, Microsoft is able to look up whether the user exists because they manage the authentication for that domain.
A federated domain means that rather than using Microsoft to process the authentication, the domain has its own Active Directory instance which it uses to process the authentication request. In this case, when a user enters an email address for a federated domain, Microsoft has no way of knowing whether the user exists because they don’t touch the authentication process. Rather, the page looks up the authentication URL for that domain and redirects the user to the page.
Knowing that this technique would work on managed domains, I forked a script that was written to take advantage of this fact, added changes to consider values for the IfExistsResult field other than just 0 or 1, and ran it against the emails I had enumerated from OSINT. I then ran it against emails I generated from the statistically likely usernames repo to see if I could find any additional emails. After receiving several dozen ‘valid’ responses, I figured my requests were being throttled. I checked the response data again to see if I could find anything different and noticed a field called ThrottleStatus, which conveniently enough, seemed to indicate whether the requests were being throttled. I also noted a field called DomainType, which may indicate whether the domain is managed.
I dug into the page’s source code looking for these names because I figured there had to be logic built into the page to handle these different status codes. I found each of them in there, along with a map for each field that described what each code indicates. I’ll present the raw code as well as my understanding of the translation:
IfExistsResult={Unknown:-1,Exists:0,NotExist:1,Throttled:2,Error:4,ExistsInOtherMicrosoftIDP:5,ExistsInBothIDPs:6}
- -1 An unknown error
- 0 The account exists, and uses that domain for authentication
- 1 The account doesn’t exist
- 2 The response is being throttled
- 4 Some server error
- 5 The account exists, but is set up to authenticate with a different identity provider. This could indicate the account is only used as a personal account
- 6 The account exists, and is set up to use both the domain and a different identity provider
In short, my initial observation that 0, 5, and 6 indicate a valid account was accurate. 5 might get us in a bit of trouble during a penetration test though as it could indicate that the email is being used for a personal account, so we definitely want to be aware of that.
ThrottleStatus={NotThrottled:0,AadThrottled:1,MsaThrottled:2}
- 0 No throttle
- 1 The Azure domain is throttling the response
- 2 The Microsoft account throttling the response
I can’t think of a reason why we would care about the type of throttling, but the field certainly indicates whether or not throttling is happening.
DomainType={Unknown:1,Consumer:2,Managed:3,Federated:4,CloudFederated:5}
- 1 Unknown type
- 2 Consumer domain, something like outlook.com or hotmail.com
- 3 Managed domain
- 4 Federated domain
- 5 Federated domain hosted in the cloud
While it’s interesting that you can tell whether a domain is hosted on-prem or in the cloud based on the response, it’s not particularly relevant to this technique. All we care about is whether the domain is managed or not.
The Tool
Building off the original script, I incorporated additional logic for each of these fields. Essentially, it just sends the username to the same API endpoint that the login page does and then processes the response. There’s some nifty code in there that the author of the original repo wrote to emulate the process of a person clicking through the site to make the requests seem more organic which could potentially help with throttling or being detected by request signature. Other than that, it just comes down to checking the domain type, whether the responses are being throttled, and of course, whether the user exists or not. You can find the tool here, all it needs to run is a file with the emails you want to test, and it will let you know whether the email exists or not and whether the domain you’re checking against is valid for this technique.