Inhalt

Continuous access evaluation

At my companies bootcamp, a few colleagues and I did research on the different Azure Active Directory tokens and authentication flows. At the end of the week one question remained unanswered

Frage
How does the usage of continuous access evaluation (CAE) and the extended lifetime of the access token impact security?

So, after I returned home, I started digging into this topic to answer the question.

OpenID Connect, OAuth2 and token

Let’s back up a second and look at the current implementation of the different protocols involved in authentication and authorization to better understand the need for CAE.

First off, OpenID Connect (OIDC), is the authentication protocol used from Microsoft in Entra ID (Azure AD). Only if a user or application has successfully authenticated, it can request token from the OAuth 2.0 token endpoint.

Conditional Access is a crucial part in securing this authentication flow even more, as it controls additional signals of the sign-in like network location, device state or risk and can add additional requirements like using MFA for additional authentication strength and can block the sign-in completely if those requirements are not met.

If the user has successfully signed in, it will be provided with different token.

One of those it the access token. This access token has a short lifetime of about a bit over an hour (60 - 90 minutes) and can be used to connect to resource providers.

Refresh token

Since the access token is short lived, you will also get a refresh token, to renew the access as well as the refresh every so often. Each time you have to reach out to the token endpoint of the authorization server, using the refresh token to request a new set of tokens.

Since the refresh token contains all sorts of imprinted security identifiers, it’s highly valuable from a security standpoint. As you can see in this screenshot from my SignIn logs I was able to refresh for a new access token from a completely different IP address and device, but the only change that Entra ID (Azure AD) registered was the change in location.

/continuous-access-evaluation/images/SignInUsingRefreshTokenFromNonCompliantDevice.png
Use refresh token from a non compliant device

Even that the new client was neither hybrid joined nor compliant in any way, was of no interest because this information is included in the refresh token itself. Only if you have a location based Conditional Access policy in place, or the refresh token is missing a needed imprint like strong authentication you will be rejected.

/continuous-access-evaluation/images/SignInUsingRefreshTokenRejected.png
Refresh token rejected based on a Conditional Access Policy

Because the refresh token is so valuable, it can be revoked by the administrator. If you revoke the currently valid refresh tokens, the user will have to go through a complete authentication before receiving a new set of tokens from the authorization server (AAD).

More information on PRT
If you want to learn more about the refresh token and it’s even more powerful friend the primary refresh token (PRT), head over to this article by Sami Lamppu and Thomas Naunheim which did a great job at explaining at the different ways the PRT is protected and how you can mitigate attacks against it.

Access token

Back to the access token. The Access token is sent directly to the resource provider e.g., Exchange Online and this service relies completely on the authorization server for authenticating the user. So as long as the access token has not exceeded its lifetime, the resource provider will accept it and grant access. Even if the associated refresh token was revoked.

/continuous-access-evaluation/images/DefaultFlowWithConditionalAccess.png
Default sign-in flow with Conditional Access

To minimize the impact, the access token is short lived, but an attacker might have access to the resource for a up to 90 minutes and up until now you couldn’t do anything to revoke this access.

This is where CAE comes into the picture to save the day.

What’s continuous access evaluation?

The idea behind this technology was first discussed by Google in February 2019. Microsoft announced their implementation goals back in April 2020 in this blog post. In January 2022 it was generally available (GA). They are also involved in adding this functionality into the OpenID standard as a protocol named Continuous access evaluation Protocol (CAEP).

The main idea of CAE is to provide a way to invalid the user access in near real time. This should happen if an event occurs like e.g. tokens are revoked, but also implement a small subset of features from conditional access policies directly into the resource provider. Currently this a limited to network location changes that should prevent the user from accessing resources.

Event based triggers

For this to work an additional communication channel between the authorization server and the resource provider has to be established. Using this channel. the authorization server can inform the resource provider as soon as an access token should be invalidated or add additional information on when an access token should be considered invalid. This communication channel is implemented via a webhook, the resource provider subscribes to it to receive events from Entra ID (Azure AD).

Currently the following critical events can be subscribed to

  • User Account is deleted or disabled
  • Password for a user is changed or reset
  • Multi-factor authentication is enabled for the user
  • Administrator explicitly revokes all refresh tokens for a user
  • High user risk detected by Entra ID (Azure AD) Identity Protection

