flawopen.com/Teardowns/cve-2024-5321-kubernetes-windows-container-log-permissions
CVE-2024-5321: permissões dos logs de contêineres em nós Windows do Kubernetes
CVE-2024-5321 (CVSS 6.1, média): em nós Windows, o kubelet criava os diretórios de log dos pods com os.MkdirAll, que ignora o modo de permissão no Windows; os logs herdavam uma ACL que permitia a BUILTIN\Users lê-los e a Authenticated Users modificá-los.
Um hospital guarda o prontuário de cada paciente em um arquivo de aço. Ao pedir um arquivo novo, a equipe escreve "somente médicos" no formulário, mas neste prédio o campo da fechadura é ignorado sem aviso, e o arquivo fica com a regra padrão da sala: qualquer funcionário pode ler e qualquer pessoa com crachá pode escrever. A correção é um funcionário que, a cada arquivo novo, instala uma fechadura própria e descarta a regra padrão da sala para aquele móvel.
Conceitos Centrais e Termos
DACL / ACE- Lista de controle de acesso discricionário do Windows: as entradas que dizem quais contas podem ler, gravar ou executar um arquivo ou diretório.
Herança de ACL- Por padrão, um arquivo ou diretório novo no Windows copia as entradas herdáveis da DACL do diretório pai. Uma DACL protegida impede isso.
BUILTIN\Users / Authenticated Users- Grupos amplos do Windows: todas as contas locais e todas as contas que já fizeram logon. O Kubernetes mapeia contêineres que rodam como ContainerUser para BUILTIN\Users quando acessam arquivos do host.
os.MkdirAll no Windows- O
os.MkdirAll(path, perm)do Go só usapermno Unix. No Windows o modo é ignorado e o diretório recebe a ACL herdada.
Análise de Causa Raiz
setupDataDirs() em pkg/kubelet/kubelet.go criava a raiz de logs dos pods (C:\var\log\pods) com os.MkdirAll(dir, 0750). O Go ignora o modo no Windows, então o diretório e os arquivos de log gravados nele herdavam a ACL do pai, que dava leitura a BUILTIN\Users e modificação a NT AUTHORITY\Authenticated Users. O PR #126104 adicionou MkdirAll() e Chmod() em pkg/util/filesystem, que convertem o modo Unix em uma DACL explícita aplicada com PROTECTED_DACL_SECURITY_INFORMATION, e passou os diretórios de log, plugins e checkpoints do kubelet a usá-las.
Fluxo de Ataque Passo a Paso
O kubelet cria a raiz de logs
Em um nó Windows, setupDataDirs() chama os.MkdirAll(. O kl.getPodLogsDir(), 0750)0750 é ignorado e o diretório herda a ACL do pai.
Grupos amplos herdam acesso
As entradas herdadas dão leitura a BUILTIN\Users e modificação a NT AUTHORITY\Authenticated Users nos diretórios de log dos pods e nos arquivos que o runtime grava neles.
Leitura de logs de outras cargas
Qualquer conta local de baixo privilégio no nó, ou uma carga com o caminho de logs montado via hostPath, lê o stdout e o stderr de outros pods, que muitas vezes trazem tokens, strings de conexão ou dados de clientes.
Adulteração das evidências
As mesmas contas podem editar ou truncar arquivos de log antes que o coletor os envie, escondendo atividade do logging centralizado.
Código-Fonte: Vulnerável 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 Verificação de Segurança para Engenharia
- ✓Atualize o kubelet de todos os nós Windows para 1.30.3, 1.29.7, 1.28.12, 1.27.16 ou posterior.
- ✓Liste os nós Windows com
kubectl get nodes -l kubernetes.io/e rodeos=windowsicacls C:\var\log\podsem cada um: nenhuma entrada de BUILTIN\Users ou Authenticated Users deve conceder leitura ou modificação. - ✓Em código Go que roda no Windows, nunca confie no argumento
permdeos.Mkdir,os.MkdirAll,os.WriteFileouos.Chmod; defina uma DACL protegida comwindowse verifique-a em um job de CI Windows..SetNamedSecurityInfo() - ✓Não monte
C:\var\lognem outros caminhos de log do nó em cargas via hostPath, a menos que a carga seja um agente de logs confiável. - ✓Mantenha segredos fora do stdout e do stderr dos contêineres e mascare tokens no logging da aplicação, pois os diretórios de log do nó são legíveis por mais gente do que o próprio pod.