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

● CVE-2024-5321 · CVSS 6.1 · Sedang
Riset Keamanan · FlawOpen

CVE-2024-5321: Izin Log Kontainer pada Node Windows Kubernetes

CVE-2024-5321 (CVSS 6.1, sedang): pada node Windows, kubelet membuat direktori log pod dengan os.MkdirAll, yang mengabaikan mode izin di Windows, sehingga log mewarisi ACL yang membolehkan BUILTIN\Users membacanya dan Authenticated Users mengubahnya.

💡 Penjelasan Sederhana (ELI5)

Sebuah rumah sakit menyimpan rekam medis setiap pasien di lemari arsip. Saat memesan lemari baru, staf menulis "khusus dokter" di formulir, tetapi di gedung ini kolom kunci diabaikan diam-diam, jadi lemari itu mengikuti aturan bawaan ruangan: semua karyawan boleh membaca dan siapa pun yang punya kartu akses boleh menulis. Perbaikannya adalah petugas yang, setiap kali lemari baru datang, memasang kunci khusus dan membuang aturan bawaan ruangan untuk lemari itu.

Konsep Kunci & Istilah

DACL / ACE
Daftar kontrol akses diskresioner Windows: entri-entri yang menentukan akun mana yang boleh membaca, menulis, atau menjalankan sebuah file atau direktori.
Pewarisan ACL
Secara bawaan, file atau direktori baru di Windows menyalin entri yang dapat diwarisi dari DACL direktori induknya. DACL yang dilindungi mencegah hal itu.
BUILTIN\Users / Authenticated Users
Grup Windows yang sangat luas: semua akun lokal, dan semua akun yang pernah log on. Kubernetes memetakan kontainer yang berjalan sebagai ContainerUser ke BUILTIN\Users saat mengakses file host.
os.MkdirAll di Windows
os.MkdirAll(path, perm) di Go hanya memakai perm di Unix. Di Windows mode itu diabaikan dan direktori mendapat ACL warisan.

Analisis Akar Masalah (Root Cause)

setupDataDirs() di pkg/kubelet/kubelet.go membuat akar log pod (C:\var\log\pods) dengan os.MkdirAll(dir, 0750). Go mengabaikan mode di Windows, sehingga direktori itu dan file log yang ditulis di dalamnya mewarisi ACL induk, yang memberi BUILTIN\Users akses baca dan NT AUTHORITY\Authenticated Users akses ubah. PR #126104 menambahkan MkdirAll() dan Chmod() di pkg/util/filesystem yang menerjemahkan mode Unix menjadi DACL eksplisit dan menerapkannya dengan PROTECTED_DACL_SECURITY_INFORMATION, lalu memindahkan direktori log, plugin, dan checkpoint kubelet ke fungsi tersebut.

Alur Serangan Langkah demi Langkah

Langkah 1

Kubelet membuat akar log

Di node Windows, setupDataDirs() memanggil os.MkdirAll(kl.getPodLogsDir(), 0750). Nilai 0750 diabaikan dan direktori mewarisi ACL induknya.

Langkah 2

Grup luas mewarisi akses

Entri warisan memberi BUILTIN\Users akses baca dan NT AUTHORITY\Authenticated Users akses ubah atas direktori log pod dan file yang ditulis runtime ke sana.

Langkah 3

Membaca log beban kerja lain

Akun lokal berhak rendah mana pun di node, atau beban kerja yang memasang jalur log lewat hostPath, dapat membaca stdout dan stderr pod lain, yang sering berisi token, string koneksi, atau data pelanggan.

Langkah 4

Merusak bukti

Akun yang sama dapat mengedit atau memotong file log sebelum dikirim oleh agen log, sehingga aktivitas tersembunyi dari logging terpusat.

Kode Sumber: Rentan vs Aman

IMPLEMENTASI RENTAN
// 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
}
PERBAIKAN AMAN & KUAT
// 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)
}

Daftar Periksa Penguatan Sistem Rekayasa

Sumber