As of today, those webhooks are not available to the public, but Microsoft services like Exchange Online, SharePoint Online, Teams, and MS Graph already subscribe to them.

Notiz
SharePoint Online currently do not support risk-based events.

Limited conditional access policy

For the second set of capabilities, the resource provider will need to evaluate changes from the client itself.

In the initial blog post it seemed that Microsoft wanted to implement a back-channel from the resource provider to Entra ID (Azure AD) which would allow communication based on anomalies detected by the resource provider.

The relying party can notice when things have changed (e.g. client coming from a new location) and tell the token issuer.

In the current documentation the wording changed quite a bit and it seems more like a communication from the authorization server to the resource provider but no back-channel.

Exchange Online, SharePoint Online, Teams, and MS Graph can synchronize key Conditional Access policies for evaluation within the service itself.

Client support

The accessing client also has to support continuous access evaluation, because it has to know how to handle the event when the access token is invalidated. In this case the resource provider will return a 401 Unauthorized to the client and the client has to go back to the authentication server and complete the authentication flow. In this flow all conditional access policies are evaluated, and Entra ID (Azure AD) can confirm if the user should still have access.

To signal to Entra ID (Azure AD) that the client can handle CAE it must send an additional claim xms_cc with the value cp1. In the sign-in logs you can see this as well. If you use a kusto query the value has to be extracted from the AuthenticationProcessingDetails field.

/continuous-access-evaluation/images/CAETokenInSignIn.png

UnifiedSignInLogs
| sort by TimeGenerated
| mv-apply IsCAEToken = todynamic(AuthenticationProcessingDetails) on (
    where IsCAEToken.key == "Is CAE Token"
    | project IsCAEToken = tostring(IsCAEToken.value)
)
| project-reorder TimeGenerated, IPAddress, AppDisplayName, IsCAEToken

UnifiedSignInLogs is a custom function that combines SignInLogs and AADNonInteractiveUserSignInLogs into one, making it much easier to check for sign-in events.

/continuous-access-evaluation/images/SignInIsCAEToken.png

Long lived access tokens

One additional but major change when using CAE is the extension of issued access tokens lifetime to up to 28 hours. This will also reduce the load on the authorization server (Entra ID (Azure AD)), because it only has to refresh the access token once a day and once per hour.

But what implications does the changed lifetime will have on the security. Since there are additional protective methods in place, it should not reduce it but strengthen it.

Tooling

There are different toolkits out there that allow you to work with Entra ID (Azure AD) tokens. To name a few

  • AADInternals by Dr. Nestori Syynimaa (@DrAzureAD)
    This is by far the most advanced toolkit out there and not limited to only tokens.
  • TokenTactics by Steve Borosh (@rvrsh3ll) and Bobby Cooke (@0xBoku)
    Highly specialized PowerShell module aimed at (ab)using refresh token to get access token from different Microsoft services.
  • BARK (BloodHound Attack Research Kit) by Andy Robbins (@_wald0)
    A fairly new addition to the tool belt, this is not limit to token research but has a more Azure focused background

Those are all great tools, but they all have one or the other limitation that make CAE research impossible.

While AADInternals and TokenTactics rely on the old v2 Entra ID (Azure AD) endpoint and therefore cannot support CAE, BARK on the other hand uses the v2 endpoint, but does not have any implementation for the needed additional client capability claim.

This is why I forked TokenTactics and modified it for my purpose and released it as TokenTacticsV2.

  • All authentication flows now use the v2 endpoint
  • Added CAE support to the RefreshTo- cmdlets using a new switch UseCAE
  • Change client ids for some of the supported apps
  • Added support for OneDrive token
  • Added new properties to Parse-JWTToken for better readability of time formats

Since I reworked large portions of the code, including my own preferences for code formatting, I decided against putting in a pull request against the original repo.

I created a pull request for BARK that adds the CAE functionality. I did not do the same with AADInternals because I was too afraid to break other stuff.

Test scenarios

I defined the following test scenarios, starting with the trigger based mitigations and ending with different location based scenarios.

Scenario 1 - User Account is deleted

Scenario 2 - User Account is disabled

Scenario 3 - Password for the user is changed

Scenario 4 - Password for the user is reset

Scenario 5 - Multi-factor authentication is enabled for the user

Scenario 6 - Revoke all refresh tokens for the user

Scenario 7 - High user risk detected by Entra ID (Azure AD) Identity Protection

