Notes on Microsoft 365 and Azure Active Directory

This post is intended to be a collection of resources, techniques and knowledge around security in Microsoft 365 and Azure Active Directory. The pages heavily relies on public resources.

The following topics are discussed:

Identify Microsoft 365 subscription and tenantID

One of the first questions that comes in mind when approaching simulated attacks, is if the targret organization has a Microsoft 365 subscription. To answer this question public APIs can be leveraged. Each tenant on Microsoft 365 has a unique identifier, namely GUID (Globally Unique IDentifier). This ID is publicly available. To get this information programatically, a GET request can be made to the following URI:

https://login.microsoftonline.com/<FQDN>/.well-known/openid-configuration

Fans of linux’s curl can do:

curl https://login.microsoftonline.com/<FQDN>/.well-known/openid-configuration

The response to the request, will have a structure similar to the listed below:

{
  "token_endpoint": "https://login.microsoftonline.com/<GUID>/oauth2/token",
  "token_endpoint_auth_methods_supported": [
    "client_secret_post",
    "private_key_jwt",
    "client_secret_basic"
  ],
  "jwks_uri": "https://login.microsoftonline.com/common/discovery/keys",
  "response_modes_supported": [
    "query",
    "fragment",
    "form_post"
  ],
  "subject_types_supported": [
    "pairwise"
  ],
  "id_token_signing_alg_values_supported": [
    "RS256"
  ],
  "response_types_supported": [
    "code",
    "id_token",
    "code id_token",
    "token id_token",
    "token"
  ],
  "scopes_supported": [
    "openid"
  ],
  "issuer": "https://sts.windows.net/<GUID>/",
  "microsoft_multi_refresh_token": true,
  "authorization_endpoint": "https://login.microsoftonline.com/<GUID>/oauth2/authorize",
  "device_authorization_endpoint": "https://login.microsoftonline.com/<GUID>/oauth2/devicecode",
  "http_logout_supported": true,
  "frontchannel_logout_supported": true,
  "end_session_endpoint": "https://login.microsoftonline.com/<GUID>/oauth2/logout",
  "claims_supported": [
    "sub",
    "iss",
    "cloud_instance_name",
    "cloud_instance_host_name",
    "cloud_graph_host_name",
    "msgraph_host",
    "aud",
    "exp",
    "iat",
    "auth_time",
    "acr",
    "amr",
    "nonce",
    "email",
    "given_name",
    "family_name",
    "nickname"
  ],
  "check_session_iframe": "https://login.microsoftonline.com/<GUID>/oauth2/checksession",
  "userinfo_endpoint": "https://login.microsoftonline.com/<GUID>/openid/userinfo",
  "kerberos_endpoint": "https://login.microsoftonline.com/<GUID>/kerberos",
  "tenant_region_scope": "WW",
  "cloud_instance_name": "microsoftonline.com",
  "cloud_graph_host_name": "graph.windows.net",
  "msgraph_host": "graph.microsoft.com",
  "rbac_url": "https://pas.windows.net"
}

The PowerShell cmdlet Get-AADIntTenantID from AADInternals [1] will parse the output and print the GUID of the tenant, effectively providing the tenantID. If this cmdlet prints a GUID it can be safely concluded that the company behind the provided FQDN, has a Microsoft 365 subscription.

Identify domains registered from a tenant

A request to the URL listed below returns the domains registered from the tenant of the given domain. This information was pulled from [1].

For Threat Intelligence purposes, that means that if an attacker uses a Microsoft tenant to host phishing infrastructure (for example register phising domains), the following URL reveals additional domains. This information may become useful to attribute activity to a specific threat actor (threat actor that uses a single tenant to conduct phishing against multiple targets).

For teams that emulate attacker activities (attack simulation, red team) caution must be exercised to not use the same tenant for registering phishing domains used in different attacks or for different stages of an engagement. This will increase the operational security (OPSEC) of the engagement and contribute to the stealthiness.

https://autodiscover-s.outlook.com/autodiscover/autodiscover.svc

Fans of Linux’s curl (body.txt contains the SOAP request):

