
كشف غير مُصادق عليه لمسار المجلد الداخلي، والبريد الإلكتروني للعميل، وسياسة الرفع لبوابات عملاء FileRise Pro عبر /api/pro/portals/get.php
get.php)تُعيد public/api/pro/portals/get.php السجل الداخلي الكامل لبوابة عميل FileRise Pro — بما في ذلك مسار مجلد التخزين الداخلي، والبريد الإلكتروني لجهة اتصال العميل، وسياسة الرفع الكاملة الخاصة بها — إلى أي مُتصل غير مُصادَق يعرف أو يستطيع تخمين الـ slug الخاص بالبوابة.
كل نقطة نهاية شقيقة في نفس الدليل (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 @ tag 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) فعلياً لصفحة هبوط البوابة المجهولة — تُحمّل من إسقاط portals.json منفصل ومختصر عبر PortalPublicMetaService::getPublicPortalMeta() وتُعيد حقول العلامة التجارية فقط:
['slug', 'label', 'title', 'introText', 'brandColor', 'footerText', 'logoFile', 'logoUrl']
بخصوص الشرط المسبق: يحتاج المهاجم فقط إلى معرفة أو تخمين الـ slug الخاص بالبوابة — وهو تسمية قصيرة قابلة للقراءة البشرية يختارها المسؤول وتُستخدم مباشرة في رابط البوابة القابل للمشاركة (/portal/<slug>)، وليس سراً عالي العشوائية. تُشارَك الـ slugs بشكل روتيني مع العملاء عبر البريد الإلكتروني أو الرابط، لذا يمكن الوصول إليها واقعياً من قبل أي شخص تلقّى رابط بوابة في أي وقت.
تم التحقق منه مباشرةً على error311/FileRise @ v3.23.1، بدون أي ملفات تعريف ارتباط/جلسة على الطلب الضعيف.
ملاحظة حول الاختبار: FileRise Pro (حزمة
ProPortals.phpالمرخّصة) هي إضافة مدفوعة منفصلة غير مُضمّنة في هذا المستودع. يُفعّل PoC أدناه وضع Pro باستخدام stub اختباري محلي بسيط يُنفّذ فقط واجهةlistPortals()التي يستدعيها كود OSS بالفعل (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. stub محلي بسيط لاختبار كود بوابة Pro على جانب OSS (ليس الحزمة المملوكة — فقط ما يكفي لتلبية 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