Scenario 8 - Switch to another location without a location based policy

Scenario 9 - Switch to a location that requires MFA without MFA in access token

Scenario 10 - Switch to a location that requires MFA with MFA in access token

Scenario 11 - Switch to a location that requires Hybrid Joined Device

Scenario 12 - Switch to blocked location

Scenario 13 - Change trusted location while access token is valid

Scenario 14 - Switch to location that requires a compliant device

Scenario 15 - Use Sign-in frequency

Trigger based scenarios

For all trigger based (scenario 1 -7) I used the following script in my testing. I first request an access token for MSGraph in TokenTacticsV2 using parameter -UseCAE. After I received the access token from the test user, I queried the Graph API every 5 seconds until the access token was invalidated.

I repeated those tests again without a CAE capable access token as an cross check.

ipmo .\TokenTactics.psd1 -Force
Get-AzureToken -Client MSGraph -UseCAE
Connect-MgGraph -AccessToken $response.access_token
$MGResponse = $true
do {
	Start-Sleep 5
	cls
	Get-Date
	try {
		Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/me"
	} catch {
		Write-Warning ( $_.Exception.Message )
		$MGResponse=$false
	}
} while ($MGResponse)

Scenario 1 - User Account is deleted

After deleting the account, it took about 30 seconds until the CAE access token was revoked.

To my surprise the nonCAE access token was also rejected after around 36 seconds.

Scenario 2 - User Account is disabled

Like in scenario 1 the CAE access token was rejected after 30 seconds.

The nonCAE access token was also invalidated, but it took about 1 1/2 minutes.

Scenario 3 - Password for the user is changed

In this case it took about 40 seconds to block the CAE access token.

In contrast, the nonCAE access token was still valid after 15 minutes and probably would have worked for the remaining lifetime of the token.

Scenario 4 - Password for the user is reset

This result was observable in this test as well. The CAE access token was revoked in under 30 seconds, but the nonCAE token was valid for a long time.

Scenario 5 - Multi-factor authentication is enabled for the user

The wording in this case leaves some room for interpretation. What exactly is meant by “MFA is enabled”?

The CAE access token was not invalidated by the following actions

  • Add an MFA authentication method using aka.ms/mysecurityinfo
  • Add an MFA authentication method using aka.ms/mfasetup
  • Enable MFA for the user using the old MFA portal

It was necessary to create a conditional access policy that required MFA for the user in question. After around 4 minutes the MFA requirement was processed, and the access token invalidated.

The nonCAE access token was valid the whole time.

Scenario 6 - Revoke all refresh tokens for the user

Using the “Revoke sessions” option in the Azure portal took about 10 seconds until the access token was revoked and is unusable.

/continuous-access-evaluation/images/RevokeSessions.png
Revoke sessions

/continuous-access-evaluation/images/AccessTokenRevoked.png
Revoked access token

Using the option “Require user to sign in again” in the Microsoft 365 Defender portal also works.

/continuous-access-evaluation/images/RequireUserToSignInAgain.png
Require user to sign in again

Scenario 7 - High user risk detected by Entra ID (Azure AD) Identity Protection

While the wording of this trigger implies that only user-linked detections with a resulting user risk of high will revoke the access token, I was able to provoke this trigger by trying to sign-in two times in a row using the Tor browser. The resulting user risk was only medium, but the access token was still rejected. This also reflects the wording in the GA blog post which states “User risk increase”.

/continuous-access-evaluation/images/MediumUserRisk.png
User with medium user risk

/continuous-access-evaluation/images/RecentRiskySignIns.png
Recent risky sign-ins

Location based scenarios

These tests where a bit challenging. Therefore, I will outline the different test methodology in each scenario.

Scenario 8 - Switch to another location without a location based policy

This was the control group and I used it as a baseline. The user had neither an MFA requirement nor a location restriction. In fact, no conditional access policy was applied at all.

Again, I used TokenTacticsV2 to get in possession of a CAE capable access token, but this time for Microsoft Teams. The victim user had logged into a Windows 11 computer using Windows Hello for Business in Finland.

This access token was then used from different locations around the world. Since flying around is time and resource consuming, I opted for the reasonable alternative and used a VPN. Each time I changed the location I wrote the current IP address into the Teams status of the user and verified the update. For the update I used the great AADInternals which provides the necessary cmdlets Set-AADIntTeamsStatusMessage and Get-AADIntTeamsAvailability.

