Divulgation non authentifiée du chemin de dossier interne, de l'e-mail client et de la politique de téléversement pour les portails clients FileRise Pro via /api/pro/portals/get.php
get.php)public/api/pro/portals/get.php renvoie l'enregistrement interne complet d'un portail client FileRise Pro — y compris le chemin de son dossier de stockage interne, l'e-mail de contact du client et sa politique de téléversement complète — à tout appelant non authentifié qui connaît ou peut deviner le slug du portail.
Chaque point de terminaison frère dans le même répertoire (list.php, save.php, listEntries.php, submitForm.php, submissions.php) appelle fr_pro_guard_auth(...) avant de s'exécuter ; get.php est la seule exception. L'application fournit déjà un point de terminaison public délibérément restreint pour le même slug (publicMeta.php) qui ne renvoie que les champs de branding — prouvant que ces données ne sont pas censées être exposées avant authentification.
public/api/pro/portals/get.phpProPortalsApiService::getPortal() → PortalController::getPortalBySlug() (src/FileRise/Http/Controllers/PortalController.php:60)error311/FileRise @ tag v3.23.1 (construit à partir du Dockerfile du projet lui-même, configuration initiale fraîche)get.php s'exécute sans aucune barrière d'authentification :
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) { ... }
Il n'y a aucun appel à fr_pro_guard_auth() nulle part dans ce fichier — contrairement à list.php / save.php / listEntries.php, qui l'appellent tous en premier.
getPortal() renvoie le même enregistrement qu'un administrateur voit sur l'écran modifier le portail, y compris les champs sensibles :
return [
'slug' => ..., 'label' => ..., 'folder' => $folder, 'clientEmail' => $clientEmail,
'sourceId' => $sourceId, 'uploadOnly' => ..., 'allowDownload' => ...,
'uploadMaxSizeMb' => ..., 'uploadExtWhitelist' => ..., 'uploadMaxPerDay' => ...,
'allowSubfolders' => ..., 'formDefaults' => ..., 'canUpload' => $canUpload, ...
];
Par contraste, publicMeta.php — le point de terminaison que le front-end (public/js/portal.js) appelle réellement pour la page d'accueil anonyme du portail — charge depuis une projection portals.json séparée et minimale via PortalPublicMetaService::getPublicPortalMeta() et ne renvoie que les champs de branding :
['slug', 'label', 'title', 'introText', 'brandColor', 'footerText', 'logoFile', 'logoUrl']
Sur la précondition : l'attaquant a seulement besoin de connaître ou deviner un slug de portail — un libellé court, lisible par l'homme, choisi par l'administrateur, utilisé directement dans l'URL partageable du portail (/portal/<slug>), et non un secret à haute entropie. Les slugs sont régulièrement partagés avec les clients par e-mail ou par lien, ce qui est donc réalistement accessible par quiconque a déjà reçu un lien de portail.
Vérifié en direct contre error311/FileRise @ v3.23.1, avec zéro cookie/session sur la requête vulnérable.
Note sur les tests : FileRise Pro (le bundle sous licence
ProPortals.php) est un add-on payant séparé non inclus dans ce dépôt. Le PoC ci-dessous active le mode Pro avec un stub de test local minimal implémentant uniquement l'interfacelistPortals()que le code OSS appelle déjà (PortalController.php:60→new ProPortals(FR_PRO_BUNDLE_DIR)→->listPortals()). Cela reproduit exactement le chemin de code côté OSS que chaque instance réellement sous licence exécute ; aucun code propriétaire n'a été utilisé ou requis.
1. Construire l'image :
git clone https://github.com/error311/FileRise.git
cd FileRise
docker build -t filerise-local -f Dockerfile .
2. Stub local minimal pour exercer le code de gating Pro côté OSS (pas le bundle propriétaire — juste assez pour satisfaire 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