Enumerate valid usernames from Office 365 using ActiveSync, Autodiscover, or office.com login page.
Enumeration Methods
ActiveSync Enumeration
This method is based on grimhacker’s method that sends Basic HTTP authentication requests to ActiveSync endpoint. However, checking the status code no longer works given that Office365 returns a 401 whether the user exists or not.
Instead, we send the same request but check for a custom HTTP response header (X-MailboxGuid) presence to identify whether a username is valid or not.
Existing Account
The request below contains the following Base64 encoded credentials in the Authorization header: valid_user@contoso.com:Password1
This elicits the following response (“401 Unauthorized”) with the X-MailboxGuid header set, indicating that the username is valid but the password is not:
Date: Fri, 31 Jan 2020 13:02:46 GMT
Connection: close
HTTP/1.1 401 Unauthorized
Content-Length: 1293
Content-Type: text/html
Server: Microsoft-IIS/10.0
request-id: d494a4bc-3867-436a-93ef-737f9e0522eb
X-CalculatedBETarget: AM0PR09MB2882.eurprd09.prod.outlook.com
X-BackEndHttpStatus: 401
X-RUM-Validated: 1
X-MailboxGuid: aadaf467-cd08-4a23-909b-9702eca5b845 <--- This header leaks the account status (existing)
X-DiagInfo: AM0PR09MB2882
X-BEServer: AM0PR09MB2882
X-Proxy-RoutingCorrectness: 1
X-Proxy-BackendServerStatus: 401
X-Powered-By: ASP.NET
X-FEServer: AM0PR06CA0096
WWW-Authenticate: Basic Realm="",Negotiate
Date: Fri, 31 Jan 2020 13:02:46 GMT
Connection: close
--snip--
Nonexistent Account
The request below contains the following Base64 encoded credentials in the Authorization header: invalid_user@contoso.com:Password1
The autodiscover endpoint allows for user enumeration without an authentication attempt. The endpoint returns a 200 status code if the user exists and a 302, if the user does not, exists (unless the redirection is made to an on-premise Exchange server).
Existing User
GET /autodiscover/autodiscover.json/v1.0/existing@contoso.com?Protocol=Autodiscoverv1 HTTP/1.1
Host: outlook.office365.com
User-Agent: Microsoft Office/16.0 (Windows NT 10.0; Microsoft Outlook 16.0.12026; Pro
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
MS-ASProtocolVersion: 14.0
GET /autodiscover/autodiscover.json/v1.0/nonexistent@contoso.com?Protocol=Autodiscoverv1 HTTP/1.1
Host: outlook.office365.com
User-Agent: Microsoft Office/16.0 (Windows NT 10.0; Microsoft Outlook 16.0.12026; Pro
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
MS-ASProtocolVersion: 14.0
<html><head><title>Object moved</title></head><body>
<h2>Object moved to <a href="https://outlook.office365.com/autodiscover/autodiscover.json?Email=nonexistent%40contoso.com&Protocol=Autodiscoverv1&RedirectCount=1">here</a>.</h2>
</body></html>
Office.com Enumeration
WARNING: This method only works for an organization that is subscribers of Exchange Online and that do not have on-premise or hybrid deployment of Exchange server.
For companies that use on-premise Exchange servers or some hybrid deployment and based on some configuration, I haven’t identified yet, the server might return a value indicating the username exists for any username value.
The method is useful when you don’t want to burn an authentication attempt with ‘Password1’ 🙂
Existing User
When the account does not exist, IfExistsResult is set to 0.
o365enum will read usernames from the file provided as the first parameter. The file should have one username per line. The output is CSV-based for easier parsing. Valid status can be 0 (invalid user), 1 (valid user), 2 (valid user and valid password).