/continuous-access-evaluation/images/ChangedTeamsMessage.png
Teams status message changed

/continuous-access-evaluation/images/TeamsStatusClient.png
Changed status in the Microsoft Teams client

/continuous-access-evaluation/images/CAEWorldMap.png
Sign in results on a world map

Time Country Result
10:22:35 PM USA Authorization has been denied for this request. (405)
10:23:45 PM Netherlands Worked
10:25:16 PM Cyprus Worked
10:27:13 PM Nigeria Worked
10:28:15 PM Singapur Authorization has been denied for this request. (405)
10:29:56 PM South Korea Authorization has been denied for this request. (405)
10:31:10 PM Irland Worked
10:32:52 PM Brazil Authorization has been denied for this request. (405)
10:33:57 PM Kenya Worked

This result surprised me, since the access token was not usable in the Americas and Asia Pacific but worked fine in Europe and Africa. But it was not rejected by CAE as the 405 clearly states, CAE would result into a 401.

Notiz
The reason for this behavior is most likely, that an access token from one Microsoft 365 datacenter geography region is only valid within the geography it was issued. Global Geography 1 – EMEA (Europe, Middle East and Africa) Global Geography 2 – Asia Pacific Global Geography 3 - Americas

With this knowledge I then got into the CAE test scenarios.

Scenario 9 - Switch to a location that requires MFA without MFA in access token

The user in question had a conditional access policy applied that required MFA outside of the companies premises. This user did not log into the computer using WHfB or any other MFA method which resulted in an CAE access token that only contains {pwd} in the amr claim.

As soon as I tried to use the access token from an IP address outside of the companies trusted location it was rejected by a 401 response.

Good to know: The access token wasn’t invalidated completely and from the work computer, the user was was still able to use this access token.

/continuous-access-evaluation/images/CAEAccessTokenDeniedTeams.png
Access token is rejected by CAE

Scenario 10 - Switch to a location that requires MFA with MFA in access token

This is a similar test to scenario 9, but now the user already signed in using a strong authentication method. The user used a FIDO key which resulted in an amr claim of {fido, mfa}.

I was able to use the token outside of the trusted location without any problems. This hints to the usage of the amr property value in the evaluation.

/continuous-access-evaluation/images/AuthStrengthIsPartOfAccessToken.png
When the access token contains a strong auth indicator the attack still works

Scenario 11 - Switch to a location that requires Hybrid Joined Device

The conditional access policy in this scenario requires an access token issued to a device that is hybrid joined. To gain access to the access token with a valid claim of a hybrid device (signin_state = dvc_dmjd) I jumped through some hoops.

On the victim’s device I retrieved a refresh token for the MSGraph through the Edge browser after the victim logged into the Graph Explorer. Next I used TokenTacticsV2 on the victims device, using the refresh token to get a access token with more permissions.

RefreshTo-MSGraphToken -RefreshToken $RefreshToken -Domain c4a8korriban.com -ClientID "de8bc8b5-d9f9-48b1-a8ad-b748da725064" -Verbose -UseCAE

After validating that the access token was indeed valid for around 24 hours and had the correct claim, I copied it off the device.

/continuous-access-evaluation/images/SignInStateClaim.png

On the attacker machine I connected to Microsoft Graph using the access token and was able to query information about the user.

/continuous-access-evaluation/images/ConnectMgGraphAccessToken.png
Connect to MSGraph using access token

Next, I generated an access token while the user was logged into a private browsing window and used Microsoft Authenticator passwordless sing-in.

Outside of the trusted location this access token is rejected.

/continuous-access-evaluation/images/AccessTokenRejectedLocation.png
401 is returned after switching locations

Scenario 12 - Switch to blocked location

But what about a more restrictive conditional access policy that only allows access from certain IP addresses?

As soon as I tried to use the access token from outside of the defined network range it was rejected.

/continuous-access-evaluation/images/BlockedLocation.png
Connection is rejected at a blocked location

This means the resource provider has knowledge over the allowed IP ranges and which action to take based on different requirements.

Scenario 13 - Change trusted location while access token is valid

What happens if you try to change the trusted locations after an CAE capable access token has been issued? Will it still be valid or is there an update mechanism that provides the new information to the resource provider?

