Cloud Security Wire
AWS Azure GCP RSS
Azure Misconfiguration high

Azure Key Vault Attack Paths: How Secrets Get Stolen and How to Harden Access Control

Azure Key Vault is the most common secrets store in Azure environments, and it sits at the centre of several high-impact attack paths. Misconfigured access policies, overprivileged managed identities, and legacy vault access models create routes from a compromised workload to full secrets exfiltration.

By Cloud Security Wire · ·
#Azure#Key Vault#secrets management#managed identity#RBAC#access policy#privilege escalation#credential theft#hardening
High Severity

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

Azure Key Vault is the default secrets store for most Azure workloads. Connection strings, API keys, TLS certificates, and encryption keys all end up there — which makes it one of the most valuable targets in an Azure compromise. A misconfigured Key Vault doesn’t just leak secrets; it exposes every downstream system that relies on those secrets.

The attack paths into Key Vault are well-understood, but they remain common in production environments. This article covers the three principal categories of Key Vault misconfiguration, how they’re exploited, and the specific controls that close them.

Key Vault Access Models

Azure Key Vault supports two access models:

Vault Access Policy (legacy): Permissions are assigned per-principal at the vault level. A service principal with Get permission on secrets can retrieve any secret in that vault. There is no secret-level granularity.

Azure RBAC (current): Role assignments on individual secrets or vault resources using standard Azure IAM. Supports fine-grained scope (specific secrets, specific keys) and the standard Azure audit trail.

The access policy model predates Azure RBAC integration and remains the default on many vaults provisioned before 2022. It is the root cause of the most common privilege escalation path.

Attack Path 1: Overprivileged Managed Identity

The most common route to Key Vault secrets in the wild. Steps:

  1. Attacker compromises a compute resource — typically an Azure VM, App Service, Container App, or Azure Function
  2. The resource has a system-assigned or user-assigned managed identity
  3. The managed identity has Key Vault Secrets User, Key Vault Reader, or a vault access policy with Get/List permissions
  4. Attacker calls the IMDS endpoint from within the compromised resource to obtain an access token for the managed identity:
# From inside the compromised VM/container
TOKEN=$(curl -s -H "Metadata:true" \
  "http://169.254.169.254/metadata/identity/oauth2/token?\
api-version=2018-02-01&resource=https://vault.azure.net" \
  | jq -r '.access_token')
  1. Attacker uses the token to enumerate and extract secrets:
# List secrets
curl -H "Authorization: Bearer $TOKEN" \
  "https://myvault.vault.azure.net/secrets?api-version=7.4"

# Get a specific secret
curl -H "Authorization: Bearer $TOKEN" \
  "https://myvault.vault.azure.net/secrets/db-connection-string?api-version=7.4"

This requires no additional privilege escalation — the managed identity’s existing permissions are sufficient. In vault access policy environments, a single Get/List policy grants access to every secret in the vault.

What makes this dangerous: The managed identity token is available to any process running inside the VM, container, or function — including attacker-controlled code injected via SSRF, command injection, or RCE. The IMDS endpoint requires no authentication.

Attack Path 2: Access Policy Privilege Escalation via Contributor

In vaults using the legacy access policy model:

  1. Attacker compromises an identity with Contributor role on the vault resource (or a parent resource group/subscription)
  2. Contributor on a Key Vault allows modifying access policies
  3. Attacker adds their controlled identity to the vault access policy with full secret permissions:
az keyvault set-policy \
  --name target-vault \
  --spn attacker-service-principal-id \
  --secret-permissions get list backup restore recover
  1. Attacker now has full secret access

The critical point: In the legacy access policy model, Contributor RBAC role + vault access policies create a separation of privileges illusion. Contributor cannot access secrets directly, but can modify the access policy to grant themselves access. In the Azure RBAC model, Contributor on a vault does not grant Key Vault Secrets User — these are separate permission planes.

Attack Path 3: Key Vault Certificate Private Key Extraction

Key Vault stores TLS certificates with exportable private keys if the certificate policy does not explicitly set exportable: false. An identity with Key Vault Certificate User or the appropriate access policy can download the private key in PFX format:

from azure.identity import DefaultAzureCredential
from azure.keyvault.certificates import CertificateClient

credential = DefaultAzureCredential()
client = CertificateClient(vault_url="https://myvault.vault.azure.net/", credential=credential)

# Download certificate with private key as PFX
secret_client = SecretClient(vault_url="https://myvault.vault.azure.net/", credential=credential)
cert_secret = secret_client.get_secret("my-tls-cert")
# cert_secret.value is a base64-encoded PFX including private key

