Divulgación no autenticada de la ruta de carpeta interna, el correo electrónico del cliente y la política de carga para los portales de clientes de FileRise Pro a través de /api/pro/portals/get.php
get.php)public/api/pro/portals/get.php devuelve el registro interno completo de un portal de cliente de FileRise Pro —incluyendo la ruta de su carpeta de almacenamiento interna, el correo electrónico de contacto del cliente y su política de carga completa— a cualquier llamador no autenticado que conozca o pueda adivinar el slug del portal.
Todos los endpoints hermanos en el mismo directorio (list.php, save.php, listEntries.php, submitForm.php, submissions.php) llaman a fr_pro_guard_auth(...) antes de ejecutarse; get.php es la única excepción. La aplicación ya incluye un endpoint público deliberadamente curado para el mismo slug (publicMeta.php) que devuelve únicamente campos de marca, lo que demuestra que estos datos no están destinados a exponerse antes de la autenticación.
public/api/pro/portals/get.phpProPortalsApiService::getPortal() → PortalController::getPortalBySlug() (src/FileRise/Http/Controllers/PortalController.php:60)error311/FileRise @ tag v3.23.1 (compilado desde el propio Dockerfile del proyecto, configuración inicial en un primer arranque limpio)get.php se ejecuta sin ninguna barrera de autenticación:
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) { ... }
No hay ninguna llamada a fr_pro_guard_auth() en este archivo —en contraste con list.php / save.php / listEntries.php, que la llaman primero.
getPortal() devuelve el mismo registro que un administrador ve en la pantalla de edición del portal, incluyendo campos sensibles:
return [
'slug' => ..., 'label' => ..., 'folder' => $folder, 'clientEmail' => $clientEmail,
'sourceId' => $sourceId, 'uploadOnly' => ..., 'allowDownload' => ...,
'uploadMaxSizeMb' => ..., 'uploadExtWhitelist' => ..., 'uploadMaxPerDay' => ...,
'allowSubfolders' => ..., 'formDefaults' => ..., 'canUpload' => $canUpload, ...
];
Como contraste, publicMeta.php —el endpoint que el front-end (public/js/portal.js) realmente llama para la página de aterrizaje anónima del portal— carga desde una proyección separada y mínima de portals.json mediante PortalPublicMetaService::getPublicPortalMeta() y devuelve únicamente campos de marca:
['slug', 'label', 'title', 'introText', 'brandColor', 'footerText', 'logoFile', 'logoUrl']
Sobre la precondición: el atacante solo necesita conocer o adivinar un slug de portal —una etiqueta corta, legible por humanos y elegida por el administrador, usada directamente en la URL compartible del portal (/portal/<slug>), no un secreto de alta entropía. Los slugs se comparten habitualmente con los clientes por correo electrónico o enlace, por lo que esto es realísticamente alcanzable por cualquiera que haya recibido alguna vez un enlace de portal.
Verificado en vivo contra error311/FileRise @ v3.23.1, sin cookies ni sesión en la solicitud vulnerable.
Nota sobre las pruebas: FileRise Pro (el paquete licenciado
ProPortals.php) es un complemento de pago separado que no está incluido en este repositorio. La PoC a continuación activa el modo Pro con un stub de prueba local mínimo que implementa únicamente la interfazlistPortals()que el código OSS ya llama (PortalController.php:60→new ProPortals(FR_PRO_BUNDLE_DIR)→->listPortals()). Esto reproduce exactamente la ruta de código del lado OSS que ejecuta toda instancia real con licencia; no se utilizó ni se requirió código propietario.
1. Compilar la imagen:
git clone https://github.com/error311/FileRise.git
cd FileRise
docker build -t filerise-local -f Dockerfile .
2. Stub local mínimo para ejercitar el código de control Pro del lado OSS (no el paquete propietario —solo lo suficiente para satisfacer 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