K8S 中对 Windows 节点的利用
在许多组织中,所运行的很大一部分服务和应用是 Windows 应用。 Windows 容器提供了一种封装进程和包依赖项的方式, 从而简化了 DevOps 实践,令 Windows 应用程序同样遵从云原生模式,实际生产中存在很多这样的混合集群。
之前两篇有关 K8S 的文章,分别探索了对于 K8S 本身功能的利用、多种不同 RBAC 权限的利用,但是对于集群中 Windows 节点的情况下并未去研究,刚好近期 K8S 的一些漏洞是针对 Windows 节点。
漏洞概述
近期 kubernetes 中发现了漏洞 CVE-2023-3676,该漏洞可在集群中的 Windows 节点上创建 pod,并在节点上提权到管理员权限。
受影响的版本:
- kubelet <= v1.28.0
- kubelet <= v1.27.4
- kubelet <= v1.26.7
- kubelet <= v1.25.12
- kubelet <= v1.24.16
漏洞详情
该漏洞是一个很典型的命令注入,由于节点上运行的 kubelet 是由本地最高权限即管理员权限,通过创建 pod 使用 kubelet 的权限以达到提权。
命令注入就是将用户提交的数据未经过滤或者过滤不严格,通过拼接将参数传入调用系统命令的函数调用处。
用户在集群中创建 pod 时,可以在主机和 pod 之间创建共享目录就是卷,卷主要用来多个 pod 共享文件,或者持久化容器生命周期内创建或修改的文件。
在 pod 中共享卷是非常方便的,volumeMounts.subPath
属性可用于指定所引用的卷内的子路径,而不是其根路径。
该漏洞就是通过 kubelet 在解析 subPath
参数时,未经处理直接传递给 Powershell
,通过 PS 检查并解析符号链接。
kubelet 在解析 yaml 文件时,会调用函数isLinkPath
来对subPath
进行校验已确保不会因为该参数创建符号链接。
该函数主要讲subPath
作为参数确实是否为符号链接,然后创建一个 PS
命令,并通过exec.Command
之间调用,而在这里并未做其他校验直接进行了命令调用,存在命令注入的可能。、
而 Powershell
中的子表达式运算符$()
返回一个或多个语句的结果。 对于单个结果,返回一个标量。 对于多个结果,返回一个数组。 如果要在另一个表达式中使用某个表达式,请使用此方法。 例如,在字符串表达式中嵌入命令的结果。
1 | PS> "Today is $(Get-Date)" |
除了以上示例外其他任何 PS 命令都可以使用,如$(Start-Process cmd)
、$(Invoke-Expression exp)
等等。
漏洞验证
通过上面分析我们可以构建一个恶意的 pod 进行验证,参考下面的 yaml
1 | apiVersion: v1 |
执行并创建该 pod,并将 cmd 设置为 cs 生成的 ps。
1 | kubectl apply -f CVE-2023-3676.yaml |
由于 subPath 不正确 pod 创建失败,但是命令成功执行,成功上线并且权限也是管理员nt authority\system
,而且由于 pod 创建失败会一直创建不停上线,后续还需优化。
补丁分析
下图为官方对漏洞修复的代码
设置了 $ErrorActionPreference = 'Stop'
:这意味着如果PowerShell
报错就会抛出异常并终止命令的执行,阻碍了攻击者通过错误获得信息。
使用 -Force 和 $env:linkpath:
在命令中使用了 -Force
参数和 $env:linkpath
变量,以确保在获取目标文件时跳过安全限制,并使用环境变量传递路径值,这样作为变量传递的话参数将被视为字符串而不能在计算表达式。
使用 cmd.Env
设置环境变量:将 path 值作为环境变量传递给,而不是直接拼接到命令中,阻止了命令注入的问题。
在集群中探索
结合之前的文章,可以在存在 RBAC 权限符合的情况下,在 Windows 节点上创建恶意的 pod 完成内网横向并且获得权限且是管理员权限,Windows 节点由于该利用会导致无法创建成功。
如果利用 Windows 版本的 kube-proxy 会导致集群出现问题,其中 kube-proxy-windows 会使用到一个 ps 脚本,该脚本存放在 configmap 中,也可以修改该脚本完成横向