For this I created two access tokens, each from a different trusted IP address.

  • AccessToken#1 @ TrustedLocation #1
  • AccessToken#2 @ TrustedLocation #2

Then I repeated the Get-AADIntTeamsAvailability command every 5 minutes from TrustedLocation#2 and removed this trusted location after 2 Minutes.

It took about one hour until I received only 401 responses but had mixed results in between. The first 401 was returned about 18 minutes after removing TrustedLocation#2. There was no major difference in behavior between AT#1 and AT#2.

These results let me conclude that there is an update mechanism to the resource provider but depending on which node you will talk to it can take some time until those updates are propagated everywhere.

I also tested adding a trusted location and had a faster deployment time of about 30 minutes.

Scenario 14 - Switch to location that requires a compliant device

While not listed as a supported requirement in the official documentation, it was mentioned as a possible scenario in the first blog post so I gave it a try.

In the future we will add more events into the instantly evaluated set, including but not limited to location and device state changes.

I create a Conditional access policy that requires the user to have a compliant device outside of trusted network locations.

Notiz
I opted for this setup because it is not possible to get an access token using the device code flow, if you require a compliant device.

Then I requested an access token without CAE, transferred it to the attacker machine and queried the MSGraph endpoint.

/continuous-access-evaluation/images/NonCAEToken.png
Use normal access token to query MSGraph

Next, I created a new access token, this time with CAE support and repeated the rest of the process. And to my delight and surprise I was presented with a 401 error message that indicated that some CAE protection was working.

/continuous-access-evaluation/images/AccessTokenRejectedDeviceCompliance.png
No access without a compliant device claim in the access token

I then copied an access token of the Microsoft Graph Explorer, which was created outside of the trusted location and compared the two tokens. One thing that stood out, was the two missing property values in the access token created inside of the trusted location. The values in question, dvc_mngd and dvc_cmp, seem to indicate that the device is managed as well as that the device is compliant.

/continuous-access-evaluation/images/CompareSignInState.png
Compare normal and CAE access token claims

Scenario 15 - Use Sign-In frequency

The last check I did had nothing to do with the revocation of the access token, but I wanted to know how the session control “Sign-in frequency” (SIF) was related to the access token lifetime.

/continuous-access-evaluation/images/SignInFrequency.png
Sign-In Frequency in Conditional Access Policy

You can’t configure the lifetime of a refresh token. You can’t reduce or lengthen their lifetime.

Source: Microsoft

Since the refresh token lifetime is “static” Entra ID (Azure AD) uses another method to restrict its lifetime. The access token has to be refreshed about every hour using the refresh token, so Entra ID (Azure AD) can use this process to reject the refresh token even if it’s lifetime is not yet reached.

/continuous-access-evaluation/images/RefreshTokenSIF.png
Refresh token invalidated by Sign-In frequency

As soon as the conditional access policy with SIF was applied, regardless of the defined value of the Sign-in frequency, the CAE capable access token lifetime was limited to the default value of 60 - 90 minutes.

/continuous-access-evaluation/images/AccessTokenWithCAETimeLimited.png
Access token with extended lifetime

But the access token will keep all other characteristics of an CAE capable access token, so it can still be revoked using one of the mentioned methods above.

/continuous-access-evaluation/images/AccessTokenSIF.png
Revoked access token with restricted lifetime because of SIF

Sidenote

This may result in a longer lifetime of the access token even if you set the login frequency to the smallest value of 1 hour. The access token is valid for the lifetime defined in the claim exp or until it is revoked through CAE.

Bear in mind that the minimum SIF equals to something between 60 and 90 minutes.

This behavior regarding SIF and access token is documented by Microsoft as well.

Organizations that use Conditional Access sign-in frequency (SIF) to enforce how frequently sign-ins occur can’t override default access token lifetime variation. When organizations use SIF, the time between credential prompts for a client is the token lifetime that ranges from 60 - 90 minutes plus the sign-in frequency interval.

Source: Microsoft

How I think it’s implemented

Disclaimer
All information in this section is based solely on my testing and public documentation. Therefore, this implementation description may be completely wrong and inaccurate, so take everything written here with a pinch of salt.

The resource provider has two ways to interact with Entra ID (Azure AD) to implement CAE. The first implementation only acts on events. This is mostly what will be part of the OpenID Shared Signals and Events Framework Specification. The second implementation is tailored to the conditional access specific values of Entra ID (Azure AD). This is a policy based approach that requires the resource provider to further validate the access token based on different attributes.