This is significant for certificates used for code signing, client authentication, or mTLS — the extracted private key can be used to impersonate the service or sign malicious code.

Hardening Controls

Switch to Azure RBAC Access Model

Migrate all vaults from the legacy access policy model to Azure RBAC. This enables:

  • Fine-grained scope to individual secrets, keys, or certificates
  • Standard Azure RBAC audit trail in Activity Logs
  • Separation of data plane (Key Vault Secrets User) and management plane (Contributor) permissions
# Check current model
az keyvault show --name myvault --query properties.enableRbacAuthorization

# Enable Azure RBAC
az keyvault update --name myvault --enable-rbac-authorization true

Least-Privilege Managed Identity Assignments

Stop assigning managed identities vault-wide Secrets User roles. Scope to individual secrets where possible:

# Scope role to a specific secret (Azure RBAC model)
SECRET_ID=$(az keyvault secret show \
  --name db-connection-string \
  --vault-name myvault \
  --query id -o tsv)

az role assignment create \
  --role "Key Vault Secrets User" \
  --assignee <managed-identity-principal-id> \
  --scope "$SECRET_ID"

If an App Service only needs one database connection string, it should only have access to that one secret — not every secret in the vault.

Disable Exportable Certificates by Default

Set exportable: false in certificate policies unless there is an explicit operational requirement:

{
  "key_props": {
    "exportable": false,
    "kty": "RSA",
    "key_size": 2048
  }
}

Review all existing certificates for exportability. Certificates used for code signing and mTLS should never be exportable.

Network Controls: Private Endpoint + Deny Public Access

Restrict vault network access to private endpoints within trusted VNets. Deny all public internet access:

az keyvault update \
  --name myvault \
  --default-action Deny \
  --bypass AzureServices

az keyvault network-rule add \
  --name myvault \
  --vnet-name production-vnet \
  --subnet app-service-subnet

Key Vault should not be reachable from the public internet in production environments. Access from CI/CD pipelines should use service connections through a private endpoint, not public HTTPS from GitHub Actions or Azure DevOps runners.

Diagnostic Logging to Sentinel

Enable Key Vault diagnostic logging and route to your SIEM. The critical log category is AuditEvent — every secret read, certificate download, and key operation:

az monitor diagnostic-settings create \
  --resource /subscriptions/.../resourceGroups/.../providers/Microsoft.KeyVault/vaults/myvault \
  --name keyvault-to-sentinel \
  --logs '[{"category": "AuditEvent", "enabled": true}]' \
  --workspace /subscriptions/.../workspaces/sentinel-workspace

A Sentinel KQL alert for unexpected secret reads:

AzureDiagnostics
| where ResourceProvider == "MICROSOFT.KEYVAULT"
| where OperationName == "SecretGet"
| where ResultType == "Success"
// Alert on access from non-approved identities
| where identity_claim_oid_g !in (approved_principal_ids)
| project TimeGenerated, CallerIPAddress, identity_claim_oid_g, 
          requestUri_s, ResourceId
| sort by TimeGenerated desc

Periodic Access Policy Audit

Run regular reviews of all vault access policies and RBAC assignments. The following identifies managed identities with vault-wide secrets access:

# List all RBAC assignments for a vault with 'Secrets Officer' or 'Secrets User' roles
az role assignment list \
  --scope /subscriptions/.../resourceGroups/.../providers/Microsoft.KeyVault/vaults/myvault \
  --query "[?contains(roleDefinitionName, 'Key Vault Secret')]" \
  -o table

Any assignment scoped to the vault rather than individual secrets should be reviewed for whether secret-level scoping is feasible.

Common Misconfigurations Summary

MisconfigurationRiskFix
Vault access policy modelContributor can grant self secret accessMigrate to Azure RBAC
Managed identity with vault-wide secret readFull vault exfiltration from any RCEScope to individual secrets
Exportable certificatesPrivate key theftSet exportable: false
Public network access enabledAccessible from attacker infrastructurePrivate endpoint + deny public
No diagnostic loggingUndetected secret readsEnable AuditEvent to SIEM
Soft-delete disabledDeleted secrets immediately unrecoverable (no forensics)Enable soft-delete + purge protection

Key Vault secrets are high-value enough that any misconfiguration in their access control tends to appear in the critical or high severity tier of cloud security assessments. The controls above are the ones that eliminate the most common paths to exfiltration — they are not complex to implement and should be treated as baseline requirements rather than security hardening additions.

← All Analysis Subscribe via RSS