flawopen.com/Teardowns/cve-2024-3177-kubernetes-serviceaccount-envfrom-secrets-bypass
CVE-2024-3177: bypass de Secrets via envFrom na admissão de ServiceAccount do Kubernetes
CVE-2024-3177 (CVSS 2.7, baixa): o plugin de admissão ServiceAccount do kube-apiserver conferia volumes de Secret e referências env valueFrom contra a lista permitida da conta de serviço, mas nunca olhava envFrom, então um pod podia carregar qualquer Secret do seu namespace.
Um hotel dá a cada camareira a lista dos quartos em que ela pode entrar. O segurança da porta de serviço confere cada chave do chaveiro e cada chave avulsa presa ao cinto com essa lista. Ninguém nunca abre a bolsinha com zíper que guarda todas as chaves de um andar de uma vez. Uma camareira liberada só para o saguão passa com a bolsinha do andar executivo, e o segurança deixa entrar, porque a lista de verificação nunca fala de bolsinhas.
Conceitos Centrais e Termos
ServiceAccount admission plugin- Controlador de admissão embutido no kube-apiserver que preenche a conta de serviço do pod e, quando solicitado, restringe quais Secrets o pod pode referenciar (
plugin/pkg/admission/serviceaccount). kubernetes.io/enforce-mountable-secrets- Anotação em uma ServiceAccount. Com o valor "true", pods que rodam com essa conta só podem referenciar os Secrets listados no campo
secretsda conta. envFrom / secretRef- Campo do contêiner que importa todas as chaves de um Secret como variáveis de ambiente de uma só vez, ao contrário de
env[].valueFrom.secretKeyRef, que importa uma única chave. Contêiner efêmero- Contêiner de depuração adicionado a um pod em execução pelo subrecurso
pods/ephemeralcontainers. Ele é admitido separadamente, porlimitEphemeralContainerSecretReferences().
Análise de Causa Raiz
limitSecretReferences() em plugin/pkg/admission/serviceaccount/admission.go aplicava a lista de Secrets montáveis percorrendo os volumes de Secret do pod e o env[].valueFrom.secretKeyRef de cada contêiner. Nunca percorria envFrom[].secretRef, que importa um Secret inteiro. A mesma lacuna existia para contêineres init e, em limitEphemeralContainerSecretReferences(), para contêineres efêmeros. O PR #124322 acrescentou o laço envFrom que faltava nos três lugares.
Fluxo de Ataque Passo a Paso
Uma conta de serviço restrita
A ServiceAccount builder tem kubernetes.io/enforce-mountable-secrets: "true" e lista apenas build-token em secrets. O mesmo namespace também guarda prod-db-credentials.
Especificação de pod com envFrom
Um usuário que pode criar pods envia um que roda como builder e define envFrom: [{secretRef: {name: prod-db-credentials}}] em um contêiner, contêiner init ou contêiner efêmero.
A verificação de admissão passa
limitSecretReferences() não encontra volume de Secret nem env[].valueFrom.secretKeyRef fora da lista permitida, então o pod é admitido.
O kubelet injeta o Secret
O kubelet resolve envFrom e define cada chave de prod-db-credentials como variável de ambiente, onde o contêiner pode lê-la.
Código-Fonte: Vulnerável vs. Seguro
// 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
}
Lista de Verificação de Segurança para Engenharia
- ✓Atualize o kube-apiserver para v1.29.4, v1.28.9, v1.27.13 ou posterior; não existe contorno por configuração.
- ✓Encontre as contas de serviço que dependem da política:
kubectl get serviceaccounts -Afiltrado pela anotaçãokubernetes.io/enforce-mountable-.secrets=true - ✓Procure nos logs de auditoria da API criações de pod e atualizações de contêiner efêmero cujo
envFrom[].secretRef.namenão esteja na listasecretsda conta de serviço do pod. - ✓Trate a permissão de criar pods em um namespace como acesso de leitura aos Secrets desse namespace; mantenha Secrets sensíveis em namespaces cujos criadores de pods já sejam confiáveis para eles.
- ✓Quando uma verificação de admissão restringir um tipo de recurso, enumere todos os campos da especificação do pod que podem referenciá-lo (volumes, volumes projetados,
env,envFrom, contêineres init e efêmeros) e crie um teste por campo.