flawopen.com/Teardowns/cve-2024-5321-kubernetes-windows-container-log-permissions
CVE-2024-5321: права на журналы контейнеров на Windows-узлах Kubernetes
CVE-2024-5321 (CVSS 6.1, средняя): на Windows-узлах kubelet создавал каталоги журналов подов через os.MkdirAll, который в Windows игнорирует режим доступа, поэтому журналы наследовали ACL, дающий BUILTIN\Users чтение, а Authenticated Users — изменение.
Больница хранит карту каждого пациента в шкафу для документов. Заказывая новый шкаф, сотрудники пишут в бланке «только для врачей», но в этом здании поле «замок» молча игнорируется, и шкаф получает правило комнаты по умолчанию: читать может любой сотрудник, писать — любой с пропуском. Исправление — служащий, который каждому новому шкафу ставит собственный замок и отменяет для него правило комнаты.
Ключевые понятия и термины
DACL / ACE- Дискреционный список управления доступом Windows: записи, которые определяют, какие учётные записи могут читать, изменять или выполнять файл или каталог.
Наследование ACL- По умолчанию новый файл или каталог в Windows копирует наследуемые записи DACL родительского каталога. Защищённый DACL это отключает.
BUILTIN\Users / Authenticated Users- Широкие группы Windows: все локальные учётные записи и все учётные записи, хоть раз вошедшие в систему. Kubernetes сопоставляет контейнеры, работающие как ContainerUser, с BUILTIN\Users при доступе к файлам хоста.
os.MkdirAll в Windows- В Go
os.MkdirAll(path, perm)учитываетpermтолько в Unix. В Windows режим игнорируется, и каталог получает унаследованный ACL.
Анализ первопричины
setupDataDirs() в pkg/kubelet/kubelet.go создавала корень журналов подов (C:\var\log\pods) вызовом os.MkdirAll(dir, 0750). Go игнорирует режим в Windows, поэтому каталог и записанные в него журналы наследовали ACL родителя, дававший BUILTIN\Users чтение, а NT AUTHORITY\Authenticated Users — изменение. PR #126104 добавил в pkg/util/filesystem функции MkdirAll() и Chmod(), которые переводят Unix-режим в явный DACL и применяют его с PROTECTED_DACL_SECURITY_INFORMATION, и перевёл на них каталоги журналов, плагинов и контрольных точек kubelet.
Пошаговый сценарий атаки
Kubelet создаёт корень журналов
На Windows-узле setupDataDirs() вызывает os.MkdirAll(. Значение kl.getPodLogsDir(), 0750)0750 игнорируется, и каталог наследует ACL родителя.
Широкие группы получают доступ
Унаследованные записи дают BUILTIN\Users чтение, а NT AUTHORITY\Authenticated Users — изменение каталогов журналов подов и файлов, которые туда пишет среда выполнения.
Чтение чужих журналов
Любая малопривилегированная локальная учётная запись на узле или нагрузка, смонтировавшая путь журналов через hostPath, читает stdout и stderr других подов, где часто есть токены, строки подключения и данные клиентов.
Подделка улик
Те же учётные записи могут править или обрезать журналы до того, как их заберёт агент доставки, скрывая активность от централизованного логирования.
Исходный код: Уязвимый vs Защищённый вариант
// 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)
}
Чек-лист по защите системы для инженеров
- ✓Обновите kubelet на всех Windows-узлах до 1.30.3, 1.29.7, 1.28.12, 1.27.16 или новее.
- ✓Получите список Windows-узлов командой
kubectl get nodes -l kubernetes.io/и выполните на каждомos=windowsicacls C:\var\log\pods: ни одна запись для BUILTIN\Users или Authenticated Users не должна давать чтение или изменение. - ✓В Go-коде, работающем в Windows, не полагайтесь на аргумент
permуos.Mkdir,os.MkdirAll,os.WriteFileиos.Chmod; задавайте защищённый DACL черезwindowsи проверяйте его в CI на Windows..SetNamedSecurityInfo() - ✓Не монтируйте
C:\var\logи другие пути журналов узла в нагрузки через hostPath, если это не доверенный агент сбора журналов. - ✓Не выводите секреты в stdout и stderr контейнеров и маскируйте токены в журналах приложения: каталоги журналов узла доступны большему числу субъектов, чем сам под.