Re-explorando a CVE-2021-43798 no Grafana

Como explorar, detectar e bloquear com NetworkPolicies e Admission Policies

CTO

João Brito

Directory Traversal pré-auth que lê arquivos locais — exploração, detecção e policies de contenção

TL;DR

Esta é uma re-exploração da CVE-2021-43798 que é um path traversal pre-auth no endpoint /public/plugins/<plugin-id>/... do Grafana 8.x. Um atacante consegue ler arquivos locais do servidor/container (ex.: /etc/grafana/grafana.ini, /var/lib/grafana/grafana.db, /proc/self/environ) usando plugin IDs válidos — muitos vêm por padrão, o que torna a exploração trivial. Estão afetadas as versões 8.0.0-beta1 a 8.3.0; o fix saiu nas 8.0.7 / 8.1.8 / 8.2.7 / 8.3.1. Atualize imediatamente. Se não for possível agora, mitigue com WAF/rewrites bloqueando ../, restrinja exposição, e aplique NetworkPolicies e Admission Policies para reduzir superfície e impacto.

Pode ser que você pense que essa CVE não tem mais sentido, porém ainda existem MUITOS ambientes desatualizados e ao mesmo tempo muitos ataques direcionados a eles, como mostra a greynoise.

Fase 1 — O que é o problema nesta CVE

Resumo técnico do bug

O handler de arquivos estáticos de plugins no Grafana 8.x não sanitiza adequadamente o caminho solicitado. Ao acessar GET /public/plugins/<plugin-id>/../../../../<arquivo>, o servidor retorna conteúdo fora do diretório do plugin, permitindo local file read sem autenticação. O vetor existe para qualquer plugin instalado (inclusive os built-ins).

Por que isso é relevante em 2025?

Apesar de antiga, a falha permaneceu amplamente explorável por muito tempo devido a exposição indevida e patching tardio; análises mostram exploração e probing recorrentes mesmo anos após o fix. Em ambientes onde Grafana tem credenciais de data sources e integrações críticas, a leitura de arquivos locais vira acesso inicial quando combinada com outros passos (ex.: decriptar segredos do grafana.db após obter a secret key).

Linha do tempo e builds corrigidos

A correção foi publicada em 7 de dezembro de 2021 nas versões 8.3.1 / 8.2.7 / 8.1.8 / 8.0.7 e detalhada pela própria Grafana Labs em posts subsequentes.

Fase 2 — O que ela afeta e cenários de exploração

Versões afetadas e correção

  • Afetadas: 8.0.0-beta1 → 8.3.0;

  • Corrigidas: 8.0.7, 8.1.8, 8.2.7, 8.3.1.
    (Atualize para uma dessas séries ou superior.)

Pré-requisitos e superfície de ataque

  • Nenhuma autenticação é necessária;

  • Um plugin-id válido é suficiente (ex.: alertlist, annolist, barchart, bargauge, candlestick, cloudwatch, dashlist, elasticsearch — todos comuns por padrão).

PoC rápida (laboratório)

Atenção: para fins educacionais em ambiente controlado.

# /etc/passwd via plugin built-in
curl -s http://GRAFANA:3000/public/plugins/alertlist/../../../../../../etc/passwd

# URL-encoded (costuma bypassar alguns filtros bobos)
curl -s "http://GRAFANA:3000/public/plugins/alertlist/..%2f..%2f..%2f..%2f..%2fetc%2fpasswd"

# Arquivos sensíveis dentro do container/VM do Grafana
curl -s http://GRAFANA:3000/public/plugins/alertlist/../../../../../../etc/grafana/grafana.ini
curl -s http://GRAFANA:3000/public/plugins/alertlist/../../../../../../var/lib/grafana/grafana.db
curl -s

Referências de módulos/PoCs públicos (para entendimento do vetor): Exploit-DB e módulo do Metasploit.

“E depois que eu leio o grafana.db?”

O Grafana armazena credenciais de data sources cifradas. O modelo atual usa DEKs (chaves de dados) guardadas no banco e protegidas por uma KEK (Key Encryption Key) definida por secret_key (config ou KMS). Se um atacante ler o banco (grafana.db) e a secret_key, há possibilidade de decriptar segredos dependendo da configuração/versão (e.g., quando a secret_key local protege os DEKs). Em setups mal configurados (sem KMS) isso pode expor senhas/tokens de fontes de dados.

Cenários práticos em Kubernetes

  1. Grafana exposto (LoadBalancer/Ingress) sem filtro de IP e sem WAF: leitura de grafana.ini + grafana.db ⇒ extração de credenciais e movimento lateral para bancos/observabilidade;

  2. Grafana in-cluster mas acessível a partir de Pods comprometidos: leitura local e exfiltração via egress aberto;

  3. Variáveis sensíveis em ambiente: leitura de /proc/self/environ pode revelar GF_SECURITY_ADMIN_PASSWORD, chaves de API, tokens de cloud.

Fase 3 — Como corrigir (prioridades e policies)

1) Atualize agora

Faça rollout para uma versão 8.0.7 / 8.1.8 / 8.2.7 / 8.3.1 (ou superior).

Recomendação: Atualize no mínimo para as versões acima e faça rollout controlado. Faça sua subscrição na imagem Grafana sem CVEs em quor.dev.

2) Hardening imediato (se não der para atualizar hoje)

Bloqueio de path traversal no edge (Nginx):

