Defender — Objective 5: Identify the Public Resource¶
Tracing the Attack Back Further¶
The ListBuckets event used level3 credentials (Objective 4). Working backward:
level3credentials 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
level1credentials 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:
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 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.