Divulgação não autenticada do caminho de pasta interna, e-mail do cliente e política de upload para portais de clientes do FileRise Pro via /api/pro/portals/get.php
get.php)public/api/pro/portals/get.php retorna o registro interno completo de um portal de cliente do FileRise Pro — incluindo o caminho da pasta de armazenamento interna, o e-mail de contato do cliente e sua política de upload completa — para qualquer chamador não autenticado que conheça ou consiga adivinhar o slug do portal.
Todos os endpoints irmãos no mesmo diretório (list.php, save.php, listEntries.php, submitForm.php, submissions.php) chamam fr_pro_guard_auth(...) antes de executar; get.php é a única exceção. A aplicação já possui um endpoint público deliberadamente curado para o mesmo slug (publicMeta.php) que retorna apenas campos de branding — provando que esses dados não devem ser expostos antes da autenticação.
public/api/pro/portals/get.phpProPortalsApiService::getPortal() → PortalController::getPortalBySlug() (src/FileRise/Http/Controllers/PortalController.php:60)error311/FileRise @ tag v3.23.1 (compilado a partir do próprio Dockerfile do projeto, configuração inicial limpa)get.php é executado sem nenhuma barreira de autenticação:
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) { ... }
Não há nenhuma chamada a fr_pro_guard_auth() em nenhum lugar deste arquivo — em contraste com list.php / save.php / listEntries.php, que todos a chamam primeiro.
getPortal() retorna o mesmo registro que um administrador vê na tela de editar portal, incluindo campos sensíveis:
return [
'slug' => ..., 'label' => ..., 'folder' => $folder, 'clientEmail' => $clientEmail,
'sourceId' => $sourceId, 'uploadOnly' => ..., 'allowDownload' => ...,
'uploadMaxSizeMb' => ..., 'uploadExtWhitelist' => ..., 'uploadMaxPerDay' => ...,
'allowSubfolders' => ..., 'formDefaults' => ..., 'canUpload' => $canUpload, ...
];
Em contraste, publicMeta.php — o endpoint que o front-end (public/js/portal.js) realmente chama para a página de destino anônima do portal — carrega a partir de uma projeção separada e mínima de portals.json via PortalPublicMetaService::getPublicPortalMeta() e retorna apenas campos de branding:
['slug', 'label', 'title', 'introText', 'brandColor', 'footerText', 'logoFile', 'logoUrl']
Sobre a pré-condição: o atacante só precisa conhecer ou adivinhar um slug de portal — um rótulo curto, legível por humanos, escolhido pelo administrador e usado diretamente na URL compartilhável do portal (/portal/<slug>), não um segredo de alta entropia. Slugs são rotineiramente compartilhados com clientes por e-mail ou link, então isso é realisticamente alcançável por qualquer pessoa que já tenha recebido um link de portal.
Verificado ao vivo contra error311/FileRise @ v3.23.1, com zero cookies/sessão na requisição vulnerável.
Nota sobre testes: O FileRise Pro (o pacote licenciado
ProPortals.php) é um add-on pago separado não incluído neste repositório. O PoC abaixo ativa o modo Pro com um stub de teste local mínimo implementando apenas a interfacelistPortals()que o código OSS já chama (PortalController.php:60→new ProPortals(FR_PRO_BUNDLE_DIR)→->listPortals()). Isso reproduz exatamente o caminho de código do lado OSS que toda instância licenciada real executa; nenhum código proprietário foi usado ou necessário.
1. Compile a imagem:
git clone https://github.com/error311/FileRise.git
cd FileRise
docker build -t filerise-local -f Dockerfile .
2. Stub local mínimo para exercitar o código de gating Pro do lado OSS (não o pacote proprietário — apenas o suficiente para satisfazer 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