flawopen.com/Teardowns/cve-2024-3177-kubernetes-serviceaccount-envfrom-secrets-bypass

● CVE-2024-3177 · CVSS 2.7 · Rendah
Riset Keamanan · FlawOpen

CVE-2024-3177: Bypass Batasan Secret lewat envFrom pada Admission ServiceAccount Kubernetes

CVE-2024-3177 (CVSS 2.7, rendah): plugin admission ServiceAccount di kube-apiserver mencocokkan volume Secret dan referensi env valueFrom dengan daftar izin service account, tetapi tidak pernah memeriksa envFrom, sehingga sebuah pod dapat memuat Secret apa pun di namespace-nya.

💡 Penjelasan Sederhana (ELI5)

Sebuah hotel memberi setiap petugas kebersihan daftar kamar yang boleh mereka masuki. Satpam di pintu staf mencocokkan setiap kunci di gantungan dan setiap kunci tunggal yang dijepit di ikat pinggang dengan daftar itu. Tidak ada yang pernah membuka dompet ritsleting berisi semua kunci satu lantai sekaligus. Petugas yang hanya diizinkan ke lobi lewat membawa dompet kunci lantai eksekutif, dan satpam membiarkannya masuk karena daftar periksanya tidak pernah menyebut dompet.

Konsep Kunci & Istilah

ServiceAccount admission plugin
Admission controller bawaan kube-apiserver yang mengisi service account pod dan, bila diminta, membatasi Secret mana yang boleh dirujuk pod (plugin/pkg/admission/serviceaccount).
kubernetes.io/enforce-mountable-secrets
Anotasi pada ServiceAccount. Jika bernilai "true", pod yang berjalan dengan akun itu hanya boleh merujuk Secret yang tercantum di field secrets akun tersebut.
envFrom / secretRef
Field kontainer yang mengimpor semua kunci sebuah Secret sekaligus sebagai variabel lingkungan, berbeda dengan env[].valueFrom.secretKeyRef yang hanya mengimpor satu kunci.
Kontainer ephemeral
Kontainer debug yang ditambahkan ke pod yang sedang berjalan lewat subresource pods/ephemeralcontainers. Admission-nya diperiksa terpisah oleh limitEphemeralContainerSecretReferences().

Analisis Akar Masalah (Root Cause)

limitSecretReferences() di plugin/pkg/admission/serviceaccount/admission.go menegakkan daftar Secret yang boleh dipasang dengan menelusuri volume Secret pod dan env[].valueFrom.secretKeyRef setiap kontainer. Fungsi itu tidak pernah menelusuri envFrom[].secretRef, yang mengimpor Secret secara utuh. Celah yang sama ada pada kontainer init dan, di limitEphemeralContainerSecretReferences(), pada kontainer ephemeral. PR #124322 menambahkan perulangan envFrom yang hilang di ketiga tempat itu.

Alur Serangan Langkah demi Langkah

Langkah 1

Service account yang dibatasi

ServiceAccount builder memiliki kubernetes.io/enforce-mountable-secrets: "true" dan hanya mencantumkan build-token di secrets. Namespace yang sama juga menyimpan prod-db-credentials.

Langkah 2

Spesifikasi pod dengan envFrom

Pengguna yang boleh membuat pod mengirim pod yang berjalan sebagai builder dan memasang envFrom: [{secretRef: {name: prod-db-credentials}}] pada kontainer biasa, init, atau ephemeral.

Langkah 3

Pemeriksaan admission lolos

limitSecretReferences() tidak menemukan volume Secret maupun env[].valueFrom.secretKeyRef di luar daftar izin, sehingga pod diterima.

Langkah 4

Kubelet menyuntikkan Secret

Kubelet menguraikan envFrom dan menjadikan setiap kunci prod-db-credentials sebagai variabel lingkungan yang dapat dibaca kontainer.

Kode Sumber: Rentan vs Aman

IMPLEMENTASI RENTAN
// plugin/pkg/admission/serviceaccount/admission.go (kube-apiserver v1.29.3)
func (s *Plugin) limitSecretReferences(serviceAccount *corev1.ServiceAccount, pod *api.Pod) error {
	// Only allow Secrets that the service account lists in its "secrets" field.
	mountableSecrets := sets.NewString()
	for _, ref := range serviceAccount.Secrets {
		mountableSecrets.Insert(ref.Name)
	}

	for _, volume := range pod.Spec.Volumes {
		source := volume.VolumeSource
		if source.Secret != nil && !mountableSecrets.Has(source.Secret.SecretName) {
			return fmt.Errorf("volume with secret.secretName=%q is not allowed because service account %s does not reference that secret", source.Secret.SecretName, serviceAccount.Name)
		}
	}

	for _, container := range pod.Spec.Containers {
		for _, env := range container.Env {
			if env.ValueFrom != nil && env.ValueFrom.SecretKeyRef != nil {
				if !mountableSecrets.Has(env.ValueFrom.SecretKeyRef.Name) {
					return fmt.Errorf("container %s with envVar %s referencing secret.secretName=%q is not allowed because service account %s does not reference that secret", container.Name, env.Name, env.ValueFrom.SecretKeyRef.Name, serviceAccount.Name)
				}
			}
		}
		// BUG: container.EnvFrom is never inspected. envFrom[].secretRef imports
		// every key of any Secret in the namespace and still passes admission.
	}
	return nil
}
PERBAIKAN AMAN & KUAT
// plugin/pkg/admission/serviceaccount/admission.go (fixed in v1.29.4, PR #124322)
func (s *Plugin) limitSecretReferences(serviceAccount *corev1.ServiceAccount, pod *api.Pod) error {
	// Only allow Secrets that the service account lists in its "secrets" field.
	mountableSecrets := sets.NewString()
	for _, ref := range serviceAccount.Secrets {
		mountableSecrets.Insert(ref.Name)
	}

	for _, volume := range pod.Spec.Volumes {
		source := volume.VolumeSource
		if source.Secret != nil && !mountableSecrets.Has(source.Secret.SecretName) {
			return fmt.Errorf("volume with secret.secretName=%q is not allowed because service account %s does not reference that secret", source.Secret.SecretName, serviceAccount.Name)
		}
	}

	for _, container := range pod.Spec.Containers {
		for _, env := range container.Env {
			if env.ValueFrom != nil && env.ValueFrom.SecretKeyRef != nil {
				if !mountableSecrets.Has(env.ValueFrom.SecretKeyRef.Name) {
					return fmt.Errorf("container %s with envVar %s referencing secret.secretName=%q is not allowed because service account %s does not reference that secret", container.Name, env.Name, env.ValueFrom.SecretKeyRef.Name, serviceAccount.Name)
				}
			}
		}
		// FIX: envFrom can import a whole Secret, so it gets the same allow-list check.
		// The patch adds this loop for init and ephemeral containers too.
		for _, envFrom := range container.EnvFrom {
			if envFrom.SecretRef != nil && !mountableSecrets.Has(envFrom.SecretRef.Name) {
				return fmt.Errorf("container %s with envFrom referencing secret.secretName=%q is not allowed because service account %s does not reference that secret", container.Name, envFrom.SecretRef.Name, serviceAccount.Name)
			}
		}
	}
	return nil
}

Daftar Periksa Penguatan Sistem Rekayasa

Sumber