curl -i -H “Content-Type:text/xml; charset=utf-8” -H ‘SOAPAction:”http://schemas.microsoft.com/exchange/2010/Autodiscover/Autodiscover/GetFederationInformation”’ -H “User-Agent:AutodiscoverClient” -d @./body.txt “https://autodiscover-s.outlook.com/autodiscover/autodiscover.svc”

Complete request (replace <DOMAIN> with the desired domain):

POST /autodiscover/autodiscover.svc HTTP/1.1
Host: autodiscover-s.outlook.com
Accept: */*
Content-Type:text/xml; charset=utf-8
SOAPAction: "http://schemas.microsoft.com/exchange/2010/Autodiscover/Autodiscover/GetFederationInformation"
User-Agent: AutodiscoverClient

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:exm="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:ext="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
        <soap:Header>
                <a:Action soap:mustUnderstand="1">http://schemas.microsoft.com/exchange/2010/Autodiscover/Autodiscover/GetFederationInformation</a:Action>
                <a:To soap:mustUnderstand="1">https://autodiscover-s.outlook.com/autodiscover/autodiscover.svc</a:To>
                <a:ReplyTo>
                        <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
                </a:ReplyTo>
        </soap:Header>
        <soap:Body>
                <GetFederationInformationRequestMessage xmlns="http://schemas.microsoft.com/exchange/2010/Autodiscover">
                        <Request>
                                <Domain><DOMAIN></Domain>
                        </Request>
                </GetFederationInformationRequestMessage>
        </soap:Body>
</soap:Envelope>

Sample response:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:a="http://www.w3.org/2005/08/addressing">
	<s:Header>
		<a:Action s:mustUnderstand="1">http://schemas.microsoft.com/exchange/2010/Autodiscover/Autodiscover/GetFederationInformationResponse</a:Action>
		<h:ServerVersionInfo xmlns:h="http://schemas.microsoft.com/exchange/2010/Autodiscover" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
			<h:MajorVersion>15</h:MajorVersion>
			<h:MinorVersion>20</h:MinorVersion>
			<h:MajorBuildNumber>5332</h:MajorBuildNumber>
			<h:MinorBuildNumber>17</h:MinorBuildNumber>
			<h:Version>Exchange2015</h:Version>
		</h:ServerVersionInfo>
	</s:Header>
	<s:Body>
		<GetFederationInformationResponseMessage xmlns="http://schemas.microsoft.com/exchange/2010/Autodiscover">
			<Response xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
				<ErrorCode>NoError</ErrorCode><ErrorMessage/>
				<ApplicationUri>outlook.com</ApplicationUri>
				<Domains>
					<Domain><DOMAIN1>.onmicrosoft.com</Domain>
					<Domain><DOMAIN></Domain>
					<Domain><DOMAIN1>.mail.onmicrosoft.com</Domain>
				</Domains>
				<TokenIssuers>
					<TokenIssuer>
						<Endpoint>https://login.microsoftonline.com/extSTS.srf</Endpoint>
						<Uri>urn:federation:MicrosoftOnline</Uri>
					</TokenIssuer>
				</TokenIssuers>
			</Response>
		</GetFederationInformationResponseMessage>
	</s:Body>
</s:Envelope>

Enumerate user accounts on Microsoft 365

It is possible to enumerate user accounts on Microsoft O365 [1], [3] by calling the API located at:

https://login.microsoftonline.com/common/GetCredentialType

The enumeration allows an attacker to identify valid users accounts in preparation for phishing.

The API enforces throttling and therefore if a large number of requests is to be made, a tool such as Fireprox [4] could allow attackers avoid this defense.

Sample request with curl:

curl -H “Content-Type: application/json; charset=UTF-8” -d ‘{“flowToken”:””,”isCookieBannerShown”:”false”,”isRemoteNGCSupported”:”false”,”originalRequest”:””,”isOtherIdpSupported”:”true”,”checkPhones”:”true”,”username”:”<USER_ACCOUNT>@<DOMAIN>”,”isFidoSupported”:”false”}’ “https://login.microsoftonline.com/common/GetCredentialType”

Sample request:

POST /common/GetCredentialType HTTP/1.1
Host: login.microsoftonline.com
Accept: */*
Content-Type: application/json; charset=UTF-8
Content-Length: 400