Client capabilities

The client has to indicate that it’s capable of continuous access evaluation. This is done by adding the claim xms_cc with the value cp1 when requesting an access token.

This claim will be also added to the access token itself, allowing the resource provider to check if the client is able to handle CAE or not. There is also a claim xms_ssm with the value of 1 in the access token, but I’m not sure if this is related.

If this claim does not contain cp1 no CAE evaluation is performed at all.

Trigger based implementation

/continuous-access-evaluation/images/CAETrigger.png
Trigger based CAE

For trigger based events the authorization server, when receiving the information that the client is capable of CAE as part of the authorization flow, has to check if the target resource provider is also capable of doing CAE. If this is the case, it will register this resource provider as a recipient of push-based security events for this object id (user or service principal). There is also a poll-based implementation available, but based on the wording in the documentation I would guess Microsoft has implemented push.

As soon as any of the supported events occur e.g. a password change, the resource provider is informed and in return will reject any access token that was issued before this time. This can be validated based on the claim iat= “issued at” in the access token itself.

Policy based implementation

/continuous-access-evaluation/images/CAEPolicyEvaluation.png
Policy based CAE

Microsoft had to add additional information to the access token to make sure that a resource provider can decide if a client should be able to access the data or not. All this information is saved in different claims in the access token.

How the resource provider is informed about the different policies for each user is not documented other than the following statement:

The resource provider evaluates the validity of the token and checks the location policy synced from Entra ID (Azure AD).

Based on the documented time limitations and the workaround, I guess that as soon the access token is used for the first time, the resource provider requests the current policy from Entra ID (Azure AD) and caches the policy for future use. This policy will be updated every two hours. The caching itself might be done on a server level, explaining the flip flop effect I observed in scenario 13. Since you might hit different servers based on the load balancer configuration for each request, a policy update can be fast or slower. At least when you get a new access token the resource provider will request a new policy, hence the upper limit of one day for policy changes.

Device information

The claim signin_state is used to tell if a device is compliant, hybrid Entra ID (Azure AD) joined or maybe just managed. Also, it contains the information if the access token was issued while beeing in a trusted location.

Value Description
dvc_mngd Device is managed (e.g. Intune)
dvc_cmp Device is compliant
dvc_dmjd Device is hybrid Entra ID (Azure AD) domain joined
inknownntwk Device is from a trusted location
kmsi Keep me signed in enabled

Strong authentication

The amr claim property contains information about what type of authentication method was used. The official documentation list possible values but is missing fido. The table below can be used as a reference which combinations are imprinted into the access token based on the different ways a user can sign in.

Authentication method Values
Password only pwd
Password and Authenticator TOTP code pwd, mfa
Password and FIDO2 key as MFA option pwd, fido, mfa
Password and Authenticator Push pwd, mfa
Windows Hello for Business rsa, mfa
Passwordless Sign-In using Authenticator App rsa, mfa
Passwordless Sign-In using FIDO2 key fido, mfa
Hybrid Joined Client SSO pwd, rsa, mfa

IP Address

The IP address will not be evaluated based on the claim but the real IP address in the HTTPS request.

CAE Policy

To evaluate the claims a policy, based on the Conditional Access policies defined in Entra ID (Azure AD) must be generated and offered to the resource provider.

The following example policy would enforce an access token that contains the claims stating

  • a sign-in using strong authentication (MFA)
  • a compliant or hybrid Entra ID (Azure AD) joined device when outside of the defined ip address range
  • comes from an IP address that matches on of the listed IP ranges

and would apply to the user object with the object id 9c47ec50-eb34-4daf-82c3-f7e9405b5cc1.

{
    "affected_oid": "9c47ec50-eb34-4daf-82c3-f7e9405b5cc1",
    "mfa_required": {
        "enabled": "true",
        "exclude_locations": []
    },
    "signin_state": {
        "condition": "OR",
        "required": [
            "dvc_cmp",
            "dvc_dmjd"
        ],
        "exclude_locations": [
            "40.126.31.70/32"
        ]
    },
    "allowed_locations": [
        "40.126.31.70/32",
        "20.190.159.3/32"
    ]
}

Since different requirements like device compliance or strong authentication can be linked to certain locations, it is safe to assume that the policy is more complex than this simplified example.

