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.


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

Social

Contact us

Almeda Campinas 802, CJ 12, Jardim Paulista,

São Paulo - SP, 01404-001

Opportunities

Our content

Social

Contact us

Almeda Campinas 802, CJ 12, Jardim Paulista,

São Paulo - SP, 01404-001

Opportunities

Our content

Social

Contact us

Almeda Campinas 802, CJ 12, Jardim Paulista,

São Paulo - SP, 01404-001

Opportunities

Our content