Skip to content

[Security] Direct-link / short-link / share-download endpoints skip permission & folder-password checks — unauthenticated download of protected files (5.0.0–5.0.5) #843

Description

@doudou868

提交前确认

ZFile 版本

5.0.5

部署方式

源码运行

问题发生在哪些存储源

本地存储

设备类型

PC

运行环境

curl / any browser

问题复现步骤

  1. Complete the install wizard, log in as admin, add a local storage source local01 containing secret/confidential.txt.
  2. Keep default storage permissions (anonymous users are NOT authorized), and set a folder password on /secret.
  3. Control (protections work):
    • Anonymous POST /api/storage/files for local01 -> 41018 unauthorized operation.
    • Listing /secret with a wrong password -> 41021 wrong password.
    • Direct GET /pd/local01/secret/confidential.txt without a signature -> rejected ("invalid signature").
  4. Attack: open a private window (not logged in) and visit:
    http://localhost:8080/directlink/local01/secret/confidential.txt
    -> 302 to /pd/local01/secret/confidential.txt?signature=... (server signs the URL for this anonymous request) -> 200, full contents of the password-protected file.

Same result via short-link generation (POST /api/short-link/batch/generate) and share creation (POST /api/share/create) - see additional context.

预期行为

The direct-link / short-link / share-download endpoints should enforce the same authorization rules as the file-list chain: an anonymous (unauthorized) request for a path inside a password-protected folder, or inside a storage source the user has no AVAILABLE permission for, should be rejected before any signed download URL is issued.

实际行为

ZFile 5.0.0-5.0.5 issues a valid signed download URL (302 to /pd/...?signature=...) and returns the full file contents (200) for anonymous requests to password-protected / unauthorized paths. 100% reproducible.

Root cause: the download link generation chain (LinkDownloadService.handlerDirectLink L66-73 -> handlerDownloadGetUrl L127-204) only checks the showPathLink system switch (default ON) and that the storage source is enabled. Permission checks (FileOperatorCheckAspect) and folder-password verification (FolderPasswordVerifyCommand) only run on the file-list chain (/api/storage/files). Additionally POST /api/short-link/batch/generate is annotated @StoragePermissionCheck(SHARE_LINK) but FileOperatorCheckAspect.annotationCheck (L54-60) only handles LINK-type annotations - SHARE_LINK falls through (annotation present, check empty). And POST /api/share/create (ShareLinkService.validateAndGetFileService L492-511) only validates the storage source exists - anonymous users can create a share pointing at any path.

Note: the AES signature mechanism added in 5.0.0 (fix for #821) guarantees authenticity, not authorization - the issuance endpoints sign download capabilities for anonymous requests to protected paths, so the unauthorized download works while the signature check is functioning correctly.

补充信息

Suggested fix: validate at signature/link issuance time (the three entry points above) that the requesting identity is allowed to access the path - reusing the same FileChain permission/password checks the list endpoint applies - instead of only validating the signature at download time.

Regression test idea: issue a direct link for a password-protected path anonymously and assert it is rejected.

Scope: affects all enabled storage source types (verified on local storage; the check gap is storage-agnostic - it is in the link/service layer, not the storage driver layer). Verified on 5.0.5 (latest release, 2026-08-13); introduced by the 5.0 architecture (4.5.0 and earlier have no proxy-download signature mechanism).

Found during a systematic review of the download chain. Happy to provide more details if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions