Advisories 1-2: Azure AD and Common WS-Trust MFA Bypass explained

If there is single post I’ve been delaying for a good while, then it’s gotta be this one. This blog details a common oversight in MFA enforcement regarding federation implementations where MFA is invoked and required in the 3rd party IDP only. This oversight become effective when several by- design features, and implementation decisions align in ”wrong” way

Well how actionable it is? I’ve been exploiting this oversight in Red Teaming assignments, so in my experience its been really popular in deployments where the MFA is not checked on cloud side.

The reason of the delay is advisory 1. I’ve been discussing the ramifications with a major vendor providing Federation solutions about mitigating the configuration risks with WS-Trust when used in conjunction with Azure AD

Advisory 1 is related to how WS-Trust protocol is used in conjunction with Azure AD where clear text username and password are forwarded in TLS protected transport using the active endpoints to provide access for so-called non modern clients (Rich /Active endpoint consuming clients)

The scope is protocol implementation related, not vendor related! List of third 3rd party vendors providing WS-Federation/Trust support for Azure AD is long (some 15 last time I checked), so I am not highlighting any vendors here. I am assuming this affects most of the vendors implementing the WS-Trust with Azure AD without knowledge of the Azure AD OAuth2 Grant Types. Obviously I can’t say anything on any of the vendors behalf.

Microsoft’s AD FS can be also considered applicable in this context. Especially advisory 2

Advisory 2 is already documented, but not well communicated, and the existing documentation is AD FS specific. How is advisory 2 related to advisory 1? The existence of advisory 2 makes advisory 1 possible in the described scope.

Scope

