Foreign Entra Workload Identities
Marius Elmiger
Why it is important to protect Microsoft cloud access tokens
While access tokens serve a similar purpose across different cloud platforms, solutions, or Web sites, there may be differences in their implementation and specific functionality. This article focuses primarily on how to extract access tokens issued by the Microsoft Cloud. It concludes with some ways to make access token theft more difficult.
An access token is a security token issued by an authorization server as part of an OAuth 2.0 flow. It contains information about the user and the resource for which the token is intended. This information can be used to access Web APIs and other protected resources. Resources validate access tokens to grant access to a client application.
The Microsoft Identity Platform implements security tokens as JSON Web Tokens (JWTs) that contain claims. Because JWTs are used as security tokens, this form of authentication is sometimes referred to as JWT authentication. A claim provides assertions about one entity, such as a client application or resource owner, to another entity, such as a resource server. A claim may also be referred to as a JWT claim or a JSON Web token claim. Additionally, cryptographic signing ensures the integrity and authenticity of the token. The token is digitally signed using a private key, and the recipient can verify its authenticity using the corresponding public key.
The default Microsoft Cloud JWT token is base64url-encoded, so it can be decoded. Decoding is preferably done offline to avoid any risk of compromise. For example, by downloading and reviewing the code from jwt.ms or jwt.io and using the HTML/JavaScript offline. Or by using a PowerShell script like the one from Vasil Michev or the Python solution roadtx from DirkJan. Once the JWT is decoded, it may look like the following truncated access token example:
{ "typ": "JWT", "nonce": "OwJxBtbDQCPbBNDAfLGfXVExE0pF8sLb36nC_MWvpDQ", "alg": "RS256" }.{ "aud": "https://graph.microsoft.com", "iss": "https://sts.windows.net/d2a16643-x/", "iat": 1715765602, "nbf": 1715765602, "exp": 1715770910, "amr": [ "pwd", "mfa" ], "app_displayname": "My Profile", "appid": "8c59ead7-d703-4a27-9e55-c96a0054c8d2", "appidacr": "0", "ipaddr": "141.195.x.x", "name": "Rabban", "oid": "4c422389-x-x-x-x", "scp": "AuditLog.Read.All BitlockerKey.Read.All CrossTenantInformation.ReadBasic.All CrossTenantUserProfileSharing.ReadWrite.All Device.EnableDisableAccount.All Device.Read.All email Group.Read.All Group.ReadWrite.All GroupMember.Read.All MailboxSettings.ReadWrite openid Organization.Read.All Policy.Read.All profile RoleManagement.ReadWrite.Directory User.Invite.All User.Read.All User.ReadBasic.All User.ReadWrite", "signin_state": [ "kmsi" ], "tid": "d2a16643-x-x-x-x", "upn": "rabban@x.onmicrosoft.com", "xms_cc": [ "cp1" ], }.[Signature]
In the above example, the token contains several claims. The most important ones are listed below to identify for example the owner, the audience, the scope, the xms_cc, and the expiration date.
Claim type | Notes |
---|---|
aud | Identifies the intended recipient of the token. In id_tokens, the audience is your app’s Application ID, assigned to your app in the Azure portal. Your app should validate this value, and reject the token if the value does not match. |
exp | The “exp” (expiration time) claim identifies the expiration time on or after which the JWT must not be accepted for processing. It is important to note that a resource may reject the token before this time as well – if for example a change in authentication is required or a token revocation has been detected. |
amr | Identifies how the subject of the token was authenticated. Microsoft identities can authenticate in a variety of ways, which may be relevant to your application. The amr claim is an array that can contain multiple items, such as [“mfa”, “rsa”, “pwd”], for an authentication that used both a password and the Authenticator app. See the amr claim section in Azure Active Directory access tokens documentation for values. |
appid | The application ID of the client using the token. The application can act as itself or on behalf of a user. The application ID typically represents an application object, but it can also represent a service principal object in Entra ID. |
ipaddr | The IP address the user authenticated from. |
scp | The set of scopes exposed by your application for which the client application has requested (and received) consent. Your app should verify that these scopes are valid ones exposed by your app, and make authorization decisions based on the value of these scopes. Only included for user tokens. |
tid | A GUID that represents the Entra ID tenant that the user is from. For work and school accounts, the GUID is the immutable tenant ID of the organization that the user belongs to. For personal accounts, the value is 9188040d-6c67-4c5b-b112-36a304b66dad. The profile scope is required in order to receive this claim. |
upn | The username of the user. May be a phone number, email address, or unformatted string. Should only be used for display purposes and providing username hints in reauthentication scenarios. |
xms_cc | The xms_cc claim with a value of cp1 in the access token is the authoritative way to identify a client application is capable of handling a claims challenge. Access Tokens with this claim can be protected by Continuous Access Evaluation (CAE) |
More details regarding the Microsoft cloud’s token claims are documented under the ID token claims reference.
A Microsoft cloud access token is limited to one resource, is typically valid for one hour, and cannot be revoked. There is also a Continuous Access Evaluation (CAE) access token that is valid for 24 hours and is revokable. Tools such as TokenTacticsV2 or roadtx can be used to do create a CAE access token from a valid refresh token or a cookie. Unfortunately, refresh tokens are not covered in this article, as they are a beast in themselves. If you want to learn more about refresh tokens, check out Dirk-Jan Mollema blogs.
If an access token can be extracted, it can usually be used from any device, from anywhere, for as long as it is valid. It is therefore crucial to protect access tokens especially for privileged users. For more details on how to do this, jump ahead to the Access Token Protection chapter.
There are many ways to grab a Microsoft Cloud Access Token. Here are some examples that we use during Security Assessments, Pentests or Red Teaming.
The easiest way for a logged-in user to obtain an access token is to open the browser developer tools and browse to Microsoft cloud front-end URLs such as https://myapps.microsoft.com/
, https://myaccount.microsoft.com/
, https://portal.azure.com/
or https://mysignins.microsoft.com/
. Here is a brief overview of access tokens that can be extracted from known API endpoints and whether they can be used for tools such as RoadRecon or AzureHound.
URL | Recipient (aud) | Scopes | RoadRecon | AzureHound |
---|---|---|---|---|
https://mysignins.microsoft.com | https://graph.microsoft.com | email openid profile AuditLog.Read.All CrossTenantInformation.ReadBasic.All Directory.Read.All Policy.Read.All User.Read UserAuthenticationMethod.ReadWrite UserAuthenticationMethod.ReadWrite.All .default | No | Yes (Entra ID only) |
https://mysignins.microsoft.com | 0000000c-0000-0000-c000-000000000000 | tenants.read .default | No | No |
https://portal.azure.com | https://graph.windows.net | restricted_user_impersonation .default | Yes | No |
https://portal.azure.com | https://management.core.windows.net | restricted_user_impersonation .default | No | No |
https://entra.microsoft.com | https://graph.windows.net | restricted_user_impersonation .default | Yes | No |
https://entra.microsoft.com | https://management.core.windows.net | restricted_user_impersonation .default | No | No |
https://myaccount.microsoft.com/ | https://graph.microsoft.com | email openid profile AuditLog.Read.All BitlockerKey.Read.All CrossTenantInformation.ReadBasic.All CrossTenantUserProfileSharing.ReadWrite.All Device.EnableDisableAccount.All Device.Read.All Group.Read.All Group.ReadWrite.All GroupMember.Read.All MailboxSettings.ReadWrite Organization.Read.All Policy.Read.All RoleManagement.ReadWrite.Directory User.Invite.All User.Read.All User.ReadBasic.All User.ReadWrite | No | Yes (Entra ID only) |
https://myaccount.microsoft.com | https://graph.microsoft.com | email openid profile AuditLog.Read.All BitlockerKey.Read.All CrossTenantInformation.ReadBasic.All CrossTenantUserProfileSharing.ReadWrite.All Device.EnableDisableAccount.All Device.Read.All Group.Read.All Group.ReadWrite.All GroupMember.Read.All MailboxSettings.ReadWrite Organization.Read.All Policy.Read.All RoleManagement.ReadWrite.Directory User.Invite.All User.Read.All User.ReadBasic.All User.ReadWrite .default | No | Yes (Entra ID only) |
https://myaccount.microsoft.com | https://graph.microsoft.com | openid profile AuditLog.Read.All BitlockerKey.Read.All CrossTenantInformation.ReadBasic.All CrossTenantUserProfileSharing.ReadWrite.All Device.EnableDisableAccount.All Device.Read.All Group.Read.All Group.ReadWrite.All GroupMember.Read.All MailboxSettings.ReadWrite Organization.Read.All Policy.Read.All RoleManagement.ReadWrite.Directory User.Invite.All User.Read.All User.ReadBasic.All User.ReadWrite | No | Yes (Entra ID only) |
https://myaccount.microsoft.com | https://graph.windows.net | restricted_user_impersonation .default | Yes | No |
https://cosmos.azure.com | https://management.azure.com | user_impersonation .default | No | Yes (ARM only) |
https://portal.office.com | https://graph.microsoft.com | email openid profile Calendars.Read Contacts.Read Family.Read Files.ReadWrite.All InformationProtectionPolicy.Read Notes.Create People.Read Presence.Read.All Sites.Read.All Tasks.ReadWrite User.Read User.ReadBasic.All .default | No | Yes (Entra ID only) |
https://portal.office.com | https://x.sharepoint.com | Files.ReadWrite.All Sites.FullControl.All User.Read.All .default | No | No |
https://portal.office.com | https://outlook.office365.com | search/SubstrateSearch-Internal.ReadWrite search/.default | No | No |
Another way to grab an access token is to steal web session cookies. The cookie is then exchanged for an access token. Stealing cookies from a browser is very well described by Justin Bui in his article Hands in the Cookie Jar: Dumping Cookies with Chromium’s Remote Debugger Port. After that, roadtx by Dirkjan can be used, e.g. with the command roadtx interactiveauth --estscookie 0.AYIAQ --tokens-stdout -r msgraph -c azps
to exchange the cookie for an access token. For example for Azurehound and RoadTools use for -r
: msgraph
, https://graph.windows.net
or https://management.azure.com
. For the -c
any Family of Client IDs (FOCI) should work.
To prevent or make it more difficult for an attacker to execute the above methods as a user with low privileges, the developer tools and remote debugging can be disabled for most browsers via group policies.
Access tokens can also be extracted from files. For example Sander Maas discovered early on that Outlook for Windows (Yes only the new one) is storing refresh and as well access token under %localappdata%\Microsoft\Olk\EBWebView\Default\Session Storage\00000x.log
. Older versions of the Azure CLI and other cmdlets stored access tokens in plain text at %USERPROFILE%\.azure
. Still a good folder to check, because who updates PowerShell modules?
Access tokens can also be found in processes such as Outlook, Word, Teams, PowerShell, etc. The easiest way to do this is to dump the process via Task Manager and search for access tokens in the dump with sysinternals strings.exe
and findstr /i eyJ0eX
. Tokens issued for https://graph.microsoft.com
can usualy be found and are usable for AzureHound.
Further reading or tools to automate the search and extraction of access tokens from processes:
A simple way to get an access token is via the OAuth 2.0 Device Authorization Grant Flow. Further details how that works can be found in the article Hidden dangers of phishing attacks. Nowadays this flow can be blocked by Conditional Access policies.
There are other OAuth 2.0 grant flows from which an access token can be obtained. Such as the password credentials grant flow or others that are well documented under Microsoft identity platform app types and authentication flows. These flows can be used if the credentials are known or abused, for example by deploying a phishing application. A good read is Nyxgeek’s Creating a Malicious Entra ID OAuth2 Application.
There are several Azure or Entra ID-related cmdlets. Some of the cmdlets allow you to view/get the access token when a session has been cached or after a successful login.
Type | Module | Command | RoadRecon | AzureHound |
---|---|---|---|---|
PowerShell | Az.Accounts | Get-AzAccessToken | Yes | Yes |
Azure CLI | az | az account get-access-token —resource=https://graph.microsoft.com —query accessToken -o tsv | Yes | Yes |
The connect-mggraph
cmdlet does not currently have a built-in method to display the access token. However, the process dump method can be used to extract the access token from an active PowerShell process that used the cmdlet connect-mggraph
.
If a cached session is open to Microsoft Graph Explorer, an access token can be extracted via the GUI. The access token cannot be used for the azurehound or roadtools tools. However, sometimes the Microsoft Graph Explorer application is allowed for all users and is therefore a good way to do an initial enumeration.
Tools that can intercept web traffic, such as Proxies, Burp, Fiddler or Zap, can be used to intercept access tokens.
If a user has sufficient access to resources such as Azure Cloud Shell, Azure Cloud Shell images, Azure Automation RunAs accounts, DevOps pipelines, or a virtual machine that uses managed identities, it is often possible to extract the access token.
Further readings:
If an access token is stolen, it can be used to access or manipulate data from anywhere on any device, bypassing any conditional access rules. Although Microsoft has or is implementing security features such as CAE or Token Protection to protect against access token theft by ensuring that a token can only be used by its intended device. However, these features do not support all 1st party cloud applications, and Token Protection currently only supports refresh tokens. The only promising feature that could protect against access token theft is Global Secure Access. We have not yet tested this preview feature, so we cannot say whether it will deliver what it promises.
The above methods for extracting access tokens, with the exception of device code authentication, malicious OAuth 2.0 applications, and Azure cloud resources, assume that an attacker is already on the device. However, this requires non-local administrator rights. Protecting the end device and especially the privileged users is therefore crucial. But hey, there is a simple trick for privileged users (e.g. tier-0), almost free, a bit annoying and not very fancy, it is called Privileged Access Workstation. If used, it is the strongest defense against token theft and solves the problem at the beginning namely where the keyboard sits.
In general and for non-privileged users, allowing access only from trusted and hardened endpoints, implementing recommended tenant security recommendations, handling credentials securely, and using phishing-resistant multi-factor authentication (MFA) is a good way to start making access token theft more difficult.
Further reading on protective measures:
As we have explored, there are several ways to obtain access tokens. If an attacker succeeds in obtaining a token, it may be valid for one hour or 24 hours. Enough time to exfiltrate data or install a backdoor for persistence, the latter depending on the user’s privileges.
There is currently no easy way to prevent the unauthorized use of access tokens, other than to make it difficult for an attacker to get close to a user with a privileged access token. This often starts with the old-fashioned but proven way of implementing a secure privileged access strategy. Done correctly, this would make it more difficult for an attacker to succeed. However, it is not uncommon in our security assessments to find privileged or mission-critical users using personal phones or even home computers to access the organization’s tenant. Similar mistakes were made with Active Directory in the past, when everyone was a domain admin and security hardening and token protection did not exist. At least there was the fortress methodology of firewalls protecting the environment from the Internet. But with the cloud, that methodology no longer fully works.
We have a different threat model when working with a cloud environment, the priority is at the endpoint where the user is either allowed or not allowed to access the cloud. Consequently, allowing access only from trusted and hardened endpoints, implementing tenant security recommendations, applying secure practices around privileged access, secure credential handling, using phishing-resistant multi-factor authentication (MFA), and an attackers mindset, to name a few, are the cornerstones of protecting cloud resources and the tokens required to access them.
Our experts will get in contact with you!
Marius Elmiger
Marius Elmiger
Marius Elmiger
Marius Elmiger
Our experts will get in contact with you!