CVE-2025-1974 - Kubernetes/Ingress-NGINX O que você precisa saber

Overview

CVE-2025-1974 - O que você precisa saber

Olá! Para quem não sabe, eu era um dos mantenedores do Ingress NGINX até o fim do ano de 2024.

Eu percebi que ouve um barulho bem grande do recente CVE-2025-1974 e fiz uma live com o meu amigo Jeferson da Linuxtips para explicar um pouco melhor, da perspectiva de um mantenedor, o que aconteceu, o motivo de ter acontecido e o que está sendo feito.

Você pode encontrar a live aqui. Durante a live, eu usei um documento explicando alguns detalhes, bem como mostrei uma PoC de um webhook vulnerável, e a idéia é deixar aqui disponível para quem quiser entender melhor.

Recap de Ingress controllers e Ingress NGINX

Antes, vamos dar uma olhada no diagrama do Ingress NGINX:

Ingress NGINX

Nesse diagrama, é importante mencionar:

  • As setas em vermelho representam o tráfego de um usuário ou admin do Kubernetes, executando ações como “kubectl”, bem como tráfego entre o apiserver e o controller chamando os validation webhooks, bem como do controller para a API para ler os objetos de ingress, secrets e finalmente para configurar o arquivo nginx.conf do nosso proxy
  • As setas em azul representam o tráfego do seu usuário, ou seja, quando você publica um Ingress, por onde seus usuários acessam o proxy e finalmente chegam nos Pods.

É importante mencionar que todas as vulnerabilidades são exploradas através do tráfego em vermelho. Ninguém chegando no seu Proxy/NGINX, nas portas 80/443 consegue explorar as vulnerabilidades a não ser que alguém tenha criado um Ingress malicioso.

Sobre permissões de acesso

O Ingress controller precisa de uma série de acessos para funcionar, tais como:

  • Ingress
  • Secret
  • Configmap
  • Node
  • Namespaces

Por esse motivo, ao explorar uma vulnerabilidade no Ingress NGINX, veremos sempre que a principal preocupação é a extração de dados, uma vez que essas secrets contém certificados digitais, ou uma secret pode conter um token de acesso ao cluster.

Você encontra as permissões necessárias para o Ingress NGINX funcionar verificando o ClusterRole utilizado.

Por exemplo, digamos que alguém conseguiu explorar o seu Ingress NGINX e consegue executar comandos lá. Uma chamada poderia ser feita, com o Token da ServiceAccount do Ingress NGINX para listar todas as secrets do cluster:

1kubectl exec -it -n ingress-nginx deploy/ingress-nginx-controller -- /bin/sh
2... Aguarde o terminal abrir...
3curl -k -H \
4    "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \ 
5    https://kubernetes.default/api/v1/namespaces/kube-system/secrets

Quando executamos esse comando, vemos que todas as secrets do namespace kube-system podem ser listadas com o token da service account Ingress NGINX

E onde está o problema?

Historicamente, o objeto Ingress no Kubernetes foi criado de forma muito simples. Isso fez com que a adição de novas funcionalidades fosse feita através de Annotations.

Por exemplo, um objeto simples de Ingress que implementa rewrite e catch all:

 1apiVersion: networking.k8s.io/v1
 2kind: Ingress
 3metadata:
 4  name: test-ingress
 5  annotations:
 6    nginx.ingress.kubernetes.io/use-regex: "true"
 7spec:
 8  ingressClassName: nginx
 9  rules:
10  - host: test.com
11    http:
12      paths:
13      - path: /foo/.*
14        pathType: ImplementationSpecific
15        backend:
16          service:
17            name: test
18            port:
19              number: 80

O problema de se utilizar annotations, é que é muito difícil validar todas as entradas. Assim, passar strings com quebras de linha, aspas, e outras tentativas de modificar a configuração final do NGINX são possíveis. O Ingress NGINX possui alguns mecanismos de defesa para isso, como a Validação de annotations que utiliza regexes para saber se o que está sendo inserido realmente faz parte daquela funcionalidade, bem como é uma tentativa de bloquear eventuais tentativas de configurações maliciosas.

Mas nem sempre é possível… No Ingress NGINX, nós usamos um recurso da linguagem “Go” (que é a linguagem em que o Ingress Controller é desenvolvido), chamado de templates.

Basicamente, toda vez que alguém altera um Ingress, adicionando uma annotation, criando um novo serviço, todas as informações são lidas e renderizadas no arquivo nginx.conf. A falha em validar todas essas informações pode levar um usuário malicioso a escrever configurações arbitrárias no nginx.conf.

Olhando a lista de CVEs recentes, podemos ver que existe um padrão: Injeção de configuração

