flawopen.com/Teardowns/cve-2024-3177-kubernetes-serviceaccount-envfrom-secrets-bypass
CVE-2024-3177: Kubernetes ServiceAccount Admission envFrom Secrets Bypass
CVE-2024-3177 (CVSS 2.7, Low): the kube-apiserver ServiceAccount admission plugin checked Secret volumes and env valueFrom references against a service account's allow-list, but never looked at envFrom, so a pod could load any Secret in its namespace.
A hotel gives each cleaner a list of the rooms they may enter. The guard at the staff door checks every key on a cleaner's key ring and every single key clipped to their belt against that list. Nobody ever looks inside the zip pouch that holds a whole floor's keys at once. A cleaner cleared only for the lobby walks through with the pouch for the executive floor, and the guard waves them in, because the checklist never mentions pouches.
Core Concepts & Subsystem Terms
ServiceAccount admission plugin- Built-in kube-apiserver admission controller that fills in a pod's service account and, when asked, restricts which Secrets the pod may reference (
plugin/pkg/admission/serviceaccount). kubernetes.io/enforce-mountable-secrets- Annotation on a ServiceAccount. When set to "true", pods running as that account may only reference Secrets listed in the account's
secretsfield. envFrom / secretRef- Container field that imports every key of a Secret as environment variables in one go, unlike
env[].valueFrom.secretKeyRef, which imports a single key. Ephemeral container- A debugging container added to a running pod through the
pods/ephemeralcontainerssubresource. It is admitted separately, bylimitEphemeralContainerSecretReferences().
Root Cause Analysis
limitSecretReferences() in plugin/pkg/admission/serviceaccount/admission.go enforced the mountable-secrets allow-list by walking the pod's Secret volumes and each container's env[].valueFrom.secretKeyRef. It never walked envFrom[].secretRef, which imports a whole Secret. The same gap existed for init containers and, in limitEphemeralContainerSecretReferences(), for ephemeral containers. PR #124322 added the missing envFrom loop in all three places.
Step-by-Step Attack Flow
A restricted service account
The ServiceAccount builder carries kubernetes.io/enforce-mountable-secrets: "true" and lists only build-token in secrets. The same namespace also holds prod-db-credentials.
Pod spec with envFrom
A user who may create pods submits one that runs as builder and sets envFrom: [{secretRef: {name: prod-db-credentials}}] on a container, init container or ephemeral container.
Admission check passes
limitSecretReferences() finds no Secret volume and no env[].valueFrom.secretKeyRef outside the allow-list, so the pod is admitted.
Kubelet injects the Secret
The kubelet resolves envFrom and sets every key of prod-db-credentials as an environment variable, where the container can read it.
Source Code: Flaw vs. Secure Implementation
// 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
}
Engineering & System Hardening Checklist
- ✓Upgrade kube-apiserver to v1.29.4, v1.28.9, v1.27.13 or later; there is no configuration workaround.
- ✓Find the service accounts that rely on the policy:
kubectl get serviceaccounts -Afiltered on thekubernetes.io/enforce-mountable-annotation.secrets=true - ✓Search API audit logs for pod create and ephemeral-container updates whose
envFrom[].secretRef.nameis not in the pod's service accountsecretslist. - ✓Treat permission to create pods in a namespace as read access to that namespace's Secrets; keep sensitive Secrets in namespaces whose pod creators are already trusted with them.
- ✓When an admission check restricts a resource type, enumerate every pod-spec field that can reference it (volumes, projected volumes,
env,envFrom, init and ephemeral containers) and add one test per field.