{
    "flowToken":"",
    "isCookieBannerShown":"false",
    "isRemoteNGCSupported":"false",
    "originalRequest":"",
    "isOtherIdpSupported":"true",
    "checkPhones":"true",
    "username":"<USER_ACCOUNT>@<DOMAIN>",
    "isFidoSupported":"false"
}

Body of sample Response if account exists (IfExistsResult equals 0):

{
  "Username": "name.lastname@imaginary-fake-domain.com",
  "Display": "name.lastname@imaginary-fake-domain.com",
  "IfExistsResult": 0,
  "IsUnmanaged": false,
  "ThrottleStatus": 1,
  "Credentials": {
    "PrefCredential": 1,
    "HasPassword": true,
    "RemoteNgcParams": null,
    "FidoParams": null,
    "SasParams": null,
    "CertAuthParams": null,
    "GoogleParams": null,
    "FacebookParams": null
  },
  "EstsProperties": {
    "UserTenantBranding": [
      {
        "Locale": 0,
        "BannerLogo": "https://aadcdn.msauthimages.net/<REDACTED>",
        "Illustration": "https://aadcdn.msauthimages.net/<REDACTED>",
        "KeepMeSignedInDisabled": false,
        "UseTransparentLightBox": false
      }
    ],
    "DomainType": 3
  },
  "IsSignupDisallowed": true,
  "apiCanary": "<REDACTED>"
}

Notes on Active Directory Federation Services auditing

To identify the FQDN of a ADFS server (the server may be internet-facing or sitting behind a proxy):

curl -s https://login.microsoftonline.com/common/userrealm/<USERNAME>@<DOMAIN>?api-version=2.0 | jq ‘.AuthURL’

Shodan and Censys provide a broader list of ADFS servers exposed (directly or through a proxy) to the internet.

To list the available ADFS endpoints use the federation metadata endpoint [5]:

curl https://<ADFS FQDN>/adfs/services/trust/mex

If either or both endpoints /adfs/services/trust/2005/windowstransport and /adfs/services/trust/13/windowstransport are available, they leak the internal hostname of the ADFS server (not the hostname of the proxy), including the Active Directory domain name. These endpoints support NTLM-based authentication for on-premises users and Microsoft recommends they should be intranet facing only [6]. Exposing these endpoints to the internet could allow attackers bypass account lockout protections.

After identifying the ADFS server and the endpoint it exposes, to identify the internal hostname and domain name acquiring information from NTLM tools such as Shodan (a form of passive scan - a third party does the scanning) or nmap (a form of active scan - the one who scans hits the scanned infrastructure) give the answer.

To retrieve disclosed NTLM information with nmap, the script http-ntlm-info is the way to go. For example:

nmap -sS -p 443 –script http-ntlm-info –script-args http-ntlm-info.root=/adfs/services/trust/2005/windowstransport <ADFS_server>

Or, in case the endpoint used above is not available:

nmap -sS -p 443 –script http-ntlm-info –script-args http-ntlm-info.root=/adfs/services/trust/13/windowstransport <ADFS_server>

Another interesting endpoint in terms of enumeration is /adfs/ls/idpinitiatedsignon.aspx. If enabled, this endpoint lists all the relying parties and provides an additional form for authentication. The list of relying parties may be utilised for spear phishing (targeted attacks) as in inspire lures related to relying parties.

Hybrid Environments

In hybrid environments authentication for valid accounts may not redirect to the ADFS server. If that’s the case, the following request returns null.

curl -s https://login.microsoftonline.com/common/userrealm/<USERNAME>@<DOMAIN>?api-version=2.0 | jq ‘.AuthURL’

To retrieve the ADFS FQDN repeat the request with an invalid email address.

Deliver emails to Office 365 bypassing third-party mail filtering