location ~* ^/public/plugins/.*/(\.\.|%2e%2e)/ {
  return 403;
}
# Proteção extra contra segmentos "dot-dot" mesmo sem barra
if ($request_uri ~* "/public/plugins/.*\.\.") { return

Apache (mod_rewrite):

RewriteEngine On
RewriteCond %{REQUEST_URI} ^/public/plugins/ [NC]
RewriteCond %{THE_REQUEST} \.\. [OR]
RewriteCond %{REQUEST_URI} \.\. [OR]
RewriteCond %{QUERY_STRING} \.\.
RewriteRule ^ - [F]

Grafana “atrás” de CDN/WAF: crie regra para negar ../ e %2f.. especificamente sob /public/plugins/.

3) NetworkPolicies (Kubernetes)

Reduza superfícies de ingress e egress do Pod do Grafana:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: grafana-minimal
  namespace: observability
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/name: grafana
  policyTypes: [Ingress, Egress]
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          name: platform
    ports:
    - protocol: TCP
      port: 3000
  egress:
  - to:
    - namespaceSelector:
        matchLabels:
          name: monitoring
    ports:
    - protocol: TCP
      port: 9090   # ex.: Prometheus
  - to:
    - ipBlock:
        cidr: 0.0.0.0/0
    ports:
    - protocol: TCP
      port: 443    # somente o estritamente necessário

Observação: a CVE lê arquivos locais; NetworkPolicy não impede a leitura, mas diminui exfiltração.

4) Admission Policies (CEL) para higiene de workloads

Bloqueie Pods privilegiados e exija digest nas imagens (reduz risco de rollbacks vulneráveis e lateral movement após initial access via Grafana):

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: baseline-security
spec:
  matchConstraints:
    resourceRules:
      - apiGroups: [""]
        apiVersions: ["v1"]
        operations: ["CREATE","UPDATE"]
        resources: ["pods"]
  validations:
    - expression: "object.spec.containers.all(c, c.image.matches('.*@sha256:.*'))"
      message: "Imagem deve estar fixada por digest"
    - expression: "has(object.spec.securityContext) && object.spec.securityContext.runAsNonRoot == true"
      message: "Pods devem rodar como não-root"
    - expression: |
        object.spec.containers.all(c,
          has(c.securityContext) &&
          c.securityContext.privileged != true &&
          c.securityContext.allowPrivilegeEscalation != true)
      message: "Sem privileged e sem allowPrivilegeEscalation"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: baseline-security-binding
spec:
  policyName: baseline-security
  validationActions: ["Deny"

5) Segredos e criptografia do Grafana

  • Rotacione secret_key e re-proteja os DEKs;

  • Considere KMS (ex.: Azure Key Vault) para a KEK, removendo segredo estático no filesystem;

  • Minimize o valor do grafana.db em repouso (credenciais por service accounts de menor privilégio, scopes e tempo de vida curto).

Deteção e resposta

Indicadores em logs (Nginx/Ingress/LB)

  • Padrões em URI/args: ../, %2e%2e, %2f.. sob /public/plugins/<plugin-id>/;

  • Acessos anômalos a /etc/, /proc/, /var/lib/grafana/ via esse endpoint.

Consultas Loki (exemplos):

{app="nginx", namespace="edge"} |= "/public/plugins/" |~ "\\.\\.|%2e%2e"
{app="ingress-nginx"} |= "/public/plugins/" |~ "%2f..|/\\.\\."

Threat hunting específico

  • Leitura de grafana.ini seguida por grafana.db no mesmo IP/ASN;

  • Padrões de varredura por plugin-ids comuns (alertlist, annolist, etc.).

Matriz de risco resumida

Cenário

Exposição

Autenticação

WAF/Rewrite

Risco

Grafana internet-exposed

Alta

Não requer

Ausente

Crítico

Grafana atrás de Ingress sem WAF

Média

Não requer

Parcial

Alto

Grafana apenas in-cluster

Baixa

Não requer

Não aplicável

Médio

Grafana atualizado + WAF + NP + policies

Baixa

Presente

Baixo


Checklist
para o seu time

  • Inventariar versões de Grafana e atualizar para 8.0.7/8.1.8/8.2.7/8.3.1 ou superior;

  • Bloquear ../ em /public/plugins/ no Nginx/Ingress/CDN;

  • Restringir exposição (IPs permitidos, mTLS quando possível);

  • Aplicar NetworkPolicies e Admission Policies base;

  • Rotacionar secret_key e avaliar KMS para dados do Grafana;

  • Alertas no Loki para padrões de travessia;

  • Assessment de plugins instalados (remover o que não é necessário) e revisão de credenciais de data sources.

Referências

  • Advisory/linhas de produtos corrigidos pela Grafana (Dec/2021). (Grafana Labs)

  • Detalhes técnicos oficiais e timeline do 0-day. (Grafana Labs)

  • Atinge 8.0.0-beta1 → 8.3.0; vetor /public/plugins/<plugin-id>/.... (GitHub)

  • Lista de plugin-ids padrão útil para provas de conceito. (SonicWall)

  • Exploit-DB / Metasploit scanner para estudo do vetor. (exploit-db.com)

  • Criptografia de segredos no Grafana (DEKs/KEK secret_key) e uso de KMS. (Grafana Labs)

  • Análise de exploração e contexto de uso como initial access. (SonicWall)


Quer entender como construir um ecossistema de containers sem CVEs? Descubra em quor.dev

Reduza sua superfície de ataque e o

custo de remediação.

Reduza sua superfície de ataque e o custo de remediação.

Com o Quor, segurança vira vantagem competitiva. Descubra como em uma demo personalizada.

Documentação

comercial@quor.dev

Powered by Getup