flawopen.com/Teardowns/cve-2024-5321-kubernetes-windows-container-log-permissions

● CVE-2024-5321 · CVSS 6.1 · 中危
安全研究 · FlawOpen

CVE-2024-5321:Kubernetes Windows 节点容器日志权限问题

CVE-2024-5321(CVSS 6.1,中危):在 Windows 节点上,kubelet 用 os.MkdirAll 创建 Pod 日志目录,而该函数在 Windows 上会忽略权限模式,于是日志继承了一个允许 BUILTIN\Users 读取、Authenticated Users 修改的 ACL。

💡 通俗易懂的原理解析 (ELI5)

医院把每位病人的病历放在档案柜里。员工订购新柜子时会在订单上写“仅限医生”,但这栋楼的系统会悄悄忽略“锁”这一栏,新柜子直接沿用房间的默认规则:任何员工都能看,任何有工牌的人都能往里写。修复办法是安排一名文员:每来一个新柜子,就给它装上专用的锁,并对这个柜子取消房间的默认规则。

核心概念与专有名词

DACL / ACE
Windows 的自主访问控制列表:由若干访问控制项组成,规定哪些账户可以读、写或执行某个文件或目录。
ACL 继承
默认情况下,Windows 新建的文件或目录会复制父目录 DACL 中可继承的条目。设置受保护的 DACL 可以阻止继承。
BUILTIN\Users / Authenticated Users
范围很广的 Windows 组:所有本地账户,以及所有登录过的账户。Kubernetes 在以 ContainerUser 运行的容器访问主机文件时,把它映射为 BUILTIN\Users。
Windows 上的 os.MkdirAll
Go 的 os.MkdirAll(path, perm) 只在 Unix 上使用 perm。在 Windows 上模式被忽略,目录获得继承来的 ACL。

根本原因剖析 (Root Cause)

pkg/kubelet/kubelet.go 中的 setupDataDirs() 用 os.MkdirAll(dir, 0750) 创建 Pod 日志根目录(C:\var\log\pods)。Go 在 Windows 上会忽略模式,因此该目录以及写入其中的日志文件继承了父目录的 ACL,BUILTIN\Users 可读,NT AUTHORITY\Authenticated Users 可改。PR #126104 在 pkg/util/filesystem 中新增 MkdirAll() 和 Chmod(),把 Unix 模式转换成显式 DACL 并以 PROTECTED_DACL_SECURITY_INFORMATION 写入,同时让 kubelet 的日志、插件和检查点目录改用它们。

攻击执行流程分解

步骤 1

kubelet 创建日志根目录

在 Windows 节点上,setupDataDirs() 调用 os.MkdirAll(kl.getPodLogsDir(), 0750)。0750 被忽略,目录继承父目录的 ACL。

步骤 2

大范围的组继承了权限

继承来的条目让 BUILTIN\Users 可以读取、NT AUTHORITY\Authenticated Users 可以修改 Pod 日志目录以及运行时写入其中的文件。

步骤 3

读取其他工作负载的日志

节点上任何低权限的本地账户,或通过 hostPath 挂载了日志路径的工作负载,都能读取其他 Pod 的 stdout 和 stderr,其中常含令牌、连接字符串或客户数据。

步骤 4

篡改证据

同样的账户可以在日志采集器收走日志之前编辑或截断日志文件,从而在集中式日志中隐藏活动。

源代码对比:漏洞与安全实现

存在漏洞的实现
// pkg/kubelet/kubelet.go (kubelet v1.30.2)
func (kl *Kubelet) setupDataDirs() error {
	if err := os.MkdirAll(kl.getRootDir(), 0750); err != nil {
		return fmt.Errorf("error creating root directory: %v", err)
	}
	// BUG on Windows: os.MkdirAll ignores the 0750 mode. The directory inherits
	// its parent's ACL, which lets BUILTIN\Users read and
	// NT AUTHORITY\Authenticated Users modify every pod's log files.
	if err := os.MkdirAll(kl.getPodLogsDir(), 0750); err != nil {
		return fmt.Errorf("error creating pod logs root directory %q: %w", kl.getPodLogsDir(), err)
	}
	if err := os.MkdirAll(kl.getPodsDir(), 0750); err != nil {
		return fmt.Errorf("error creating pods directory: %v", err)
	}
	return nil
}
加固后的安全修复
// pkg/kubelet/kubelet.go (fixed in v1.30.3, PR #126104)
func (kl *Kubelet) setupDataDirs() error {
	if err := os.MkdirAll(kl.getRootDir(), 0750); err != nil {
		return fmt.Errorf("error creating root directory: %v", err)
	}
	// FIX: utilfs.MkdirAll is plain os.MkdirAll on Linux. On Windows it also
	// calls utilfs.Chmod, which replaces the inherited ACL with an explicit one.
	if err := utilfs.MkdirAll(kl.getPodLogsDir(), 0750); err != nil {
		return fmt.Errorf("error creating pod logs root directory %q: %w", kl.getPodLogsDir(), err)
	}
	if err := os.MkdirAll(kl.getPodsDir(), 0750); err != nil {
		return fmt.Errorf("error creating pods directory: %v", err)
	}
	return nil
}

// pkg/util/filesystem/util_windows.go
func MkdirAll(path string, perm os.FileMode) error {
	if err := os.MkdirAll(path, perm); err != nil {
		return fmt.Errorf("Error creating directory %s: %v", path, err)
	}
	return Chmod(path, perm)
}

func Chmod(path string, filemode os.FileMode) error {
	// Maps owner, group and other mode bits to ACEs for the file's owner SID,
	// its group SID and BUILTIN\Users (BU). For 0750 the result is
	// "D:(A;OICI;FA;;;<owner>)(A;OICI;FRFX;;;<group>)(A;OICI;;;;BU)".
	// (Abridged: the real function builds this string inline.)
	dacl, err := daclForMode(path, filemode)
	if err != nil {
		return err
	}
	newSD, err := windows.SecurityDescriptorFromString(dacl)
	if err != nil {
		return fmt.Errorf("Error creating new security descriptor from DACL string: %v", err)
	}
	newDACL, _, err := newSD.DACL()
	if err != nil {
		return fmt.Errorf("Error getting DACL from new security descriptor: %v", err)
	}
	// PROTECTED_DACL_SECURITY_INFORMATION blocks inheritance from the parent,
	// so the BUILTIN\Users and Authenticated Users entries no longer apply.
	return windows.SetNamedSecurityInfo(path, windows.SE_FILE_OBJECT,
		windows.DACL_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION,
		nil, nil, newDACL, nil)
}

工程与系统安全加固清单

参考来源