CVEDescriçãoCausa principalScore
CVE-2021-25748Bypass de validação do campo ‘path’Falha na validação de input de annotation / injection de configuração6.5
CVE-2022-4886Bypass de validação do campo ‘path’Falha na validação de input de annotation / injection de configuração8.8
CVE-2023-5043Injeção de configurações via snippet annotationFalha na validação de input de annotation / injection de configuração7.6
CVE-2024-7646Bypass do validador de annotationsFalha na validação de input de annotation / injection de configuração8.8
CVE-2025-24513Auth secret file path traversal vulnerabilityFalha na validação de input do secret / injection de configuração4.8
CVE-2025-24514Configuration injection via unsanitized auth-url annotationFalha na validação de input de annotation / injection de configuração8.8
CVE-2025-1097Configuration injection via unsanitized auth-tls-match-cn annotationFalha na validação de input de annotation / injection de configuração8.8
CVE-2025-1098Configuration injection via unsanitized mirror annotationsFalha na validação de input de annotation / injection de configuração8.8
CVE-2025-1974ingress-nginx admission controller RCE escalationFalha na validação + execução de código não validado durante o teste do admission webhook9.8

Mas por que score 9.8? É possível mesmo um usuário não autenticado explorar?

Sim e não. O que acontece é que a vulnerabilidade CVE-2025-1974 trata de uma falha no Webhook de validação.

Por exemplo, ao tentarmos criar um Ingress conforme abaixo, um erro irá acontecer pois trata-se de um ingress duplicado:

 1apiVersion: networking.k8s.io/v1
 2kind: Ingress
 3metadata:
 4  name: test-ingress1
 5spec:
 6  ingressClassName: nginx
 7  rules:
 8  - host: test.com
 9    http:
10      paths:
11      - path: /foo/
12        pathType: Prefix
13        backend:
14          service:
15            name: test
16            port:
17              number: 80
18---
19apiVersion: networking.k8s.io/v1
20kind: Ingress
21metadata:
22  name: test-ingress2
23spec:
24  ingressClassName: nginx
25  rules:
26  - host: test.com
27    http:
28      paths:
29      - path: /foo/
30        pathType: Prefix
31        backend:
32          service:
33            name: test
34            port:
35              number: 80

e executar

1kubectl apply -f ingress-dup.yaml
2ingress.networking.k8s.io/test-ingress1 applied
3Error from server (BadRequest): error when creating "ingress-dup.yaml": admission webhook "validate.nginx.ingress.kubernetes.io" denied the request: host "test.com" and path "/foo/" is already defined in ingress default/test-ingress1

O webhook do Ingress NGINX é usado para, entre outras coisas:

  • Validar se o path no spec é válido
  • Validar se não existe uma tentativa de criar um objeto de Ingress duplicado (mesmo host, mesmo path), em namespaces iguais ou diferentes
  • Validar se a configuração solicitada não vai quebrar o NGINX, rodando um nginx -t na configuração renderizada

Esse último ítem é onde o problema é apresentado:

Um usuário com acesso ao endpoint do webhook de validação pode mandar uma request maliciosa para executar comandos no nginx

Então, primeiro ponto: você precisa ter acesso à API de Webhook, que não fica exposta de forma autenticada para fora do cluster ou na API do Kubernetes. Mas… se você tem acesso ao shell de um Pod rodando no Cluster, e não tem uma Network Policy que bloqueie seus Pods de acessar o endpoint do Webhook, as requisições podem ser feitas desse Pod.

E como isso acontece? Me de exemplos!

Vou demonstrar com um webhook de validação. Caso você tenha curiosidade em saber como criar um controller e webhooks, veja esse vídeo da V Körbes em uma live

Supondo que tenhamos o seguinte código vulnerável abaixo:

 1func (v *YoloCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
 2	yolo, ok := obj.(*vaicorinthiansv1.Yolo)
 3	if !ok {
 4		return nil, fmt.Errorf("expected a Yolo object but got %T", obj)
 5	}
 6	yololog.Info("Validation for Yolo upon creation", "name", yolo.GetName())
 7
 8	path := fmt.Sprintf("%s/%s", yolo.Namespace, yolo.Spec.Foo)
 9	content, err := os.ReadFile(path)
10	if err != nil {
11		return nil, fmt.Errorf("erro ao ler o objeto yolo: %w", err)
12	}
13
14	if string(content) != "xpto" {
15		return nil, fmt.Errorf("o conteúdo do objeto yolo não é o esperado: %s", string(content))
16	}
17
18	return nil, nil
19}

Basicamente, esse código:

  • Recebe a requisição de criação do objeto yolo.vaicorinthians.rkatz.xyz
  • Concatena o namespace e o campo .spec.foo em uma string, que representa o caminho de um arquivo
  • Tenta ler o arquivo
  • Caso a tentativa de ler o arquivo retorne um erro, retorna erro na validação
  • Caso o conteúdo do arquivo não seja xpto, retorna um erro contendo o valor do arquivo
  • Caso contrário, autoriza a requisição

Ao criarmos o manifesto abaixo, receberemos um erro:

1kind: Yolo
2apiVersion: vaicorinthians.rkatz.xyz/v1
3metadata:
4  name: blo
5  namespace: default
6spec:
7  foo: bar

e executarmos:

1kubectl apply -f ~/tmp/ingresscve/yolo.yaml
2Error from server (Forbidden): error when creating "/Users/rkatz/tmp/ingresscve/yolo.yaml": admission webhook "vyolo-v1.kb.io" denied the request: erro ao ler o objeto yolo: open default/bar: no such file or directory

