Cloud Security Wire
AWS Azure GCP RSS
Azure Misconfiguration high

Azure Function Apps Managed Identity Privilege Escalation: From App to Subscription Owner

Azure Function Apps with system-assigned managed identities are a common privilege escalation path in cloud penetration tests and real-world breaches. An over-permissive managed identity on a vulnerable function can hand an attacker Subscription Owner. Here's how the path works and how to close it.

By Cloud Security Wire · ·
#Azure#Function Apps#managed identity#privilege escalation#RBAC#IAM#Entra ID#cloud security#serverless#SSRF
High Severity

This issue has been assessed as high severity. Review affected configurations immediately.

Azure Function Apps are the serverless compute layer most organisations reach for when they need event-driven processing, scheduled tasks, or lightweight API backends. Managed identities — Azure’s mechanism for giving applications an identity without managing credentials — are the recommended pattern for granting function apps access to other Azure resources. The combination is everywhere. It is also one of the most consistent privilege escalation paths in Azure penetration tests and, increasingly, in real-world incidents.

The problem is not managed identities themselves. It’s the gap between what the identity needs and what it’s actually been granted — and the fact that a vulnerable function app with an over-permissive managed identity gives any attacker who can execute code in that function the identity’s full permissions.

How the Escalation Path Works

A system-assigned managed identity is bound to the function app’s lifecycle. When the function runs, the Azure Instance Metadata Service (IMDS) endpoint at http://169.254.169.254/metadata/identity/oauth2/token is available from within the execution environment. Any code running in the function — including code injected by an attacker via a vulnerability — can request an access token for that identity by calling the IMDS endpoint.

# From inside a Function App execution context
curl -s "http://169.254.169.254/metadata/identity/oauth2/token?\
api-version=2018-02-01&resource=https://management.azure.com/" \
-H "Metadata: true"

# Returns:
# {
#   "access_token": "eyJ0eXAiOiJKV1Q...",
#   "client_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
#   "expires_in": "85888",
#   "token_type": "Bearer"
# }

With that token, the attacker makes Microsoft Graph or Azure Resource Manager API calls with the managed identity’s permissions. If the identity has been granted Contributor or Owner on the subscription — which is common when developers need broad access “to test things” and never revisit the assignment — the path to full subscription takeover is straightforward.

# Use the token to enumerate the subscription
curl -s "https://management.azure.com/subscriptions?api-version=2020-01-01" \
-H "Authorization: Bearer $ACCESS_TOKEN"

# If Owner-level: enumerate, create new users, exfiltrate all resources
az role assignment list --all --output table

The Vulnerability Surface in Function Apps

Several vulnerability classes in function apps create code execution paths that an external attacker can reach:

Injection in function triggers: Function Apps frequently process HTTP requests, queue messages, or blob storage events containing user-supplied content. Any injection vulnerability (command injection, SSRF to internal endpoints, template injection, deserialization) in the function code provides code execution in the runtime environment — which has IMDS access.

Dependency vulnerabilities: Function Apps package their dependencies. A vulnerable npm, PyPI, or NuGet dependency in the function’s package set is exploitable in the same context as an application-level vulnerability.

Logic flaws in custom authentication: Functions exposed via HTTP often implement their own authentication against Function Keys or custom headers. Bypasses in these mechanisms grant unauthenticated access to function execution.

CI/CD pipeline compromise: If the function’s deployment pipeline (GitHub Actions, Azure DevOps) is compromised, an attacker can replace the function code itself, gaining a persistent execution context with full managed identity permissions.

What Over-Permissive Assignments Look Like

The most common misconfiguration patterns, in order of frequency:

# Enumerate function app managed identity assignments
az webapp identity show \
  --name my-function-app \
  --resource-group my-rg

# Check what role assignments exist for that identity
IDENTITY_OBJECT_ID=$(az webapp identity show \
  --name my-function-app \
  --resource-group my-rg \
  --query principalId -o tsv)

az role assignment list \
  --assignee $IDENTITY_OBJECT_ID \
  --all \
  --output table

Common findings:

  • Contributor scoped to the subscription rather than the specific resources the function needs
  • Owner granted during development “temporarily” and never removed
  • Storage Blob Data Contributor on /* (all storage accounts) rather than the specific container
  • Key Vault Secrets User on all key vaults in the subscription rather than the specific vault

Each of these turns a function app vulnerability into an impact radius far larger than the function itself.

Hardening Steps

Scope managed identity assignments to the minimum required resource. A function that reads from one blob container should have Storage Blob Data Reader scoped to that container’s resource ID, not the storage account, not the subscription.

# Correct scoping: assign to specific container only
az role assignment create \
  --assignee $IDENTITY_OBJECT_ID \
  --role "Storage Blob Data Reader" \
  --scope "/subscriptions/$SUB_ID/resourceGroups/$RG/providers/\
Microsoft.Storage/storageAccounts/$STORAGE_ACCT/blobServices/default/\
containers/$CONTAINER_NAME"

Prefer user-assigned managed identities for shared permissions. A user-assigned identity can be attached to multiple function apps with identical permission requirements, making it easier to audit and rotate. System-assigned identities multiply uncontrolled.

Enable network restrictions on function app HTTP endpoints. Functions that don’t need to be internet-facing should use Private Endpoints or IP allow-listing. Reducing the network exposure surface limits the injected-code-via-HTTP vector.

Monitor IMDS token requests via Defender for Cloud. Azure Defender for Resource Manager and Defender for Servers both generate alerts for anomalous IMDS token requests. Specifically, look for:

  • Tokens requested for Microsoft Graph with high-privilege scopes
  • Token requests from IPs outside the expected function execution range
  • Role assignment creation events (Microsoft.Authorization/roleAssignments/write) performed by a managed identity
// Sentinel: Detect managed identity creating new role assignments
AzureActivity
| where OperationNameValue == "MICROSOFT.AUTHORIZATION/ROLEASSIGNMENTS/WRITE"
| where ActivityStatusValue == "Success"
| where Caller !endswith ".microsoft.com"
| where CallerIpAddress != ""
| join kind=leftouter (
    AzureActivity
    | where CategoryValue == "Administrative"
    | summarize AppCount = count() by Caller
) on Caller
| where AppCount < 5  // filter out high-volume automation
| project TimeGenerated, Caller, Properties, ResourceGroup, SubscriptionId

Audit function app permissions quarterly as part of your non-human identity review cycle. Managed identity sprawl compounds over time: functions get deployed, over-permissioned, and then deprioritised as teams move on. Each dormant function with broad permissions is an unused attack surface.

Detection via Defender for Cloud

The following Microsoft Defender for Cloud alerts should be enabled and routed to your SIEM for any subscription hosting function apps with managed identities:

  • Suspicious use of managed identity by a function app — detects IMDS token requests inconsistent with the function’s normal operation pattern
  • Privileged role assignment to managed identity — fires when Owner or high-privilege Contributor is assigned to a managed identity
  • Azure Resource Manager anomalous activity from a suspicious IP — catches management plane calls from unexpected sources using managed identity tokens

None of these replace baseline hygiene — least-privilege scoping is the control that matters. Detection tells you when a misconfiguration has been exploited; the misconfiguration should be fixed regardless.

← All Analysis Subscribe via RSS