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 से बनाता था, जो Windows पर अनुमति मोड को अनदेखा करता है; इसलिए लॉग को ऐसी ACL विरासत में मिलती थी जिससे BUILTIN\Users उन्हें पढ़ सकते थे और Authenticated Users बदल सकते थे।

💡 आसान भाषा में (ELI5)

एक अस्पताल हर मरीज़ की फ़ाइल एक अलमारी में रखता है। नई अलमारी मँगाते समय कर्मचारी ऑर्डर फ़ॉर्म पर "सिर्फ़ डॉक्टर" लिखते हैं, लेकिन इस इमारत में ताले वाला खाना चुपचाप अनदेखा हो जाता है और अलमारी कमरे का डिफ़ॉल्ट नियम ले लेती है: कोई भी कर्मचारी पढ़ सकता है और बैज वाला कोई भी व्यक्ति उसमें लिख सकता है। सुधार यह है कि एक क्लर्क हर नई अलमारी पर अपना अलग ताला लगाए और उस अलमारी के लिए कमरे का डिफ़ॉल्ट नियम हटा दे।

इस पेज के मुख्य शब्द

DACL / ACE
Windows की डिस्क्रेशनरी एक्सेस कंट्रोल लिस्ट: वे एंट्री जो तय करती हैं कि कौन से अकाउंट किसी फ़ाइल या डायरेक्टरी को पढ़, लिख या चला सकते हैं।
ACL इनहेरिटेंस
डिफ़ॉल्ट रूप से Windows में नई फ़ाइल या डायरेक्टरी अपनी पैरेंट डायरेक्टरी की DACL की इनहेरिट होने लायक एंट्री कॉपी कर लेती है। प्रोटेक्टेड DACL इसे रोकती है।
BUILTIN\Users / Authenticated Users
Windows के बहुत बड़े समूह: सभी लोकल अकाउंट, और हर वह अकाउंट जिसने कभी लॉग ऑन किया हो। ContainerUser के रूप में चलने वाले कंटेनर जब होस्ट फ़ाइलें छूते हैं तो Kubernetes उन्हें BUILTIN\Users मानता है।
Windows पर os.MkdirAll
Go का os.MkdirAll(path, perm) केवल Unix पर perm का उपयोग करता है। Windows पर मोड अनदेखा होता है और डायरेक्टरी को विरासत वाली ACL मिलती है।

मूल कारण विश्लेषण (Root Cause)

pkg/kubelet/kubelet.go में setupDataDirs() पॉड लॉग की रूट डायरेक्टरी (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 की लॉग, प्लगइन और चेकपॉइंट डायरेक्टरी को इन्हीं पर ले आया।

हमले का चरण-दर-चरण प्रवाह

चरण 1

kubelet लॉग रूट बनाता है

Windows नोड पर setupDataDirs() os.MkdirAll(kl.getPodLogsDir(), 0750) कॉल करता है। 0750 अनदेखा होता है और डायरेक्टरी पैरेंट की ACL ले लेती है।

चरण 2

बड़े समूहों को एक्सेस मिल जाता है

विरासत में मिली एंट्री BUILTIN\Users को पढ़ने और NT AUTHORITY\Authenticated Users को बदलने का अधिकार देती हैं, पॉड लॉग डायरेक्टरी पर भी और रनटाइम द्वारा उनमें लिखी फ़ाइलों पर भी।

चरण 3

दूसरे वर्कलोड के लॉग पढ़ना

नोड पर कोई भी कम अधिकार वाला लोकल अकाउंट, या hostPath से लॉग पाथ माउंट करने वाला वर्कलोड, दूसरे पॉड का 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)
}

इंजीनियरिंग और सिस्टम सुरक्षा चेकलिस्ट

स्रोत