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

● CVE-2024-3177 · CVSS 2.7 · Low
FlawOpen Security Research

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.

💡 Plain English Explainer (ELI5)

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 secrets field.
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/ephemeralcontainers subresource. It is admitted separately, by limitEphemeralContainerSecretReferences().

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

Step 1

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.

Step 2

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.

Step 3

Admission check passes

limitSecretReferences() finds no Secret volume and no env[].valueFrom.secretKeyRef outside the allow-list, so the pod is admitted.

Step 4

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

UNPATCHED FLAW
// 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
}
HARDENED SECURE PATCH
// 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

Sources