No rate limiting/lockout on unauthenticated share password bcrypt check (brute-force + CPU-exhaustion)
medium Possibly Valid medium confidence
Status
Possibly Valid
The missing anti-automation control on an unauthenticated bcrypt password check is definitively present in source (CWE-307-class), but exploitable reachability depends on the runtime existence of a password-protected share and attacker knowledge of its high-entropy hash, so possibly_valid with the reachability precondition named.
Repository / Component
Plain-English Description
For password-protected public share links, anyone who has the link can try passwords as fast as the server can check them, because there is no limit, delay, or lockout on failed attempts. This allows both password guessing and turning cheap requests into heavy server CPU load.
Description of the Underlying Issue
authenticateShareRequest (http/public.go:129-154) verifies a share password by reading the attacker-supplied X-SHARE-PASSWORD header (:138) and calling bcrypt.CompareHashAndPassword(l.PasswordHash, password) (:146). This branch runs only for password-protected shares (l.PasswordHash != ''; :130-132 returns early otherwise, and a matching ?token= also bypasses at :134-135). It is reached without authentication via GET /api/public/dl and /api/public/share (withHashFile, public.go:18-91). There is no rate limiting, throttling, lockout, or attempt accounting anywhere in the request path: the router installs only a CSP-setting middleware (http/http.go:30-35) and handle() (http/data.go:50-85) adds no throttle or limiter. Each attempt costs a full bcrypt comparison (CPU). This is a CWE-307-class missing anti-automation control on an unauthenticated, CPU-expensive check.
Potential Attack
Given a known password-protected share hash (a distributed share link; share Hash is base64 of 6 random bytes, ~48-bit, http/share.go:111-117), an attacker scripts repeated GET /api/public/dl/<hash> requests varying the X-SHARE-PASSWORD header. With no lockout or backoff, guesses are limited only by bcrypt speed, enabling offline-style online brute-forcing of the share password. Separately, firing many concurrent requests converts cheap HTTP into sustained server-side bcrypt CPU work.
Outcomes of Potential Attack
Two outcomes: (1) unlimited online password guessing against a protected share, risking unauthorized access to the shared file/directory if the password is weak; and (2) CPU-exhaustion denial of service — each unauthenticated request forces a full bcrypt comparison, so a flood of attempts saturates CPU and degrades service for all users. bcrypt's cost partially self-limits guessing throughput but is precisely what makes the DoS effective per request.
Affected Scope
Unauthenticated share password verification: authenticateShareRequest (http/public.go:129-154), reached via GET /api/public/dl and /api/public/share (withHashFile, public.go:18-91).
Suggested Fix (plain english)
Add limits on failed share-password attempts (per share and per client IP) with backoff or temporary lockout, and cap how much password-checking work one client can trigger.
Suggested Fix (detailed)
Introduce per-share and per-IP rate limiting with exponential backoff and temporary lockout on failed X-SHARE-PASSWORD attempts, and account attempts server-side (e.g. in a small in-memory/store-backed counter keyed by share hash and client IP), applied in the withHashFile/authenticateShareRequest path (http/public.go). Consider constant-work throttling to bound bcrypt CPU per client. Verify that repeated wrong-password attempts against a share are throttled/locked out and that concurrent attempts cannot drive unbounded bcrypt CPU. Add the limiter as middleware in http/data.go handle() so it covers the public download/share routes. Add per-share and per-IP rate limiting with exponential backoff/temporary lockout on failed share-password attempts, and account attempts server-side; consider constant-work throttling to bound bcrypt CPU per client.
Validation
The missing anti-automation control on an unauthenticated bcrypt password check is definitively present in source (CWE-307-class), but exploitable reachability depends on the runtime existence of a password-protected share and attacker knowledge of its high-entropy hash, so possibly_valid with the reachability precondition named.
Full Evidence
http/public.go:138,146 password := r.Header.Get('X-SHARE-PASSWORD'); bcrypt.CompareHashAndPassword([]byte(l.PasswordHash),[]byte(password))http/public.go:130-132 authenticateShareRequest returns 0,nil when l.PasswordHash=='' (bcrypt branch only for password-protected shares)
http/http.go:30-35 only a CSP-setting middleware; http/data.go:50-85 handle() adds no throttle/rate limiter/lockout
http/share.go:111-117 share Hash = base64(6 random bytes) (~48-bit)
Proven fact: bcrypt.CompareHashAndPassword runs on the attacker-supplied X-SHARE-PASSWORD header with no rate limiting, throttling, lockout, or attempt accounting anywhere in the request path.
Proven fact: Each attempt is unauthenticated and costs a full bcrypt comparison (CPU).
Unvalidated fact: The vulnerable bcrypt branch is only reachable when the targeted share has a non-empty PasswordHash AND the attacker knows the share's ~48-bit random hash (withHashFile first resolves GetByHash) - i.e., possession of a distributed password-protected share link, a runtime/threat-model precondition not determinable from source.
Unvalidated fact: Brute-force success further depends on the operator/user-chosen password strength; bcrypt's cost partially self-limits guessing throughput.
http/public.go:138,146 password := r.Header.Get('X-SHARE-PASSWORD'); bcrypt.CompareHashAndPassword([]byte(l.PasswordHash),[]byte(password)) — verified in source.http/public.go:130-132 authenticateShareRequest returns 0,nil when l.PasswordHash=='' (bcrypt branch only for password-protected shares); :134-135 a matching ?token= also bypasses.
http/http.go:30-35 only a CSP-setting middleware; http/data.go:50-85 handle() adds no throttle/rate limiter/lockout.
http/share.go:111-117 share Hash = base64(6 random bytes) (~48-bit).
Unvalidated fact: The vulnerable bcrypt branch is only reachable when the targeted share has a non-empty PasswordHash AND the attacker knows the share's ~48-bit random hash (withHashFile first resolves GetByHash) — i.e., possession of a distributed password-protected share link, a runtime/threat-model precondition not determinable from source.
http/public.go:138-151 bcrypt compare on attacker-supplied X-SHARE-PASSWORD
http/http.go:30-35 only middleware sets CSP; no throttle
http/data.go:50-85 no rate limiter in handle()