Skip to content

Defender — Objective 5: Identify the Public Resource

Tracing the Attack Back Further

The ListBuckets event used level3 credentials (Objective 4). Working backward:

  • level3 credentials came from the ECS container's metadata endpoint — stolen via SSRF
  • The attacker got into the ECS app using credentials found in the Docker image
  • The Docker image came from ECR, accessed using level1 (Lambda) credentials
  • The attacker had level1 credentials because Lambda leaked them in an error response

The ECR image was a critical pivot. Let's check why the attacker could access it.


Checking CloudTrail for ECR Access

The earlier events from the level1 role:

2018-11-28T23:05:53Z  104.102.221.250  level1/level1  ListImages
2018-11-28T23:06:17Z  104.102.221.250  level1/level1  BatchGetImage
2018-11-28T23:06:33Z  104.102.221.250  level1/level1  GetDownloadUrlForLayer

The ListImages event's requestParameters:

{
    "repositoryName": "level2",
    "registryId": "653711331788"
}

The attacker pulled the level2 ECR image. But did they even need the level1 credentials for this? Let's check the repository's policy.


Checking the ECR Repository Policy

aws --profile target_security ecr get-repository-policy --repository-name level2

aws ecr get-repository-policy — fetches the resource-based policy attached to an ECR repository.

Part What it does
aws ecr get-repository-policy Get the policy controlling who can access this ECR repo
--repository-name level2 The ECR repository to check
--profile target_security Run against the Target account

Resource-based policy vs IAM policy — what's the difference?

Type Attached to Controls
IAM policy Identity (user/role) What that identity can do across all resources
Resource-based policy Resource (S3 bucket, ECR repo, Lambda, etc.) Who can access this specific resource, regardless of their IAM policies

ECR repo policies are resource-based — they define who from outside the account (or inside) can pull images. S3 bucket policies work the same way.


Reading the Policy

Raw response:

{
    "policyText": "{\"Version\":\"2008-10-17\",\"Statement\":[...escaped...]}",
    "repositoryName": "level2"
}

The policyText field is JSON-encoded-as-a-string — the JSON policy is escaped and wrapped in quotes. To read it:

aws --profile target_security ecr get-repository-policy --repository-name level2 | jq '.policyText|fromjson'

jq '.policyText|fromjson' breakdown:

Part What it does
.policyText Extract the policyText field (currently a JSON string)
\|fromjson Parse that string as JSON — converts "{ \"key\": \"val\" }" into { "key": "val" }

Result:

{
    "Version": "2008-10-17",
    "Statement": [{
        "Sid": "AccessControl",
        "Effect": "Allow",
        "Principal": "*",
        "Action": [
            "ecr:GetDownloadUrlForLayer",
            "ecr:BatchGetImage",
            "ecr:BatchCheckLayerAvailability",
            "ecr:ListImages",
            "ecr:DescribeImages"
        ]
    }]
}


The Misconfiguration: Principal: "*"

Principal: "*" means the wildcard principal — anyone in the world. No authentication required. Any AWS account, or anyone with the AWS CLI or SDK, can call ListImages, BatchGetImage, GetDownloadUrlForLayer on this repository.

This means: - The attacker didn't even need the stolen level1 credentials to pull the image - The ECR repository was independently vulnerable — a separate misconfiguration from the Lambda credential leak - The attack could have started directly from the public ECR repo without any prior credential theft

Principal: "*" vs Principal: {"AWS": "*"}:

Value What it means
"*" Any principal at all — including unauthenticated requests
{"AWS": "*"} Any authenticated AWS identity (any IAM user/role from any account)
{"AWS": "arn:aws:iam::123456789012:root"} Only this specific AWS account
{"Service": "ecs-tasks.amazonaws.com"} Only the ECS service

In ECR, even {"AWS": "*"} is bad — it means any random AWS account can pull your images. "*" is worse — it's truly public.


Finding All Public Resources Proactively

In a real engagement, you'd scan for public resources before tracing the specific attack:

Tools: - CloudMapper — maps out all public resources in an AWS account - Prowler — runs hundreds of security checks including public S3, public ECR, public RDS, etc. - AWS Config Rules — s3-bucket-public-read-prohibited, ecr-private-image-scanning-enabled, etc. - AWS Security Hub — aggregates findings from Config, GuardDuty, Inspector into a single view

Manual check for public ECR repos:

# List all repos
aws --profile target_security ecr describe-repositories

# Check policy on each
aws --profile target_security ecr get-repository-policy --repository-name <name> | jq '.policyText|fromjson'

Any repo with Principal: "*" or Principal: {"AWS": "*"} is either misconfigured or intentionally public — either way, worth reviewing.