Hardening Azure Managed Identity: Blue Team Defense Guide

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 for 169.254.169.254.
  • The Fragility of the Metadata: true Header: Microsoft introduced the Metadata: true header 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]:::success

Layer 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:

BASH
# 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:

  1. Ensure pods do not run with hostNetwork: true.
  2. Apply an AKS Network Policy (Calico or Azure CNI) denying pod egress to 169.254.169.254/32.
  3. 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:

JSON
{
  "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:

BASH
# 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 like Key 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:

  1. Define Trusted Named Locations: Configure the public egress IP addresses of your corporate networks, VPN gateways, and Azure NAT Gateways.
  2. 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:

YAML
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

KQL
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

KQL
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:

C
#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 --> VerifyState

Phase 1: Live Triage & Identity Scope Identification#

  1. Extract Identity Object ID: Identify the Object ID (oid) of the compromised Managed Identity from the Azure portal or CLI:
BASH
# 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
  1. Audit Azure Activity Logs for Malicious Operations: Search for actions initiated by the identity's oid in the past 24 hours:
BASH
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:

  1. 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:
BASH
# 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 {}
  1. For User-Assigned Identities: Detach the identity from all associated VMs and delete or disable the identity:
BASH
# 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#

  1. Audit for Backdoor Role Assignments: Verify whether the attacker assigned new roles to unauthorized accounts or external service principals:
BASH
# 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
  1. Inspect Key Vault Access History: If the identity had access to Key Vaults, query Key Vault Diagnostic Logs in Log Analytics:
KQL
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 iptables rules block the web process account (www-data) from reaching 169.254.169.254.
  • Confirm that no built-in Contributor or Owner roles 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#

  1. Microsoft Learn: Azure Instance Metadata Service (IMDS) security guidelines — Microsoft Documentation
  2. Microsoft Security Response Center (MSRC): Best practices for securing Azure Managed Identities — MSRC Blog
  3. CISA / NSA Cybersecurity Advisory: Top Cloud Security Misconfigurations and Mitigation Strategies — CISA Advisory
  4. MITRE ATT&CK Framework: Technique T1552.005: Unsecured Credentials - Cloud Instance Metadata API — MITRE ATT&CK

Comments