Scope of this advisory are primarily customers who use WS /* -Protocols for federated domains in Azure AD, and utilize access policies to enforce and bypass MFA only in the IDP side. Not checking the status of MFA in Conditional Access, or using the -SupportsMFA option for the Microsoft MFA enabled users.

Its worth noting, that the attack can also pierce cloud MFA requirement if EAS clients can generate known bypass claims. There are unfortunately guides on web that advise doing this. In the latter scenario the attack penetrates two MFA layers, (Becomes even worse)

I won’t be providing any PoC’s, since all of the below advisories rely on by-design implementation, and can be replicated in few minutes, given you have understanding of both sides of the equation (Azure AD and WS-protocols)

Advisory 1 – Pivoting from legacy to modern auth

Background

UserName endpoints on WS-Trust are usually associated with legacy clients, thus they are often excluded from MFA. These endpoints are usually associated with legacy clients requesting access to Azure AD relying Party (Office 365 services often) – This is only true by association, as WS-Specification doesn’t define any difference between the so-called modern and legacy clients, the concept of difference only exists within Azure AD /Office 365 context, which the federation service is not aware of.

Accessing ”modern” API’s unprotected by the IDP

SAML assertion can extracted from the IDP, if the IDP is thinking it’s ActiveSync client. Once the token is received in response, it is then converted to OAuth2 Access Token with “saml1_1-bearer” OAuth2 Grant. This can be done by using many of the existing public clients.

Effectively the SAML token destined to legacy endpoint in Azure AD is converted to “Modern Auth” OAuth2 Access Token, and not being just used for legacy protocols anymore.

Root of this problem is related to the fact, that for SAML response from WS-Trust endpoint there is no ”validFor” context, which would dictate, that this request is valid for only let’s say https://graph.microsoft.com – not entire Azure AD Federation (Conditional Access solves this issue, as it binds access in cloud to different API’s)

In the picture: MFA is enabled for browser clients, and UserNameEndpoint is only allowed for ActiveSync clients without MFA /There lies the problem…

But wait, I have disabled legacy protocols in Office 365?

Disabling legacy protocols doesn’t help in this case, as the attacker is only consuming “modern API’s” (Microsoft OAuth2 parlance).

Attack in practice – On-prem MFA bypass

Example: The deployment only allows native ActiveSync to access WS-Trust’s Username bindings without MFA

What attacker does: Attacker uses ‘X-MS-Application’ header to spoof client to fetch SAML token using WS-Trust’s ’UserNameWSTrustBinding’.

The attacker then exchanges the SAML token in Azure AD to access the OAuth2 SPN (Audience). The exchange of the token is possible due to explicit support of “saml1_1-bearer” oAuth2 Grant Type, which cant be disabled on any of the ServicePrincipals in AzureAD

Documentation regarding the flow type

Link Microsoft identity platform and OAuth 2.0 SAML bearer assertion flow

Advisory 2 – Client Header Based Access Policies (Applies to WS-Federate as well)

This advisory is well documented, and is here only as reminder because it allows the first advisory to exist.

Don’t use user spoof-able headers as an basis for access policies at IDP’s controlling access to Azure AD.

Access Policies

Some vendors might implement additional checks here, but the protocol itself doesn’t dictate those to be done. This is basically where different products provide access policies to be enforced claims pipeline, to produce authorization result.

AD FS roles
https://docs.microsoft.com/en-us/windows-server/identity/ad-fs/technical-reference/the-role-of-the-claims-pipeline

Business policy claims?

Some of the claims presented in the following category should be only implemented to support business policies, not security policies, as advised in the Microsoft’s article

Client Spoof-able headers (Examples from AD FS)

Multiple claims exist which clients can modify, or set. Below are the most common ones. Read the article if you’re curious to see the list of claims in AD FS

  • X-MS-FORWARDED-CLIENT-IP
https://docs.microsoft.com/en-us/windows-server/identity/ad-fs/operations/access-control-policies-w2k12#x-ms-forwarded-client-ip
  • X-MS-CLIENT-APPLICATION
https://docs.microsoft.com/en-us/windows-server/identity/ad-fs/operations/access-control-policies-w2k12#x-ms-client-application
https://docs.microsoft.com/en-us/windows-server/identity/ad-fs/operations/access-control-policies-w2k12#scenario3

The documentation exist for AD FS 2012 R2, but it’s applicable to similar implementations including subsequent AD FS versions at least as up to 2016. Be sure to read the article as it lists very clear reasons,

Mitigations

  • a. Use Conditional Access to check whether MFA was actually performed at the 3rd party IDP for the requested API, or if there is valid reason to explicitly bypass the API
  • b. If you don’t have Conditional Access, don’t allow UserNameMixed to be used with spoof-able headers. Basically you wouldn’t allow native clients anymore for ActiveSync, as there isn’t safe header available for this decision to be made in the authorization pipeline

About spoof-able headers

  • X-MS-ADFS-Proxy-Client-IP seems to be based on the actual client IP with WAP, and should be more reliable, than the client writable header X-MS-FORWARDED-CLIENT-IP, which is advised to be used in many scenarios.
  • X-MS-FORWARDED-CLIENT-IP is often advised to be used in many guides because MS Exchange Online Connections pass multiple IP’s in headers, and thus the actual client IP hitting ADFS includes both Exchange and the Client IP’s in the header
    • as the documentation says, is best effort style header, and shouldn’t be used outside of business logic use scenarios
  • X-MS-CLIENT-APPLICATION here isn’t reliable way to inspect ActiveSync header for a legitimate client at the IDP’s side, The only way to allow it without MFA is Conditional Access

Also its worth noting that Basic Auth for Exchange, which is key reason why MFA bypasses on the WS-Trust endpoint are still used is going to be deprecated very soon

Azure AD – Add Custom claims for WS-Federation applications

Disclaimer: The information in this weblog is provided “AS IS” with no warranties and confers no rights.

Most guides using Azure AD as IDP focus at OAuth and OAuth2/w OIDC flows for API access, and for enterprise SSO SAML. Not much endorsement for WS-Federation, and that’s understandable because the two previous options cover pretty much every scenario you would ideally have. But what if you have a app that is using explicitly WS-Federation? For example Microsoft OWIN based application?

There aren’t good support articles on using WS-Federation and custom claims in Azure AD when Azure AD is IDP, but WS-Federation is definitely supported when Azure AD is acting as IDP .

The only article I found covering custom claims and mentioning WS-Federation was the one I’ve used to write my previous article on preview of custom claims (how to get sAMAccountName into JWT tokens)

Since Azure AD App’s are largely protocol agnostic I figured that maybe the claims mappings are somewhat ambiguous also, just as long you understand how custom claims, and federations work

Guide to testing

The order here is important. While SAML endpoint is enabled by default in any Azure AD App, any specific settings, especially one’s for configuring Service Provider aren’t directly available in the ”non-SAML” app you create – Unless you play around with the App Registration Manifest. But there are other features that require spawning app initially as SAML app (For example using custom token sign-in cert, using token encryption and so on)

  • Configure new SAML federation at Azure AD
  • Add the custom claims for the federation
  • Set Single-Sign On mode to disabled
    • If you’re planning to use this in production, you have get the Metadata from SAML federation settings, I will do some further investigation later to update this blog
  • For metadata replace the metadata URL with tenant and AppID details

Test the federation.

I have handy NodeJS Express App I use pretty much for testing and debugging any authorization related stuff, with very simple purpose of catching different inbound calls.

About the code… The code itself is epitome of laziness, so I am too embarrassed to share it :)…

  • Invoke the call
    • Use AppID or any Identifier you’ve defined in the manifest
    • If you have multiple reply URL’s define the ’wreply’ param
https://login.microsoftonline.com/common/wsfed?
 wtrealm=1de8c976-fe00-4008-91d5-e5a2381d40a6
 &wctx=WSfedState
 &wa=wsignin1.0
  • Confirm the emitted claims
    • In this particular case its user.displayname +(join) user.dnsdomain
Typical WS-Federation body

There are many questions and details that I could add here, but since I am just fooling around with a feature, I am not devoting any more time until there is actual production need for such scenario.

LAB: Microsoft Defender ATP and Conditional Access

The integration between Intune and Microsoft Defender Advanced Threat Protection (MDATP) has been there for a while now. It’s an interesting feature, as it allows the risk score assigned by MDATP to be utilized in CA policies. Most organizations I’ve worked with only use Intune for MDM and MAM and still use SCCM (or the like) for managing workstations. While Intune’s capabilities in workstation management are still limited, it’s constantly evolving – and with SCCM co-management supported, it’s becoming a very viable option to enroll also your workstations into Intune. In any case, I wanted to do a quick experiment on how the MDATP -> Intune -> CA integration works, and how quickly we can go from detecting an alert in MDATP into actually making access restrictions in Azure AD based on the incident.

In all its simplicity, the environment is depicted in the picture below. The laptop in this case is just a VM running in Azure but it will do the trick. The Windows 10 is directly joined to Azure AD (this would work equally well with hybrid scenarios, but I got lazy setting up the local infrastructure). The device will be enrolled into Intune and into MDATP (using Intune).

Setup: Intune + MDATP + CA

To begin with, you have to integrate Intune with MDATP. You’ll find the setting within the Intune management blade:

As well as in the advanced settings of the MDATP portal:

Once the tenant level integration is done, you need to create a device configuration profile for MDATP:

The next thing you need is a device compliance policy. This will determine at which MDATP risk level the device will be marked as non-compliant. In my case, I’ve set it to ”Low”, which means that any risk level higher than that will push the device out of compliance. We’ll see how the risk levels are shown in MDATP later.

On the compliance policy settings we want to make sure that devices with no compliancy status will be marked non-compliant:

And finally, you should set up a security baseline for MDATP. Although not strictly required for this experiment, it gives you a good idea which MDATP features you can centrally manage via Intune. If you’re running your Windows 10 remotely, be careful with the firewall configurations. The default settings in the baseline will make you lose RDP connectivity (been there…).

Finally, we’ll create a couple of conditional access policies. For testing purposes, I’ll create two policies:

  1. Access to SharePoint Online will require either a compliant device or MFA
  2. Access to Exchange Online will require both a compliant device and MFA (picture below)

Enroll!

Now we’re good to go! I start by joining the Windows 10 client to Azure AD, which will also automatically enroll it into Intune (as I have enabled the enrollment policy). After that, I’ll login with an Azure AD account and run the following command. This should generate an informational alert in MDATP, just to show that we’re getting the data.

The information in the MDATP portal lags a few minutes behind, you have to wait for a bit for the information to appear. But eventually, you should see this:

As you can see, the risk level is currently ”No known risks”, as this activity is not considered suspicious. Note, this view also shows the machines exposure level. MDATP assesses any vulnerabilities on the host, either due to missing security patches or known vulnerable configurations. The exposure level does not impact the devices compliance status, but provides very useful information about the security posture of the device.

At this point, we can see our device also in Intune, and it is compliant with all the defined policies:

When I access SharePoint Online, I’m able to get in with just username and password. And if I login to Exchange Online, I need to authenticate using MFA, but I’m able to get in. So everything is working as expected. Note: if you’re using Chrome, you need to have the Windows 10 Accounts extension enabled. Otherwise the device information will not get conveyed to Azure AD, and it will assume that you’re using a non-compliant device.

Infect me!

Now, let’s break things. In order to do that, I need to simulate an attack on my Windows 10 device so that MDATP will increase the risk level. For that purpose I’m using a Word document that is crafted for this purpose.

The document contains a macro that drops two files on the desktop (diy_rs3_jscript_executes_ps.js and WinATP-Intro-Backdoorexe.jpg). It will then establish persistence by modifying the HKCU\Software\Microsoft\Windows\CurrentVersion\Run registry key. And finally, it starts a trusted process (RuntimeBroker.exe) and injects malicious code into it. This kind of pattern might go unnoticed from traditional anti-malware solutions, and that’s of course the point here.

Once I’ve opened the document, I’ll wait a couple of minutes and voilà! There’s not just one alert, but a bunch of them, and the device risk level jumped to High. We’ll briefly look into the forensics later, now we just want to see what happens with conditional access.

The information has also propagated to Intune via the integration and the device is no longer compliant (this took literally just a couple of minutes from the simulated infection):

At this point, I’m still able to use my open browser session as the access token is still valid (1 hour validity time). But I’m impatient so I just close my browser and reopen it. When I login to Exchange Online now, I get the following notice. In this case, it should say: ”Oops – you really shouldn’t have opened that document”.

I’m still able to access SharePoint Online but it now requires MFA. So the CA policies work just as expected.

Off the grid

One useful feature within MDATP is that you can completely isolate the client from the network. It might be that the device is currently in the corporate network, and therefore just restricting access through Azure AD might not be enough (unless you’ve implemented a full-blown zero-trust model).

Once I do that, it takes a minute or two and I lose my RDP connection. Luckily, that the device will still be able to communicate back to MDATP (well, it would be a pretty crappy feature otherwise). This way, once the remediation activities have been completed and, you can remotely allow the device back into the network. Also, I can still continue my forensic analysis while the device is safely off the grid.

CSI

So what would happen next? Obviously, at this point someone from the SOC team, or whoever is monitoring the alerts, would need to investigate what happend, and help getting the device back into compliance. There are a couple of MDATP features worth exploring at this point (we won’t go through everything). MDATP shows you the process tree during the incident, which gives a nice overview of what happened:

 You can also check on individual files, and where they have been seen in the organization, in this case I’m looking at the WinATP-Intro-Backdoorexe.jpg:

You can also take the file hash and use it to drill down to the data using the advanced hunting functionality. This allows you to search the data directly from the MDATP logs using Kusto query language (same that is used with Log Analytics). There’s also a community that has produced useful examples, both for generic use as well as for identifying specific exploits (https://github.com/microsoft/WindowsDefenderATP-Hunting-Queries). In this case, I’m just looking into all the events involving this particular file.

Another useful feature is the ”Collect investigation package”:

This will pull all sorts of useful information from the machine and allow you to download it in zip format. This can be very useful when doing the forensic analysis.

Reincarnation

So let’s say we’ve now concluded our investigations and cleaned up the machine. What next? We have to decrease the risk level of the machine for it to become compliant again. To do that, we have to resolve the alerts in the portal. You can also link alerts into incidents, as in this case all the alerts originating from this machine should all be related to a single incident.

Once all the alerts have been resolved, the risk level goes back to ”No known risks”. At this point, if we have isolated the machine, we could release it from the isolation (it took a couple of minutes for me to be able to reconnect my RDP session after the release).

And as expected, device status in Intune is back to compliant:

And I’m able to read my email again!

That concludes today’s experiments.

Azure AD Directories and B2B user decision matrix – One-slider

Click for larger version

Ever pondered on how to decide about B2B Account Types? One thing is for sure, If you’re enterprise org, you’re better of having multiple partner account types, because it’s not one size fits all type scenario

  • The matrix makes clear separation between collaboration only, and administrative tasks performing partners
    • This is based on multiple recommendations, but is based mostly on the following Azure Subscription recommendation from Azure Secure DevOps Kit. (Obviously if the account is also homed in Azure AD and you’ve setup B2B conditional access policy for guests, you might consider yourself covered to some extent…)
link
  • Where authentication happens is important part on the picture, and is the main deciding factor on users home directory

Other considerations

  • Licensing is separate discussion… Anyway key takeaway is, that the 1:5 ratio licensing works in the background = If you assign license to guest user its one license gone, and doesn’t benefit from the B2B-Licensing
  • The picture doesn’t consider SSO between the host tenant and its IDP, as it basically wouldn’t add anything to the picture (unless you’d ”multiplex” multiple claims provider against single AD FS, and then via claims pipeline transformations emit claims for the guest type users in you’re tenant)
  • More detailed explanation of all the scenarios (minus the flowchart) can be found here Properties of an Azure Active Directory B2B collaboration user

Please don’t hesitate to comment or send feedback, if you notice any errors or wrong assumptions in the flowchart

”MOAR STUFF”

If you want to check B2B deep diver on user types and authn/authz, then check: https://securecloud.blog/2019/05/06/deep-diver-azure-ad-b2b/

Click for bigger picture

Add sAMAccountName to Azure AD Access Token (JWT) with Claims Mapping Policy (and avoiding AADSTS50146)

With the possibilities available (and quite many of blogs) regarding the subject), I cant blame anyone for wondering whats the right way to do this. At least I can present one way that worked for me

Here are the total ways to do it (1. obviously not the JWT token)

  1. With SAML federations you have full claims selection in GUI
  2. Populate optional claims to the API in app registration manifest, given you’ve updated the schema for the particular app
  3. Create custom Claims Policy, to choose emitted claims (The option we’re exploring here)
  4. Query the directory extension claims from Microsoft Graph API appended in to the directory schema extension app* that Graph API can call

Please note, for sAMAccountName we’re not using the approach where we add directory extensions to Graph API queryable application = NO DIRECTORY EXTENSION SYNC IN AAD CONNECT NEEDED


Checklist for using Claims Mapping Policy

Pre: Have Client application, and web API ready before proceeding

#Example App to Add the Claims 

AzureADPreview\Connect-AzureAD

$Definition = [ordered]@{
    "ClaimsMappingPolicy" = [ordered]@{
        "Version" = 1
        "IncludeBasicClaimSet" = $true
        "ClaimsSchema" = @(
            [ordered]@{
                "Source" = "user"
                "ID" = "onpremisessamaccountname"
                "JwtClaimType" = "onpremisessamaccountname"
            }
        )
    }
}

$pol =  New-AzureADPolicy -Definition ($definition | ConvertTo-Json -Depth 3) -DisplayName ("Policy_" + ([System.Guid]::NewGuid().guid) + "_" + $template.Values.claimsschema.JwtClaimType) -Type "ClaimsMappingPolicy" 
 
$entApp =  New-AzureADApplication -DisplayName  ("DemoApp_" + $template.Values.claimsschema.JwtClaimType)
$spnob =  New-AzureADServicePrincipal -DisplayName $entApp.DisplayName -AppId $entApp.AppId 

Add-AzureADServicePrincipalPolicy -Id $spnob.ObjectId -RefObjectId $pol.Id 

#From the GUI change the Identifier and acceptMappedClaims value (From the legacy experience)


  • Generally: The app that will emit the claims is not the one you use as the clientID (Client subscribing to the Audience)
    • Essentially you should create un-trusted client with clientID, and then add under Api permissions the audience/resource you’re using
  • Ensure that SPN has IdentifierURI that matches registered custom domain in the tenant
    • The reasoning is vaguely explained here & here
      • Whatever research work the feedback senders did, it sure looked in depth 🙂
  • Update the app manifest to Accept Mapped Claims
    • Do this in the legacy experience, the new experience at least in my tenant didn’t support updating this particular value
”Insufficient privileges to complete the operation”

if mapped claims are not accepted in manifest, and pre-requisites are not satisfied you might get this error

”AADSTS50146: This application is required to be configured with an application-specific signing key. It is either not configured with one, or the key has expired or is not yet valid. Please contact the application’s administrator.”

  • Below is example for the Manifest changes (AcceptMappedClaims, and verified domain matching URI)
     "id": "901e4433-88a9-4f76-84ca-ddb4ceac8703",
    "acceptMappedClaims": true,
    "accessTokenAcceptedVersion": null,
    "addIns": [],
    "allowPublicClient": null,
    "appId": "9bcda514-7e6a-4702-9a0a-735dfdf248fd",
    "appRoles": [],
    "oauth2AllowUrlPathMatching": false,
    "createdDateTime": "2019-06-05T17:37:58Z",
    "groupMembershipClaims": null,
    "identifierUris": [
        "https://samajwt.dewi.red"
    ],

Testing

References

https://github.com/MicrosoftDocs/azure-docs/issues/5394

https://github.com/MicrosoftDocs/azure-docs/blob/master/articles/active-directory/develop/active-directory-claims-mapping.md

https://github.com/Azure-Samples/active-directory-dotnet-daemon-certificate-credential#create-a-self-signed-certificate

https://github.com/MicrosoftDocs/azure-docs/blob/master/articles/active-directory/develop/v2-protocols-oidc.md#fetch-the-openid-connect-metadata-document

https://github.com/MicrosoftDocs/azure-docs/blob/master/articles/active-directory/develop/active-directory-claims-mapping.md#example-create-and-assign-a-policy-to-include-the-employeeid-and-tenantcountry-as-claims-in-tokens-issued-to-a-service-principal