flawopen.com/Teardowns/cve-2024-5321-kubernetes-windows-container-log-permissions
CVE-2024-5321: permisos de los registros de contenedores en nodos Windows de Kubernetes
CVE-2024-5321 (CVSS 6.1, media): en nodos Windows, el kubelet creaba los directorios de registros de los pods con os.MkdirAll, que ignora el modo de permisos en Windows; los registros heredaban una ACL que permitía a BUILTIN\Users leerlos y a Authenticated Users modificarlos.
Un hospital guarda la historia clínica de cada paciente en un archivador. Al pedir un archivador nuevo, el personal escribe "solo médicos" en el formulario, pero en este edificio el campo de la cerradura se ignora sin avisar y el archivador se queda con la regla por defecto de la sala: cualquier empleado puede leerlo y cualquiera con tarjeta puede escribir en él. La solución es un administrativo que, cada vez que llega un archivador, le pone su propia cerradura y anula para ese mueble la regla por defecto de la sala.
Conceptos Clave y Términos
DACL / ACE- Lista de control de acceso discrecional de Windows: las entradas que indican qué cuentas pueden leer, escribir o ejecutar un archivo o directorio.
Herencia de ACL- Por defecto, un archivo o directorio nuevo en Windows copia las entradas heredables de la DACL de su directorio padre. Una DACL protegida lo impide.
BUILTIN\Users / Authenticated Users- Grupos amplios de Windows: todas las cuentas locales y todas las cuentas que han iniciado sesión alguna vez. Kubernetes asocia los contenedores que se ejecutan como ContainerUser a BUILTIN\Users cuando acceden a archivos del host.
os.MkdirAll en Windowsos.MkdirAll(path, perm)de Go solo usapermen Unix. En Windows el modo se ignora y el directorio recibe la ACL heredada.
Análisis de Causa Raíz
setupDataDirs() en pkg/kubelet/kubelet.go creaba la raíz de registros de los pods (C:\var\log\pods) con os.MkdirAll(dir, 0750). Go ignora el modo en Windows, así que el directorio y los archivos de registro escritos en él heredaban la ACL del padre, que daba lectura a BUILTIN\Users y modificación a NT AUTHORITY\Authenticated Users. El PR #126104 añadió MkdirAll() y Chmod() en pkg/util/filesystem, que traducen el modo Unix a una DACL explícita aplicada con PROTECTED_DACL_SECURITY_INFORMATION, y cambió a ellas los directorios de registros, plugins y checkpoints del kubelet.
Flujo de Ataque Paso a Paso
El kubelet crea la raíz de registros
En un nodo Windows, setupDataDirs() llama a os.MkdirAll(. El kl.getPodLogsDir(), 0750)0750 se ignora y el directorio hereda la ACL de su padre.
Grupos amplios heredan acceso
Las entradas heredadas dan lectura a BUILTIN\Users y modificación a NT AUTHORITY\Authenticated Users sobre los directorios de registros de los pods y los archivos que el runtime escribe en ellos.
Lectura de registros ajenos
Cualquier cuenta local con pocos privilegios en el nodo, o una carga con la ruta de registros montada por hostPath, lee el stdout y el stderr de otros pods, que a menudo contienen tokens, cadenas de conexión o datos de clientes.
Manipulación de las pruebas
Las mismas cuentas pueden editar o truncar archivos de registro antes de que el agente de envío los recoja, ocultando actividad al registro centralizado.
Código Fuente: Vulnerable vs. Seguro
// 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)
}
Lista de Verificación de Seguridad para Ingeniería
- ✓Actualice el kubelet de todos los nodos Windows a 1.30.3, 1.29.7, 1.28.12, 1.27.16 o posterior.
- ✓Liste los nodos Windows con
kubectl get nodes -l kubernetes.io/y ejecuteos=windowsicacls C:\var\log\podsen cada uno: ninguna entrada de BUILTIN\Users o Authenticated Users debe conceder lectura o modificación. - ✓En código Go que se ejecute en Windows, no confíe nunca en el argumento
permdeos.Mkdir,os.MkdirAll,os.WriteFileuos.Chmod; establezca una DACL protegida conwindowsy compruébela en un trabajo de CI en Windows..SetNamedSecurityInfo() - ✓No monte
C:\var\logni otras rutas de registros del nodo en cargas mediante hostPath, salvo que la carga sea un agente de registros de confianza. - ✓Mantenga los secretos fuera del stdout y el stderr de los contenedores y oculte los tokens en los registros de la aplicación, porque los directorios de registros del nodo los puede leer más gente que el propio pod.