GHSA-MR74-928F-RW69
Vulnerability from github – Published: 2026-03-02 20:15 – Updated: 2026-03-05 22:49Summary
When a user creates a public share link for a directory, the withHashFile middleware in http/public.go (line 59) uses filepath.Dir(link.Path) to compute the BasePathFs root. This sets the filesystem root to the parent directory instead of the shared directory itself, allowing anyone with the share link to browse and download files from all sibling directories.
Details
In http/public.go lines 52-64, the withHashFile function handles public share link requests:
basePath := link.Path // e.g. "/documents/shared"
filePath := ""
if file.IsDir {
basePath = filepath.Dir(basePath) // BUG: becomes "/documents" (parent!)
filePath = ifPath
}
d.user.Fs = afero.NewBasePathFs(d.user.Fs, basePath)
When a directory at /documents/shared is shared, filepath.Dir("/documents/shared") evaluates to "/documents". The BasePathFs is then rooted at the parent directory /documents/, giving the share link access to everything under /documents/ - not just the intended /documents/shared/.
This affects both publicShareHandler (directory listing via /api/public/share/{hash}) and publicDlHandler (file download via /api/public/dl/{hash}/path).
PoC
- Set up filebrowser with a user whose scope contains:
-
/documents/shared/public-file.txt(intended to be shared)
-
/documents/secrets/passwords.txt(NOT intended to be shared)
-
/documents/private/financial.csv(NOT intended to be shared)
- Create a public share link for the directory
/documents/shared(via POST/api/share/documents/shared) - Access the share link:
GET /api/public/share/{hash} -
- Expected: Lists only contents of
/documents/shared/
- Expected: Lists only contents of
-
- Actual: Lists contents of
/documents/(parent), revealingsecrets/,private/, andshared/directories
- Actual: Lists contents of
- Download sibling files:
GET /api/public/dl/{hash}/secrets/passwords.txt -
- Expected: 404 or 403 (file outside share scope)
-
- Actual: 200 with file contents (sibling file downloaded successfully)
Standalone Go test reproducing the exact vulnerable code path with
afero.NewBasePathFs:
- Actual: 200 with file contents (sibling file downloaded successfully)
Standalone Go test reproducing the exact vulnerable code path with
func TestShareScopeEscape(t *testing.T) {
baseFs := afero.NewMemMapFs()
afero.WriteFile(baseFs, "/documents/shared/public.txt", []byte("public"), 0644)
afero.WriteFile(baseFs, "/documents/secrets/passwords.txt", []byte("admin:hunter2"), 0644)
linkPath := "/documents/shared"
basePath := filepath.Dir(linkPath) // BUG: "/documents"
scopedFs := afero.NewBasePathFs(baseFs, basePath)
// Sibling file is accessible through the share:
f, err := scopedFs.Open("/secrets/passwords.txt")
// err is nil - file accessible! Content: "admin:hunter2"
}
This test passes, confirming the vulnerability.
Impact
Unauthenticated information disclosure (CWE-200, CWE-706). Anyone with a public share link for a directory can: - Browse all sibling directories and files of the shared directory - - Download any file within the parent directory scope - - This works without authentication (public shares) or after providing the share password (password-protected shares) All filebrowser v2.x installations that use directory sharing are affected.
Recommended Fix
Remove the filepath.Dir() call and use link.Path directly as the BasePathFs root:
if file.IsDir {
// Don't change basePath - keep it as link.Path
filePath = ifPath
}
d.user.Fs = afero.NewBasePathFs(d.user.Fs, basePath)
Affected commit: e3d00d591b567a8bfe3b02e42ba586859002c77d (latest)
File: http/public.go, line 59
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 2.60.0"
},
"package": {
"ecosystem": "Go",
"name": "github.com/filebrowser/filebrowser/v2"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.61.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-28492"
],
"database_specific": {
"cwe_ids": [
"CWE-200"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-02T20:15:46Z",
"nvd_published_at": "2026-03-05T21:16:22Z",
"severity": "HIGH"
},
"details": "### Summary\nWhen a user creates a public share link for a **directory**, the `withHashFile` middleware in `http/public.go` (line 59) uses `filepath.Dir(link.Path)` to compute the `BasePathFs` root. This sets the filesystem root to the **parent directory** instead of the shared directory itself, allowing anyone with the share link to browse and download files from all sibling directories.\n\n### Details\nIn `http/public.go` lines 52-64, the `withHashFile` function handles public share link requests:\n\n```go\nbasePath := link.Path // e.g. \"/documents/shared\"\nfilePath := \"\"\n\nif file.IsDir {\n basePath = filepath.Dir(basePath) // BUG: becomes \"/documents\" (parent!)\n filePath = ifPath\n}\n\nd.user.Fs = afero.NewBasePathFs(d.user.Fs, basePath)\n```\n\nWhen a directory at `/documents/shared` is shared, `filepath.Dir(\"/documents/shared\")` evaluates to `\"/documents\"`. The `BasePathFs` is then rooted at the parent directory `/documents/`, giving the share link access to **everything** under `/documents/` - not just the intended `/documents/shared/`.\n\nThis affects both `publicShareHandler` (directory listing via `/api/public/share/{hash}`) and `publicDlHandler` (file download via `/api/public/dl/{hash}/path`).\n\n### PoC\n\n1. Set up filebrowser with a user whose scope contains:\n2. - `/documents/shared/public-file.txt` (intended to be shared)\n3. - `/documents/secrets/passwords.txt` (NOT intended to be shared)\n4. - `/documents/private/financial.csv` (NOT intended to be shared)\n2. Create a public share link for the directory `/documents/shared` (via POST `/api/share/documents/shared`)\n3. Access the share link: `GET /api/public/share/{hash}`\n4. - **Expected**: Lists only contents of `/documents/shared/`\n5. - **Actual**: Lists contents of `/documents/` (parent), revealing `secrets/`, `private/`, and `shared/` directories\n4. Download sibling files: `GET /api/public/dl/{hash}/secrets/passwords.txt`\n5. - **Expected**: 404 or 403 (file outside share scope)\n6. - **Actual**: 200 with file contents (sibling file downloaded successfully)\n**Standalone Go test** reproducing the exact vulnerable code path with `afero.NewBasePathFs`:\n\n```go\nfunc TestShareScopeEscape(t *testing.T) {\n baseFs := afero.NewMemMapFs()\n afero.WriteFile(baseFs, \"/documents/shared/public.txt\", []byte(\"public\"), 0644)\n afero.WriteFile(baseFs, \"/documents/secrets/passwords.txt\", []byte(\"admin:hunter2\"), 0644)\n\n linkPath := \"/documents/shared\"\n basePath := filepath.Dir(linkPath) // BUG: \"/documents\"\n scopedFs := afero.NewBasePathFs(baseFs, basePath)\n\n // Sibling file is accessible through the share:\n f, err := scopedFs.Open(\"/secrets/passwords.txt\")\n // err is nil - file accessible! Content: \"admin:hunter2\"\n}\n```\n\nThis test passes, confirming the vulnerability.\n\n### Impact\n\n**Unauthenticated information disclosure (CWE-200, CWE-706)**. Anyone with a public share link for a directory can:\n- Browse all sibling directories and files of the shared directory\n- - Download any file within the parent directory scope\n- - This works without authentication (public shares) or after providing the share password (password-protected shares)\nAll filebrowser v2.x installations that use directory sharing are affected.\n\n### Recommended Fix\n\nRemove the `filepath.Dir()` call and use `link.Path` directly as the `BasePathFs` root:\n\n```go\nif file.IsDir {\n // Don\u0027t change basePath - keep it as link.Path\n filePath = ifPath\n}\nd.user.Fs = afero.NewBasePathFs(d.user.Fs, basePath)\n```\n\n**Affected commit**: e3d00d591b567a8bfe3b02e42ba586859002c77d (latest)\n**File**: `http/public.go`, line 59",
"id": "GHSA-mr74-928f-rw69",
"modified": "2026-03-05T22:49:48Z",
"published": "2026-03-02T20:15:46Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/filebrowser/filebrowser/security/advisories/GHSA-mr74-928f-rw69"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-28492"
},
{
"type": "WEB",
"url": "https://github.com/filebrowser/filebrowser/commit/31194fb57a5b92e7155219d7ec7273028fcb2e83"
},
{
"type": "PACKAGE",
"url": "https://github.com/filebrowser/filebrowser"
},
{
"type": "WEB",
"url": "https://github.com/filebrowser/filebrowser/releases/tag/v2.61.0"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "FileBrowser has Path Traversal in Public Share Links that Exposes Files Outside Shared Directory"
}
Sightings
| Author | Source | Type | Date |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.