This section provides a methodology on how to bypass third-party mail filtering (if this is possible) to deliver an email to the Office 365 of tenant, leveraging publicly available information.

Organizations that migrate their email infrastructure to Office 365 often add an additional security layer by sending first the emails to a third-party mail filtering service (such as Proofpoint). However, due to misconfigurations it may still be possible to send an email directly to the tenant effectively bypassing the third-party filtering.

First things first. To identify what email fitlering service is used, first look at the MX record of a domain. Assuming Proofpoint services are used to filter incoming emails for the imaginary domain imaginary-prd-domain.com, the MX DNS record for imaginary-prd-domain.com should like:

;; ANSWER SECTION:
imaginary-prd-domain.com.   1800   IN   MX   10 <SUBDOMAIN1>.pphosted.com.
imaginary-prd-domain.com.   1800   IN   MX   10 <SUBDOMAIN2>.pphosted.com.

Next step is to identify if the imaginary domain is linked to Microsoft cloud infrastructure. For this, the cmdlet Get-AADIntTenantID [1] provides the answer.

Get-AADIntTenantID -Domain imaginary-prd-domain.com

After confirming that the domain is linked to a tenant, next is to identify all the domains that are registered from that tenant. If the MX record for the domain formatted as .onmicrosoft.com points to the domain formatted as *.mail.protection.outlook.com that indicates that Office 365 is used for emails. The list of registered domains under the tenant, is:

Get-AADIntTenantDomains -Domain imaginary-prd-domain.com

Run through the list of domains the above cmdlet returns and look for .onmicrosoft.com. Assuming this is imaginary-prd-domain.onmicrosoft.com, the next step is to check the MX record for that domain:

dig mx imaginary-prd-domain.onmicrosoft.com

If the result includes the domain mail.protection.outlook.com that means Office 365 is used for email delivery. Therefore, if the mail filtering is not configured properly, it may be possible to send emails directly to <DOMAIN>.mail.protection.outlook.com.

The next step in the process is to identify the address to send the email. To find this information, traverse the MX record of the <DOMAIN>.mail.onmicrosoft.com domain. This is in the form:

<DOMAIN>.mail.protection.outlook.com

The IP address the domain listed above resolves to is the destination address.

The information posted in this section was acquired from [2].

#email

Microsoft Exchange Online legacy protocols MFA check

When the user agent BAV2ROPC is send along with an authentication request to Microsoft Exchange Online, the ROPC OAuth [9] flow is initiated. If invalid_grant is returned, this is indicative that MFA is enabled.

Attackers have used this technique to identify if MFA is enabled [8] and avoid other services that may be sitting behind MFA. For an attacker with valid credential this could lead to Business Email Compromise (BEC).

The request to check this:

POST {tenant}/oauth2/v2.0/token
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
User-Agent: BAV2ROPC

client_id=6731de76-14a6-49ae-97bc-6eba6914391e&scope=user.read%20openid%20profile%20offline_access&username=MyUsername@imaginaryclientname.com&password=SuperS3cret&grant_type=password

References

[1] https://o365blog.com/

[2] https://practical365.com/how-to-ensure-your-third-party-filtering-gateway-is-secure/

[3] https://warroom.rsmus.com/enumerating-emails-via-office-com/

[4] https://github.com/ustayready/fireprox

[5] https://docs.microsoft.com/en-us/windows-server/identity/ad-fs/troubleshooting/ad-fs-tshoot-endpoints

[6] https://docs.microsoft.com/en-us/windows-server/identity/ad-fs/deployment/best-practices-securing-ad-fs#disable-ws-trust-windows-endpoints-on-the-proxy-ie-from-extranet

[7] https://docs.microsoft.com/en-us/windows-server/identity/ad-fs/troubleshooting/ad-fs-tshoot-initiatedsignon

[8] https://www.microsoft.com/security/blog/2021/06/14/behind-the-scenes-of-business-email-compromise-using-cross-domain-threat-data-to-disrupt-a-large-bec-infrastructure/

[9] https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth-ropc


tags: #Microsoft 365 and Azure Active Directory