
Unauthenticated disclosure of internal folder path, client email, and upload policy for FileRise Pro client portals via /api/pro/portals/get.php
get.php)public/api/pro/portals/get.php returns the full internal record of a FileRise Pro client portal — including its internal storage folder path, the client's contact email, and its complete upload policy — to any unauthenticated caller who knows or can guess the portal's slug.
Every sibling endpoint in the same directory (list.php, save.php, listEntries.php, submitForm.php, submissions.php) calls fr_pro_guard_auth(...) before running; get.php is the only exception. The application already ships a deliberately-curated public endpoint for the same slug (publicMeta.php) that returns only branding fields — proving this data is not meant to be exposed pre-authentication.
public/api/pro/portals/get.phpProPortalsApiService::getPortal() → PortalController::getPortalBySlug() (src/FileRise/Http/Controllers/PortalController.php:60)error311/FileRise @ tag v3.23.1 (built from the project's own Dockerfile, fresh first-run setup)get.php runs with no authentication gate:
require_once __DIR__ . '/../_common.php'; require_once PROJECT_ROOT . '/src/FileRise/Domain/ProPortalsApiService.php';
try { $slug = isset($_GET['slug']) ? (string)$_GET['slug'] : ''; fr_pro_emit_result(\FileRise\Domain\ProPortalsApiService::getPortal($slug)); } catch (Throwable $e) { ... }
There is no fr_pro_guard_auth() call anywhere in this file — in contrast with list.php / save.php / listEntries.php, which all call it first.
getPortal() returns the same record an admin sees on the edit portal screen, including sensitive fields:
return [
'slug' => ..., 'label' => ..., 'folder' => $folder, 'clientEmail' => $clientEmail,
'sourceId' => $sourceId, 'uploadOnly' => ..., 'allowDownload' => ...,
'uploadMaxSizeMb' => ..., 'uploadExtWhitelist' => ..., 'uploadMaxPerDay' => ...,
'allowSubfolders' => ..., 'formDefaults' => ..., 'canUpload' => $canUpload, ...
];
For contrast, publicMeta.php — the endpoint the front-end (public/js/portal.js) actually calls for the anonymous portal landing page — loads from a separate, minimal portals.json projection via PortalPublicMetaService::getPublicPortalMeta() and returns only branding fields:
['slug', 'label', 'title', 'introText', 'brandColor', 'footerText', 'logoFile', 'logoUrl']
On the precondition: the attacker only needs to know or guess a portal slug — a short, human-readable, admin-chosen label used directly in the shareable portal URL (/portal/<slug>), not a high-entropy secret. Slugs are routinely shared with clients via email or link, so this is realistically reachable by anyone who has ever received a portal link.
Verified live against error311/FileRise @ v3.23.1, with zero cookies/session on the vulnerable request.
Note on testing: FileRise Pro (the licensed
ProPortals.phpbundle) is a separate paid add-on not included in this repository. The PoC below activates Pro mode with a minimal local test stub implementing only thelistPortals()interface the OSS code already calls (PortalController.php:60→new ProPortals(FR_PRO_BUNDLE_DIR)→->listPortals()). This reproduces exactly the OSS-side code path every real licensed instance runs; no proprietary code was used or required.
1. Build the image:
git clone https://github.com/error311/FileRise.git
cd FileRise
docker build -t filerise-local -f Dockerfile .
2. Minimal local stub to exercise the OSS-side Pro gating code (not the proprietary bundle — just enough to satisfy FR_PRO_ACTIVE / ProPortals::listPortals()):
mkdir -p data/users/pro
cat > data/users/pro/bootstrap_pro.php <<'PHP'
<?php
define('FR_PRO_ACTIVE', true);
PHP
cat > data/users/pro/ProPortals.php <<'PHP'
<?php
class ProPortals {
public function __construct(string $dir) {}
public function listPortals(): array {
return ['client-portal' => [
'label' => 'Client Portal',
'folder' => 'confidential/client-acme-contracts',
'clientEmail' => '[email protected]',
'uploadOnly' => true, 'allowDownload' => false,
'uploadMaxSizeMb' => 25, 'uploadExtWhitelist' => 'pdf,docx', 'uploadMaxPerDay' => 10,
]];
}
}
PHP