Currently there is an upper limit of 5000 IP address ranges. If this limit is reached the location based policy evaluation is turned off, the access token lifetime will be limited to a 1 hour and only trigger based events are evaluated.

When the sum of all IP ranges specified in location policies exceeds 5,000, user change location flow won’t be enforced by CAE in real time.

When an attacker now tries to use a stolen access token, based those additional claims and the policy definition the resource provider is now able to reject the access token and redirect the client application back to Entra ID (Azure AD) to get a new set of tokens. In this case the attacker would have to get access to a new refresh or access token.

/continuous-access-evaluation/images/CAEDemo.png
CAE evaluation of access token

There are some restrictions that apply to CAE access token and locations. Not supported for location based CAE are

  • MFA trusted IPs
  • Trusted location that includes MFA Trusted IPs
  • Country locations
  • Split Tunnel scenarios that result in different IP addresses at Entra ID (Azure AD) and the resource provider

In all those cases location based restrictions are no longer evaluated and the access token lifetime is reduced to the default value. All other CAE capabilities are untouched.

Therefore, you should avoid the usage of those CAE location types if you want all CAE protections.

TL;DR

  • Continuous access evaluation allows you to revoke access token for key Microsoft applications and can therefore limit the time an attacker might have access to your company data.
  • Since the access token, as well as the refresh token, have some information about the sign-on process “baked” in, an attacker might not always be stopped when you think she would be stopped.
  • Trigger based CAE actions are always applied to CAE access tokens.
  • Location based restrictions in Conditional Access Policies are not evaluated in some cases.
  • The usage of SIF will limit the access token lifetime to its default value.

Conclusion

As mentioned at the start of this article, I wanted to answer one question going into this research.

Frage
How does the usage of continuous access evaluation (CAE) and the extended lifetime of access tokens impact security?

Based on the observed behavior in my testing, there definitely are security implications.

Depending on your Conditional Access Policy you have additional protection out of the box. If you restrict access to certain IP ranges, this is definitely a win for you.

If you use Entra ID (Azure AD) Plan 2 the user risk based triggers are a great way to minimize impact as well.

However, there are some caveats.

Since you do not have access to all the resource provider access logs, you cannot check for any unusual IP addresses using an access token. And since most of the other information that are used to check if the requestor is allowed to access (e.g. device compliance or strong authentication) are imprinted into the access token, they do not offer additional protection.

When you have Microsoft Defender for Cloud Apps and/or Microsoft Sentinel with the Office 365 data connector enabled (it’s free) in place, you can check for those access patterns in SharePoint, Exchange and Teams. MDA will do this automatically and you will get alerts that might result in a user risk CAE trigger.

For Microsoft Graph this not always the case and you currently do not have any way to detect such access patterns on you own. Maybe Microsoft will offer additional logs and detection capabilities for those services in the future.

But since all those downsides are the same as with the refresh token, if not protected by a session key, CAE finally allows you to shut down access faster and more completely than with CAE. If you revoke the users’ sessions, the refresh token as well as access token are invalidated almost immediately.

Also, I would love to see that Microsoft add an additional check at the resource provider end. This check should which check if the IP address used from the user is relatively near to the IP address used to issue the access token. If the distance us above a certain threshold and both IP addresses are not part of the trusted locations, a new access token must be issued. This would complicate most access token attacks but will put additional load on Entra ID (Azure AD) token endpoint.

/continuous-access-evaluation/images/CAEProposedTrigger.png
CAE based impossible travel logic

It is important to understand how this feature works, and which impact it has on your current security posture. This allows you to decide if you want to keep it enabled for all of your users or maybe add additional Conditional Access Policies that benefit the feature. Using the Sign-In frequency in you CA policies would also allow you to restrict the lifetime of the access token, while still maintaining all other CAE capabilities.

I think Continuous access evaluation is a great way to reduce the time to act in case of an attack on an identity and hopefully Microsoft will open up the implementation to third-party applications, so that this is not limited to Microsoft products but can be implemented in any application.

Attribution

I want to thank Thomas Naunheim for prove reading my work and having an open ear to my ramblings about CAE and access token.

Also a big shout out to Steve Borosh (@rvrsh3ll) and Bobby Cooke (@0xBoku) for their initial work on TokenTactics which made the development of TokenTacticsV2 a lot easier.