File Browser has a DoS Vulnerability via Public Login API
Description
A missing maximum password length check in FileBrowser's login API allows an attacker to send arbitrarily large passwords, causing excessive CPU/memory usage and denial of service.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A missing maximum password length check in FileBrowser's login API allows an attacker to send arbitrarily large passwords, causing excessive CPU/memory usage and denial of service.
Vulnerability
In FileBrowser, the CheckPwd function in users/password.go does not enforce a maximum password length when processing login requests to the /api/login endpoint. An attacker can send a JSON payload containing an arbitrarily large password string, which is then hashed, leading to excessive CPU and memory consumption. This affects all versions prior to the fix in commit 847d08b, released in version 2.63.6 [1][2][3][4].
Exploitation
An attacker does not need authentication or prior access; they can send HTTP POST requests to the public /api/login endpoint with a large password (e.g., a ~1 GB string generated by repeating a phrase) using a simple script. By sending multiple concurrent requests, the server's CPU and memory are rapidly exhausted, causing the container to become unresponsive or crash [2][3].
Impact
Successful exploitation results in a denial of service (DoS). The FileBrowser container crashes or becomes heavily lagged, and the Docker daemon may return HTTP 500 errors even after the container is destroyed. The service is unavailable to legitimate users; no data is read or modified, but availability is severely impacted [2][3].
Mitigation
The vulnerability is fixed in FileBrowser version 2.63.6, released on an unknown date but referenced in the release notes [4]. Users should upgrade to v2.63.6 or later. The fix implements a maximum password length check in the login handling code [1]. No workaround is available for unpatched versions [2][3].
AI Insight generated on Jun 12, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
1- Range: <= 1.11.0
Patches
1847d08bdd135fix: address three security disclosures (archive traversal, login DoS, symlink escape)
4 files changed · +77 −1
files/file.go+55 −0 modified@@ -133,6 +133,17 @@ func stat(opts *FileOptions) (*FileInfo, error) { return file, nil } + // The path is a symlink. Refuse to follow it if its on-disk target escapes + // the user's scoped root; otherwise a symlink that lives lexically inside + // the scope but points outside it would let a restricted user read, write, + // or share files beyond their boundary. + if file != nil && file.IsSymlink { + ok, scopeErr := WithinScope(opts.Fs, opts.Path) + if scopeErr != nil || !ok { + return nil, os.ErrPermission + } + } + // fs doesn't support afero.Lstater interface or the file is a symlink info, err := opts.Fs.Stat(opts.Path) if err != nil { @@ -165,6 +176,50 @@ func stat(opts *FileOptions) (*FileInfo, error) { return file, nil } +// WithinScope reports whether the on-disk target of p — after resolving any +// symbolic links — stays within the scoped root of fsys. It exists to stop a +// symlink that lives lexically inside a user's scope but points outside it +// from being followed for reads, writes, or shares. +// +// Paths that do not exist yet (e.g. a brand-new file being created) are +// validated against their nearest existing ancestor, so legitimate new files +// are always allowed. For a filesystem that is not scoped with BasePathFs the +// check is a no-op and returns true. +// +// Note: a dangling symlink whose target does not yet exist resolves to its +// containing directory and is therefore allowed; writing through such a link +// could still create a file outside the scope. Callers that create files +// should treat this as best-effort and rely on rejecting existing escaping +// symlinks, which covers the disclosure and overwrite vectors. +func WithinScope(fsys afero.Fs, p string) (bool, error) { + bfs, ok := fsys.(*afero.BasePathFs) + if !ok { + // Not a scoped filesystem; nothing to enforce. + return true, nil + } + + root, err := filepath.EvalSymlinks(afero.FullBaseFsPath(bfs, "/")) + if err != nil { + return false, err + } + + target := afero.FullBaseFsPath(bfs, p) + resolved, err := filepath.EvalSymlinks(target) + for errors.Is(err, fs.ErrNotExist) { + parent := filepath.Dir(target) + if parent == target { + break + } + target = parent + resolved, err = filepath.EvalSymlinks(target) + } + if err != nil { + return false, err + } + + return resolved == root || strings.HasPrefix(resolved, root+string(filepath.Separator)), nil +} + // Checksum checksums a given File for a given User, using a specific // algorithm. The checksums data is saved on File object. func (i *FileInfo) Checksum(algo string) error {
http/auth.go+9 −1 modified@@ -20,6 +20,8 @@ import ( const ( DefaultTokenExpirationTime = time.Hour * 2 + + maxAuthBodySize = 1 << 20 // 1 MiB ) type userInfo struct { @@ -120,6 +122,10 @@ func withAdmin(fn handleFunc) handleFunc { func loginHandler(tokenExpireTime time.Duration) handleFunc { return func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { + if r.Body != nil { + r.Body = http.MaxBytesReader(w, r.Body, maxAuthBodySize) + } + auther, err := d.store.Auth.Get(d.settings.AuthMethod) if err != nil { return http.StatusInternalServerError, err @@ -142,7 +148,7 @@ type signupBody struct { Password string `json:"password"` } -var signupHandler = func(_ http.ResponseWriter, r *http.Request, d *data) (int, error) { +var signupHandler = func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { if !d.settings.Signup { return http.StatusMethodNotAllowed, nil } @@ -151,6 +157,8 @@ var signupHandler = func(_ http.ResponseWriter, r *http.Request, d *data) (int, return http.StatusBadRequest, nil } + r.Body = http.MaxBytesReader(w, r.Body, maxAuthBodySize) + info := &signupBody{} err := json.NewDecoder(r.Body).Decode(info) if err != nil {
http/raw.go+6 −0 modified@@ -125,6 +125,12 @@ func getFiles(d *data, path, commonPath string) ([]archives.FileInfo, error) { nameInArchive := strings.TrimPrefix(path, commonPath) nameInArchive = strings.TrimPrefix(nameInArchive, string(filepath.Separator)) nameInArchive = filepath.ToSlash(nameInArchive) + // filepath.ToSlash only rewrites the host separator, so on a Linux + // host a stored backslash survives and is emitted verbatim into the + // archive. Windows extractors then treat "\" as a path separator, + // allowing the entry to escape the extraction directory (zip-slip). + // Strip Windows separators regardless of host OS. + nameInArchive = strings.ReplaceAll(nameInArchive, "\\", "/") archiveFiles = append(archiveFiles, archives.FileInfo{ FileInfo: info,
http/resource.go+7 −0 modified@@ -295,6 +295,13 @@ func addVersionSuffix(source string, afs afero.Fs) string { } func writeFile(afs afero.Fs, dst string, in io.Reader, fileMode, dirMode fs.FileMode) (os.FileInfo, error) { + // Refuse to write through a symlink that escapes the user's scope, so an + // overwrite of an existing escaping symlink cannot modify a file outside + // the boundary. + if ok, err := files.WithinScope(afs, dst); err != nil || !ok { + return nil, os.ErrPermission + } + dir, _ := path.Split(dst) err := afs.MkdirAll(dir, dirMode) if err != nil {
Vulnerability mechanics
Root cause
"Missing input size validation on the password field in the login API allows an attacker to send arbitrarily large payloads that are fed into bcrypt hashing, causing a denial of service via resource exhaustion."
Attack vector
An unauthenticated attacker sends a POST request to `/api/login` with a JSON body containing an extremely large password string (approximately 1 GB per the PoC). The server reads the entire payload into memory and passes it to bcrypt for hashing and verification, which spikes CPU and memory usage. With concurrent requests (30 at a time, 1000 total in the PoC), this exhausts server resources, causing crashes, heavy lag, and potentially destabilizing the host Docker daemon [ref_id=2][ref_id=3].
Affected code
The vulnerability lies in the `loginHandler` function in `http/auth.go` and the `CheckPwd` function in `users/password.go`. No maximum password length was enforced, so an arbitrarily large string could be sent to the `/api/login` endpoint and passed to bcrypt hashing, exhausting server resources [ref_id=1][ref_id=2].
What the fix does
The patch introduces `maxAuthBodySize` (1 MiB) and wraps the request body in `loginHandler` and `signupHandler` with `http.MaxBytesReader` to reject payloads exceeding that limit before any JSON decoding or bcrypt hashing occurs [patch_id=5751403]. This prevents an attacker from sending multi-gigabyte passwords that would exhaust server memory and CPU, closing the unauthenticated denial-of-service vector.
Preconditions
- authNo authentication required — the /api/login endpoint is public
- networkAttacker must be able to send HTTP POST requests to the server
- inputAttacker must craft a JSON body with a password field containing an extremely large string
Reproduction
Create a ~1 GB password file: `yes "thisisalongphraseithinksoyeahitisactuallyimsureitiswhatisthisisamouthwoahimcoolwheredidthiscomefromwowza" | head -n 10000000 > large-password.txt`. Start a filebrowser container: `docker run -v filebrowser_data:/srv -v filebrowser_database:/database -v filebrowser_config:/config -p 8080:80 filebrowser/filebrowser`. Run the provided Python script with `CONCURRENT_REQUESTS=30` and `TOTAL_REQUESTS=1000` against `http://localhost:8080/api/login` to observe CPU/memory exhaustion and service degradation [ref_id=2][ref_id=3].
Generated on Jun 12, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.