Hardening Azure Managed Identity: Blue Team Defense Guide
Overview & Defensive Context#
In our morning threat modeling analysis, we deconstructed the exploitation lifecycle of Azure Managed Identities and the Instance Metadata Service (IMDS). We demonstrated how application-layer vulnerabilities—specifically Server-Side Request Forgery (SSRF) and remote code execution—enable external threat actors to query http://169.254.169.254/metadata/identity/oauth2/token. By supplying the required Metadata: true header, attackers extract cryptographically signed Microsoft Entra ID OAuth2 Bearer tokens, pivot across administrative resource audiences (ARM, Key Vault, Microsoft Graph), and exploit overprivileged RBAC assignments (such as Contributor or Virtual Machine Contributor) to achieve full cloud subscription takeover.
For cloud security engineers and blue teams, defending Azure Managed Identities exposes critical flaws in legacy perimeter security assumptions:
- The Invisibility of Link-Local Traffic: IMDS operates over a non-routable link-local IPv4 address (
169.254.169.254) routed internally by the Azure hypervisor. Traditional Network Security Groups (NSGs), Azure Firewalls, and external Web Application Firewalls (WAFs) operate outside the host network namespace and cannot filter egress traffic destined for169.254.169.254. - The Fragility of the
Metadata: trueHeader: Microsoft introduced theMetadata: trueheader requirement to mitigate blind SSRF. However, advanced application exploitation—including header injection, proxy abuse, CRLF injection, and cURL wrapper manipulation—allows attackers to satisfy this requirement with minimal friction. - Token Exportability and Unbounded Replay: Entra ID tokens issued to managed identities are standard Bearer tokens. Possession grants authorization. Because these tokens lack source-IP binding, device health attestation, or mTLS proof-of-possession, an attacker can exfiltrate a token and execute REST API calls against Azure Resource Manager (
management.azure.com) from any external workstation globally until token expiration.
[!WARNING] Relying solely on Azure's default metadata protections without host-level isolation and strict RBAC boundaries allows a single compromised web container to compromise your entire cloud control plane.
This guide provides an enterprise-ready blue team defense blueprint: host-level IMDS packet filtering with iptables and eBPF, a least-privilege custom Azure RBAC architecture, production-grade Sigma and Microsoft Sentinel KQL detection queries, and an end-to-end incident response playbook for rapid token revocation.
Architecture Hardening#
Securing Azure workloads requires a defense-in-depth model that neutralizes IMDS exploitation across three architectural tiers: Host-Level IMDS Process Segmentation, Least-Privilege Custom RBAC Scoping, and Conditional Access for Workload Identities.
Defense Architecture Flowchart#
flowchart TD
classDef workload fill:#1e293b,stroke:#ef4444,stroke-width:2px,color:#f8fafc
classDef filter fill:#0f172a,stroke:#3b82f6,stroke-width:2px,color:#93c5fd
classDef iam fill:#064e3b,stroke:#10b981,stroke-width:2px,color:#6ee7b7
classDef monitor fill:#4c1d95,stroke:#8b5cf6,stroke-width:2px,color:#ddd6fe
classDef drop fill:#450a0a,stroke:#dc2626,stroke-width:2px,color:#fca5a5
classDef success fill:#065f46,stroke:#34d399,stroke-width:2px,color:#a7f3d0
AppProcess[Web Application Process e.g. www-data]:::workload --> KernelFilter{Ring 1: Host iptables / eBPF Filter}:::filter
KernelFilter -- UID Matches Unprivileged Web User --> Drop1[Action: DROP Packet to 169.254.169.254]:::drop
KernelFilter -- UID Matches Root or waagent Daemon --> IMDS[Ring 2: Azure IMDS Listener 169.254.169.254]:::filter
IMDS --> EntraPolicy{Ring 3: Entra ID Conditional Access}:::iam
EntraPolicy -- Token Request Originates from Untrusted IP --> Drop2[Action: Block Token Issuance]:::drop
EntraPolicy -- Verified VNet NAT Gateway Egress --> TokenIssued[OAuth2 Bearer Token Issued]:::iam
TokenIssued --> ARMValidation{Ring 4: Least-Privilege Custom RBAC}:::iam
ARMValidation -- Invokes Disallowed Action: runCommand or roleAssignments --> Drop3[Action: HTTP 403 Forbidden]:::drop
ARMValidation -- Invokes Authorized Scoped Operation --> Execute[Authorized Cloud Resource Access]:::successLayer 1: Host-Level IMDS Network Segmentation & Process Isolation#
On Azure Virtual Machines, only system administrative daemons—specifically the Azure Linux Agent (walinuxagent/waagent) and specific operational tools—require access to IMDS. Web applications running as unprivileged service accounts (www-data, nginx, nobody) must be explicitly blocked from communicating with 169.254.169.254.
Implement host-level packet filtering using Linux iptables owner matching:
# Create dedicated chain for IMDS filtering
sudo iptables -N IMDS_FILTER
## Route all outbound traffic targeting 169.254.169.254 to IMDS_FILTER
sudo iptables -A OUTPUT -d 169.254.169.254 -j IMDS_FILTER
## Explicitly permit the Azure Linux Agent and root processes
sudo iptables -A IMDS_FILTER -m owner --uid-owner root -j ACCEPT
sudo iptables -A IMDS_FILTER -m owner --uid-owner walinuxagent -j ACCEPT
## Log unauthorized access attempts from web application accounts
sudo iptables -A IMDS_FILTER -m owner --uid-owner www-data \
-m limit --limit 5/min -j LOG --log-prefix "[SECURITY] IMDS_BLOCKED_WWW: " --log-level 4
## Drop all remaining outbound requests to IMDS from unprivileged users
sudo iptables -A IMDS_FILTER -j DROP
## Save iptables configuration persistently
sudo netfilter-persistent save
For Azure Kubernetes Service (AKS) clusters, eliminate host-network metadata inheritance:
- Ensure pods do not run with
hostNetwork: true. - Apply an AKS Network Policy (Calico or Azure CNI) denying pod egress to
169.254.169.254/32. - Migrate legacy pod identities to Azure Workload Identity, which leverages standard Kubernetes Projected Service Account Tokens federated via OpenID Connect (OIDC), removing the requirement for pods to query IMDS directly.
Layer 2: Least-Privilege Custom Azure RBAC Architecture#
The most devastating consequence of IMDS token theft is inheriting overly broad permissions. Organizations must completely ban the assignment of built-in roles such as Owner, Contributor, or Virtual Machine Contributor to workload managed identities.
Define a restrictive custom Azure RBAC role that strips lateral movement permissions:
{
"Name": "Workload-LeastPrivilege-AppRole",
"IsDataActionsOnly": false,
"Description": "Restricted role for web applications allowing read-only resource inspection without lateral movement",
"Actions": [
"Microsoft.Resources/subscriptions/resourceGroups/read",
"Microsoft.Compute/virtualMachines/read"
],
"NotActions": [
"Microsoft.Compute/virtualMachines/runCommand/*",
"Microsoft.Compute/virtualMachines/extensions/*",
"Microsoft.Authorization/roleAssignments/*",
"Microsoft.Authorization/roleDefinitions/*",
"Microsoft.KeyVault/vaults/write",
"Microsoft.KeyVault/vaults/delete"
],
"DataActions": [],
"NotDataActions": [],
"AssignableScopes": [
"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/production-rg"
]
}
Deploy the custom role and assign it strictly at the specific Resource Group or Resource level, never at the Subscription level:
# Create the custom role in your subscription
az role definition create --role-definition role-definition.json
## Assign role strictly to target Resource Group
az role assignment create \
--assignee "4b68449c-d018-4e5a-a309-c5603848b64b" \
--role "Workload-LeastPrivilege-AppRole" \
--resource-group "production-rg"
[!TIP] When granting access to Azure Key Vault, always use Azure RBAC data plane roles (such as
Key Vault Secrets User) scoped to specific individual secrets, rather than legacy Key Vault Access Policies or administrative roles likeKey Vault Administrator.
Layer 3: Entra ID Conditional Access for Workload Identities#
To prevent exfiltrated Bearer tokens from being used from external attacker infrastructure, configure Conditional Access for Workload Identities in Microsoft Entra ID:
- Define Trusted Named Locations: Configure the public egress IP addresses of your corporate networks, VPN gateways, and Azure NAT Gateways.
- Enforce Location Blocking: Apply a Conditional Access policy targeting Workload Identities / Service Principals, enforcing a rule that blocks access if the request originates from an untrusted IP address.
Production Detection Queries#
Detecting managed identity token abuse requires correlated telemetry spanning host audit logs, Azure Activity logs, and Entra ID Sign-In logs.
Production Sigma Rule: Suspicious Azure VM Run Command Invocation#
The following Sigma rule detects invocations of the Azure VM runCommand API—a primary lateral movement technique following IMDS token exfiltration:
title: Azure VM Run Command Invocation by Managed Identity
id: e8a3d1c4-7259-4fa2-9bc1-54129b8c71a3
status: production
description: |
Detects instances where the Azure Resource Manager API virtualMachines/runCommand/action is invoked
by a service principal associated with an Azure Managed Identity, indicating potential lateral
movement following IMDS token theft.
references:
- https://learn.microsoft.com/en-us/azure/virtual-machines/instance-metadata-service
- https://attack.mitre.org/techniques/T1552/005/
tags:
- attack.execution
- attack.t1059.004
- attack.lateral_movement
- attack.t1021
- attack.credential_access
- attack.t1552.005
logsource:
service: azure.activitylogs
detection:
selection_operation:
operationName|contains:
- 'Microsoft.Compute/virtualMachines/runCommand/action'
- 'Microsoft.Compute/virtualMachines/runShellScript/action'
selection_identity:
caller|contains:
- 'MSI'
- 'ManagedIdentity'
- 'userAssignedIdentities'
filter_known_automation:
callerIpAddress|cidr:
- '10.200.0.0/16' # Authorized Admin Subnet
condition: selection_operation and (selection_identity or not filter_known_automation)
fields:
- caller
- callerIpAddress
- resourceId
- operationName
- correlationId
falsepositives:
- Legitimate automated Azure DevOps or GitHub Actions deployment pipelines using managed identities to configure VMs.
level: critical
Microsoft Sentinel KQL Telemetry: Detecting Anomalous MSI Egress & Multi-Audience Pivots#
Deploy these production Kusto Query Language (KQL) rules within Microsoft Sentinel to identify compromised managed identities:
Query 1: Managed Identity Sign-In from Untrusted External IP
AADServicePrincipalSignInLogs
| where ServicePrincipalType == "ManagedIdentity"
| where IPAddress !in ("Known_Azure_NAT_Egress_IP", "Corporate_VPN_Egress_IP")
| project TimeGenerated, ServicePrincipalName, ServicePrincipalId, IPAddress, Location, ResourceDisplayName
| order by TimeGenerated desc
Query 2: Rapid Multi-Audience Token Request Anomaly
AzureActivity
| where TimeGenerated > ago(1h)
| where Caller has "userAssignedIdentities" or Caller has "managedIdentities"
| summarize TargetResourceCount = dcount(ResourceProviderValue),
ResourceProviders = make_set(ResourceProviderValue) by Caller, CallerIpAddress, bin(TimeGenerated, 15m)
| where TargetResourceCount >= 3
| order by TargetResourceCount desc
eBPF Runtime Kernel Probe: Detecting Unauthorized IMDS Sockets#
The eBPF tracepoint probe below (imds_socket_guard.bpf.c) intercepts sys_enter_connect system calls, alerting when non-root processes attempt to establish a TCP socket connection to 169.254.169.254:
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_endian.h>
#define IMDS_IPV4 0xA9FEA9FE // 169.254.169.254 in network byte order
struct event_t {
__u32 pid;
__u32 uid;
__u32 dst_ip;
__u16 dst_port;
char comm[16];
};
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 1 << 24);
} events SEC(".maps");
SEC("tracepoint/syscalls/sys_enter_connect")
int trace_connect_enter(struct trace_event_raw_sys_enter *ctx) {
struct sockaddr_in *addr = (struct sockaddr_in *)ctx->args[1];
struct sockaddr_in sa;
bpf_probe_read_user(&sa, sizeof(sa), addr);
if (sa.sin_family != AF_INET) {
return 0;
}
// Check if target IP is 169.254.169.254 on port 80
if (sa.sin_addr.s_addr == bpf_htonl(0xA9FEA9FE) && sa.sin_port == bpf_htons(80)) {
__u32 uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
// Ignore legitimate root/system management daemon (UID 0)
if (uid == 0) {
return 0;
}
struct event_t *event = bpf_ringbuf_reserve(&events, sizeof(struct event_t), 0);
if (!event) {
return 0;
}
event->pid = bpf_get_current_pid_tgid() >> 32;
event->uid = uid;
event->dst_ip = sa.sin_addr.s_addr;
event->dst_port = bpf_ntohs(sa.sin_port);
bpf_get_current_comm(&event->comm, sizeof(event->comm));
bpf_ringbuf_submit(event, 0);
}
return 0;
}
char LICENSE[] SEC("license") = "GPL";
Enterprise Mitigation Matrix#
When prioritizing defenses against cloud metadata credential abuse, security leaders must balance operational velocity against blast radius reduction:
| Remediation Strategy | Implementation Effort | Blast Radius & Operational Risk | Detection & Prevention Efficacy | Performance Overhead | Architectural Longevity |
|---|---|---|---|---|---|
| Workaround: Host iptables Owner Filtering | 15 - 30 Minutes (Applied via cloud-init, Ansible, or VM extensions) |
Low Ensure waagent (UID 0) is excluded to prevent agent failures. |
High (95%) Blocks web processes from communicating with 169.254.169.254. |
Zero (Kernel packet filter < 0.1% CPU). |
Interim to Long-Term Reliable host boundary on IaaS VMs. |
| Hotfix: Custom Least-Privilege RBAC Scoping | 1 - 3 Hours (Audit current role assignments and replace with custom roles) |
Low to Moderate Must verify application dependencies prior to revoking broad roles. |
Comprehensive for Escalation Strips runCommand, roleAssignments/write, and control plane takeovers. |
Zero Azure control plane enforcement. |
Permanent Best Practice Essential Zero-Trust architecture principle. |
| Architecture Fix: Azure Workload Identity + Conditional Access | 2 - 5 Business Days (Migrate to OIDC federated tokens & IP-gated Conditional Access) |
Moderate Requires configuring OIDC issuer on Kubernetes/App Services. |
Absolute (100%) Eliminates IMDS dependency entirely and binds tokens to verified networks. |
Zero Standard OpenID Connect flow. |
Permanent State Future-proof identity architecture. |
[!IMPORTANT] Host-level iptables filtering protects the VM, but least-privilege RBAC protects the entire Azure tenant. Both controls must be implemented in tandem to form an effective defense-in-depth posture.
Incident Response & Verification Playbook#
If an application hosting a managed identity suffers an SSRF or unauthorized execution incident, execute the following four-phase incident response workflow immediately:
flowchart TD
classDef check fill:#1e293b,stroke:#f59e0b,stroke-width:2px,color:#fef3c7
classDef alert fill:#450a0a,stroke:#dc2626,stroke-width:2px,color:#fca5a5
classDef clean fill:#064e3b,stroke:#10b981,stroke-width:2px,color:#6ee7b7
classDef triage fill:#0f172a,stroke:#3b82f6,stroke-width:2px,color:#93c5fd
Start[Step 1: Identify Compromised Identity OID]:::triage --> AuditLogs[Step 2: Query AzureActivity for Recent Actions]:::triage
AuditLogs --> CheckActions{High-Risk Actions Observed? runCommand / writeRole}:::check
CheckActions -- Yes --> DeclareSeverity1[Declare Severity 1 Incident: Tenant Compromise]:::alert
CheckActions -- No --> RevokeIdentity[Step 3: Immediate Identity Revocation]:::clean
DeclareSeverity1 --> IsolateSub[Sever Network Access and Isolate Impacted VMs]:::alert
IsolateSub --> RevokeIdentity
RevokeIdentity --> SystemAssigned{System or User Assigned?}:::check
SystemAssigned -- System-Assigned --> ToggleIdentity[Disable and Re-enable Managed Identity on VM]:::clean
SystemAssigned -- User-Assigned --> UnassignIdentity[Detach Identity and Delete Service Principal]:::clean
ToggleIdentity --> VerifyState[Step 4: Audit Role Assignments and Key Vault Access]:::clean
UnassignIdentity --> VerifyStatePhase 1: Live Triage & Identity Scope Identification#
- Extract Identity Object ID:
Identify the Object ID (
oid) of the compromised Managed Identity from the Azure portal or CLI:
# For System-Assigned Identity on VM
az vm identity show --name "prod-web-vm" --resource-group "production-rg" --query principalId -o tsv
## For User-Assigned Identity
az identity show --name "app-identity" --resource-group "production-rg" --query principalId -o tsv
- Audit Azure Activity Logs for Malicious Operations:
Search for actions initiated by the identity's
oidin the past 24 hours:
PRINCIPAL_ID="4b68449c-d018-4e5a-a309-c5603848b64b"
az monitor activity-log list \
--caller "${PRINCIPAL_ID}" \
--start-time $(date -u -d '24 hours ago' '+%Y-%m-%dT%H:%M:%SZ') \
--query "[].{Time:eventTimestamp, Action:operationName.value, Status:status.value, Resource:resourceId}" \
-o table
Phase 2: Token Invalidation & Immediate Containment#
Because Azure Entra ID tokens cannot be selectively revoked once issued, you must invalidate the issuing service principal context:
- For System-Assigned Identities: Disable and re-enable the managed identity on the Azure resource. This action destroys the existing service principal in Entra ID and immediately invalidates all outstanding tokens:
# Remove system-assigned identity to kill active tokens
az vm identity remove --name "prod-web-vm" --resource-group "production-rg"
## Strip all RBAC role assignments across the subscription
az role assignment list --assignee "${PRINCIPAL_ID}" --query "[].id" -o tsv | \
xargs -I {} az role assignment delete --ids {}
- For User-Assigned Identities: Detach the identity from all associated VMs and delete or disable the identity:
# Unassign from VM
az vm identity remove --name "prod-web-vm" --resource-group "production-rg" \
--identities "/subscriptions/sub-id/resourcegroups/production-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/app-identity"
## Delete the user-assigned identity resource
az identity delete --name "app-identity" --resource-group "production-rg"
Phase 3: Blast Radius & Persistence Auditing#
- Audit for Backdoor Role Assignments: Verify whether the attacker assigned new roles to unauthorized accounts or external service principals:
# Check for recently created role assignments
az role assignment list \
--query "[?createdOn >= '$(date -u -d '24 hours ago' '+%Y-%m-%d')'].{Principal:principalName, Role:roleDefinitionName, Scope:scope}" \
-o table
- Inspect Key Vault Access History: If the identity had access to Key Vaults, query Key Vault Diagnostic Logs in Log Analytics:
AzureDiagnostics
| where ResourceType == "VAULTS"
| where OperationName == "SecretGet" or OperationName == "KeyGet"
| where identity_claim_oid_g == "4b68449c-d018-4e5a-a309-c5603848b64b"
| project TimeGenerated, OperationName, id_s, CallerIPAddress
Rotate any secret, certificate, or database connection string accessed during the compromise window.
Phase 4: Post-Remediation Verification Checklist#
Before restoring the application to service:
- Verify that host-level
iptablesrules block the web process account (www-data) from reaching169.254.169.254. - Confirm that no built-in
ContributororOwnerroles remain assigned to the workload identity. - Verify that newly created custom roles explicitly deny
Microsoft.Compute/virtualMachines/runCommand/*. - Ensure Sentinel KQL alerts are operational for anomalous
AADServicePrincipalSignInLogs.
Authoritative References#
- Microsoft Learn: Azure Instance Metadata Service (IMDS) security guidelines — Microsoft Documentation
- Microsoft Security Response Center (MSRC): Best practices for securing Azure Managed Identities — MSRC Blog
- CISA / NSA Cybersecurity Advisory: Top Cloud Security Misconfigurations and Mitigation Strategies — CISA Advisory
- MITRE ATT&CK Framework: Technique T1552.005: Unsecured Credentials - Cloud Instance Metadata API — MITRE ATT&CK
Comments
Post a Comment