Opa, então quer dizer que eu posso tentar por exemplo um ataque de path transversal, usando o namespace?

Vamos tentar novamente, com o manifesto vulnerável abaixo:

1kind: Yolo
2apiVersion: vaicorinthians.rkatz.xyz/v1
3metadata:
4  name: blo
5  namespace: /var/run/secrets/kubernetes.io/serviceaccount/
6spec:
7  foo: token

Ao tentarmos com esse manifesto, um erro irá ocorrer. Isso porque o Kubernetes faz uma validação inicial para verificar se o campo namespace contém apenas caracteres válidos:

1kubectl apply -f ~/tmp/ingresscve/yolo-vuln.yaml
2error: error when retrieving current configuration of:
3Resource: "vaicorinthians.rkatz.xyz/v1, Resource=yolos", GroupVersionKind: "vaicorinthians.rkatz.xyz/v1, Kind=Yolo"
4Name: "blo", Namespace: "/var/run/secrets/kubernetes.io/serviceaccount/"
5from server for: "/Users/rkatz/tmp/ingresscve/yolo-vuln.yaml": invalid namespace "/var/run/secrets/kubernetes.io/serviceaccount/": [may not contain '/']

Mas existe um problema: o CVE menciona um acesso não autenticado ao webhook. Usando uma ferramenta chamada kube-review podemos criar uma solicitação de review, e encaminhar direto ao nosso webhook:

1kube-review create yolo-vuln.yaml > admission-yolo.json

Esse comando irá criar um payload JSON que pode ser enviado ao nosso webhook. Eu não irei colar o payload, mas você pode encontrar um similar no Github de um dos engenheiros da Wiz que reportou o CVE.

Logo, dentro de um Pod no meu cluster Kubernetes, caso eu execute o comando abaixo, eu estou enviando esse payload diretamente para o meu Webhook:

1curl -k -v https://webhooktest-webhook-service.webhooktest-system/validate-vaicorinthians-rkatz-xyz-v1-yolo -H "Content-Type: application/json" -d '@admission-yolo.json'

E o resultado? Eu acabei de conseguir o token de Service Account do meu controller, que pode ser usado para extrair informações:

1{"kind":"AdmissionReview","apiVersion":"admission.k8s.io/v1","response":{"allowed":false,"status":{"metadata":{},"message":"o conteúdo do objeto yolo não é o esperado: <<token-de-acesso>>","reason":"Forbidden","code":403}}}

Conclusões

A comunidade vem trabalhando para corrigir essas vulnerabilidades, bem como em um novo projeto chamado InGate cujo objetivo é reimplementar as funcionalidades do Ingress NGINX, porém usando o Gateway API e aplicando algumas das melhores práticas que não estavam disponíveis quando o Ingress NGINX foi criado, lá em 2014.

Porém esse não é um trabalho simples. A maioria dos mantenedores Open Source fazem isso em seu tempo livre, de forma voluntária. Por isso, seja legal com sua comunidade Open Source, e lembre-se de ter empatia, são seres humanos também :)

Adicionalmente, algumas medidas podem ser tomadas, como:

  • Sempre, SEMPRE leia o Changelog dos componentes que você usa, podem existir Breaking Changes
  • Evite ao máximo habilitar o uso de annotations -snippet no Ingress NGINX. Quando você habilita essas annotations, você basicamente está permitindo que seus usuários escrevam diretamente no arquivo nginx.conf sem qualquer validação
  • Algumas medidas defensivas como mecanismos de validação de annotations, ou um novo mecanismo de renderização de templates estão sendo criados. É importante verificar a documentação do Ingress NGINX para saber mais sobre as opções disponíveis
  • Evite permitir aos seus usuários fazer configurações diretas no Kubernetes. Utilize-se ao invés disso de workflows, CI/CD, habilite a validação dos manifestos antes de permitir que o pipeline aplique eles
  • Use mecanismos de allow-list para as annotations, de forma a permitir que seus usuários utilizem apenas funcionalidades que são realmente necessárias, e diminuindo a superfície de ataques
  • NetworkPolicies e outros recursos são seus amigos!

Referências e Acknowledges

Antes de mais nada, o grande acknowledge vai para a comunidade Ingress NGINX e Kubernetes. Lidar com esse CVE não foi fácil para eles, ainda mais com o ruído e repercursão que teve, e ainda assim eles trabalharam incessantemente por 3 meses para corrigir os problemas

Eu mencionei durante a live a existência de outros Ingress Controllers também, e eu tenho um carinho especial pelo Ingress HAProxy de meu amigo João Morais. Esse Ingress foi criado pelo João na época que trabalhavamos juntos no SERPRO para lidar com o volume de tráfego dos ambientes, e está lá até hoje atendendo suas requisições nos sistemas do Governo Brasileiro.

A Wiz foi extremamente responsável no reporte da vulnerabilidade e nos auxiliou em todas as etapas, eles escreveram um report muito bom que pode ser encontrado aqui