An Operator’s Guide to Device-Joined Hosts and the PRT Cookie

Introduction

About five years ago, Lee Chagolla-Christensen shared a blog detailing the research and development process behind his RequestAADRefreshToken proof-of-concept (POC). In short, on Entra ID joined (including hybrid joined) hosts, it’s possible to obtain a primary refresh token (PRT) cookie from the logged in user’s logon session, enabling an attacker to satisfy single-sign-on (SSO) requirements to cloud resources. Dirk-jan Mollema has also blogged about this capability, where he noted that these PRT cookies (and access tokens requested with them) may contain the multi-factor authentication (MFA) claim — enabling the attacker to access MFA-protected resources.

For a capability that has been publicly known for half a decade, I’ve seen shockingly little online reference to it. I’m not sure if the frequency at which I encounter cloud/hybrid joined devices has recently increased or if I was sleeping on this capability for literal years (more likely), but this tradecraft has been a serious crutch on red team operations in the last six months. While some teams out there are undoubtedly reaping the benefits of this tradecraft during routine operations, I think there are probably quite a few operators out there who, like me, came across the prior works I’ve linked to but didn’t immediately connect the dots.

This blog won’t contain any truly new information or research, but it will try to distill some operationally-focused knowledge that I learned while fumbling through this tradecraft over the last year.

Situational Awareness

A new beacon has called back to your C2 server and you want to know if this blog is applicable to you! This tradecraft will work from device joined hosts — hosts that are either Entra ID joined (cloud-only join) or hybrid joined (joined to both Entra ID and on-premises AD), so we need to identify the join state.

Relevant join information, including the join type, can be obtained by calling NetGetAadJoinInformation from NetApi32.dll. I created a small Beacon object file (BOF) to call the API, which can be found in this PR to the TrustedSec situational awareness (SA) BOF repo. We’re looking for the “Device join” join type (as opposed to workplace join, where an Entra ID work/school account is added to the device but the device isn’t joined to Entra ID).

Join Type Enumeration

Some of the other information can be useful, including the tenant ID and user join information, which reveals some details about the account that was leveraged to perform the Entra ID device join (in this case, the same account I’m logged in with locally in the lab, but that is not guaranteed to always align).

Note: I’m using Havoc and its demon agent for C2 in my lab environment since it’s easy to set up, there’s no licensing to mess with, and it nicely approximates some relevant capabilities of commercial frameworks; however, the tradecraft discussed in this post is C2-agnostic. Most if not all of the tools that are referenced already have Cobalt Strike aggressor scripts or can be easily hooked into your C2 of choice.

We can also get most of this information from the registry under the key HKLM\SYSTEM\CurrentControlSet\Control\CloudDomainJoin. If present, its values hold information on the device join state and associated Entra ID tenant. An easy approach is to use TrustedSec’s SA reg_query_recursive BOF to surface the interesting values that are stored there.

Join Info Enumeration via the Registry

The JoinInfo key won’t be present on workplace joined hosts, so just the presence of that key and its values should indicate we’re on a host that is device joined.

Alright; we’ve determined the host is Entra ID joined (cloud only in my lab), so the next question is, “What work or school accounts are added to the device?” Or, “What accounts can I obtain refresh token cookies for?”

Initially, this seems like it should be a 1:1 relationship with the account our agent is running in the context of. It probably will be in most cases; however, I’ve encountered production environments where we were able to obtain refresh tokens for multiple Entra ID accounts associated with the compromised user’s logon session. This can happen if the user has added multiple “work or school” accounts within System Settings. Envision a scenario involving a user who has been provisioned a separate admin account; if the user adds both their standard Entra ID account and admin Entra ID account to their user profile, now from the standard user account logon session our agent is running in, we can obtain a refresh token for both accounts. I’ve attempted to mimic this in my lab setup (I’m logged in on the box as MattC@specterdev.onmicrosoft.com and the agent is running as this account), but I’ve also connected a second account to my profile (i.e., admin-mattc@specterdev.onmicrosoft.com) within System Settings.

Multiple Entra ID Accounts Added to the User Profile

Note: In these scenarios where multiple work/school accounts are in play, you can obtain a PRT for the account the user is locally logged in with and can obtain a refresh token for the other account(s). I’ll cover this more in the next section.

Finding a C2-friendly way to enumerate which work/school accounts have been added to the user’s profile has proven to be the most difficult part of putting these tradecraft notes together. If you’re familiar with enumerating cloud-joined or hybrid-joined devices, you probably know that you can use dsregcmd.exe to enumerate the information we’ve discussed thus far, and it can also be used to list “web account manager (WAM) accounts.” If you are unfamiliar with the WAM, it is a technology on Windows that allows software such as the Microsoft Authentication Library (MSAL) to acquire tokens for cloud-based accounts. These cloud-based accounts include Entra ID accounts, AD FS accounts (which are sometimes referred to as “Enterprise” accounts), personal Microsoft accounts, and Microsoft work or school accounts. The dsregcmd.exe utility will collectively call all of these account types “WAM accounts” because the WAM can acquire tokens for all of them. The phrase “WAM account” is seldom used elsewhere and “cloud-based accounts” may be a more appropriate phrase, but we will stick with using “WAM account” in this blog to stay consistent with the output of the dsregcmd.exe utility.

If we’re feeling into process creation and/or spawning cmd.exe, we can leverage dsregcmd.exe with the /listaccounts flag to enumerate WAM accounts that have been added (or the /status flag to enumerate the device’s join state).

07 April 2025


>>More