flawopen.com/Teardowns/cve-2024-3177-kubernetes-serviceaccount-envfrom-secrets-bypass
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.
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
secretsakun tersebut. envFrom / secretRef- Field kontainer yang mengimpor semua kunci sebuah Secret sekaligus sebagai variabel lingkungan, berbeda dengan
env[].valueFrom.secretKeyRefyang hanya mengimpor satu kunci. Kontainer ephemeral- Kontainer debug yang ditambahkan ke pod yang sedang berjalan lewat subresource
pods/ephemeralcontainers. Admission-nya diperiksa terpisah olehlimitEphemeralContainerSecretReferences().
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
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.
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.
Pemeriksaan admission lolos
limitSecretReferences() tidak menemukan volume Secret maupun env[].valueFrom.secretKeyRef di luar daftar izin, sehingga pod diterima.
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
// 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
}
// 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
- ✓Perbarui kube-apiserver ke v1.29.4, v1.28.9, v1.27.13 atau lebih baru; tidak ada solusi sementara lewat konfigurasi.
- ✓Temukan service account yang bergantung pada kebijakan ini:
kubectl get serviceaccounts -Adifilter pada anotasikubernetes.io/enforce-mountable-.secrets=true - ✓Cari di log audit API pembuatan pod dan pembaruan kontainer ephemeral yang
envFrom[].secretRef.name-nya tidak ada di daftarsecretsservice account pod tersebut. - ✓Anggap izin membuat pod di sebuah namespace sebagai akses baca ke Secret namespace itu; simpan Secret sensitif hanya di namespace yang pembuat pod-nya memang sudah dipercaya untuk Secret tersebut.
- ✓Saat pemeriksaan admission membatasi suatu jenis sumber daya, daftarkan setiap field spesifikasi pod yang dapat merujuknya (volume, volume terproyeksi,
env,envFrom, kontainer init dan ephemeral) dan tulis satu tes per field.