EN

Re-exploring CVE-2021-43798 in Grafana

How to explore, detect, and block with NetworkPolicies and Admission Policies

CTO

João Brito

Pre-auth Directory Traversal reading local files — exploitation, detection, and mitigation policies

TL;DR

This is a re-exploitation of CVE-2021-43798, which is a pre-auth path traversal in the endpoint /public/plugins/<plugin-id>/... of Grafana 8.x. An attacker can read local files from the server/container (e.g., /etc/grafana/grafana.ini, /var/lib/grafana/grafana.db, /proc/self/environ) using valid plugin IDs — many of which come by default, making exploitation trivial. Versions 8.0.0-beta1 to 8.3.0 are affected; the fix was released in 8.0.7 / 8.1.8 / 8.2.7 / 8.3.1. Update immediately. If that is not possible right now, mitigate using WAF/rewrites blocking ../, restrict exposure, and apply NetworkPolicies and Admission Policies to reduce surface area and impact.

You might think this CVE no longer makes sense, but there are still MANY outdated environments and, at the same time, many targeted attacks against them, as shown by greynoise.

Phase 1 — What the problem is in this CVE

Technical summary of the bug

The static file handler for plugins in Grafana 8.x does not properly sanitize the requested path. When accessing GET /public/plugins/<plugin-id>/../../../../<file>, the server returns content outside the plugin directory, allowing unauthenticated local file read. The vector exists for any installed plugin (including built-ins).

Why is this relevant in 2025?

Despite being old, the flaw remained widely exploitable for a long time due to improper exposure and delayed patching; analysis shows recurring exploitation and probing even years after the fix. In environments where Grafana holds credentials for critical data sources and integrations, reading local files becomes initial access when combined with other steps (e.g., decrypting secrets from grafana.db after obtaining the secret key).

Timeline and fixed builds

The fix was published on December 7, 2021, in versions 8.3.1 / 8.2.7 / 8.1.8 / 8.0.7 and detailed by Grafana Labs itself in subsequent posts.

Phase 2 — What it affects and exploitation scenarios

Affected versions and fix

  • Affected: 8.0.0-beta1 → 8.3.0;

  • Fixed: 8.0.7, 8.1.8, 8.2.7, 8.3.1.
    (Update to one of these series or higher.)

Prerequisites and attack surface

  • No authentication is required;

  • A valid plugin-id is sufficient (e.g., alertlist, annolist, barchart, bargauge, candlestick, cloudwatch, dashlist, elasticsearch — all common by default).

Quick PoC (lab)

Warning: for educational purposes in a controlled environment.

# /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

References to public modules/PoCs (to understand the vector): Exploit-DB and Metasploit module.

“And what do I do after reading grafana.db?”

Grafana stores encrypted data source credentials. The current model uses DEKs (data keys) kept in the database and protected by a KEK (Key Encryption Key) defined by secret_key (config or KMS). If an attacker reads the database (grafana.db) and the secret_key, it is possible to decrypt secrets depending on the configuration/version (e.g., when the local secret_key protects the DEKs). In misconfigured setups (without KMS) this can expose database passwords/tokens.

Practical scenarios in Kubernetes

  1. Exposed Grafana (LoadBalancer/Ingress) without IP filtering and without WAF: reading grafana.ini + grafana.db ⇒ credential extraction and lateral movement to databases/observability platforms;

  2. Grafana in-cluster but accessible from compromised Pods: local reading and exfiltration via open egress;

  3. Sensitive environment variables: reading /proc/self/environ can reveal GF_SECURITY_ADMIN_PASSWORD, API keys, and cloud tokens.

Phase 3 — How to fix (priorities and policies)

1) Update now

Roll out to version 8.0.7 / 8.1.8 / 8.2.7 / 8.3.1 (or higher).

Recommendation: Upgrade to at least the versions above and perform a controlled rollout. Subscribe to the Grafana image without CVEs at quor.dev.

2) Immediate Hardening (if migration is not possible today)

Blocking path traversal at the 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 “behind” CDN/WAF: create a rule to deny ../ and %2f.. specifically under /public/plugins/.

3) NetworkPolicies (Kubernetes)

Reduce ingress and egress surfaces of the Grafana Pod:

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

Note: the CVE reads local files; NetworkPolicy does not prevent file reading but reduces exfiltration.

4) Admission Policies (CEL) for workload hygiene

Block privileged Pods and require image digest (reduces the risk of vulnerable rollbacks and lateral movement after 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) Grafana Secrets and Encryption

  • Rotate secret_key and re-protect DEKs;

  • Consider KMS (e.g., Azure Key Vault) for KEK, removing static secrets from the filesystem;

  • Minimize the value of grafana.db at rest (credentials used by lowest-privilege service accounts, short scopes, and lifespan).

Detection and Response

Indicators in logs (Nginx/Ingress/LB)

  • Patterns in URI/args: ../, %2e%2e, %2f.. under /public/plugins/<plugin-id>/;

  • Anomalous access to /etc/, /proc/, /var/lib/grafana/ via this endpoint.

Loki Queries (examples):

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

Specific threat hunting

  • Reading grafana.ini followed by grafana.db from the same IP/ASN;

  • Scan patterns for common plugin-ids (alertlist, annolist, etc.).

Risk matrix summary

Scenario

Exposure

Authentication

WAF/Rewrite

Risk

Grafana internet-exposed

High

Not required

Absent

Critical

Grafana behind Ingress without WAF

Medium

Not required

Partial

High

Grafana in-cluster only

Low

Not required

Not applicable

Medium

Updated Grafana + WAF + NP + policies

Low

Present

Low


Checklist for your team

  • Inventory Grafana versions and update to 8.0.7/8.1.8/8.2.7/8.3.1 or higher;

  • Block ../ in /public/plugins/ inside Nginx/Ingress/CDN;

  • Restrict exposure (allowlisted IPs, mTLS when possible);

  • Apply NetworkPolicies and baseline Admission Policies;

  • Rotate secret_key and evaluate KMS for Grafana data;

  • Set up alerts in Loki for path traversal patterns;

  • Conduct an assessment of installed plugins (remove what is not needed) and review data source credentials.

References

  • Advisory/corrected product lines by Grafana (Dec/2021). (Grafana Labs)

  • Official technical details and timeline of the 0-day. (Grafana Labs)

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

  • List of default plugin-ids useful for proofs of concept. (SonicWall)

  • Exploit-DB / Metasploit scanner to study the vector. (exploit-db.com)

  • Enrypting secrets in Grafana (DEKs/KEK secret_key) and KMS usage. (Grafana Labs)

  • Exploitation analysis and context of usage for initial access. (SonicWall)


Want to understand how to build a container ecosystem without CVEs? Discover at quor.dev

Newsletter Getup.

Atualizações sobre Kubernetes e Software Supply Chain Security todos os meses.

Operating Kubernetes in production for more than 13 years. With Quor, this experience extends to software supply chain security as well.