DOKIMASecurity review report

Overview

Unauthenticated public download endpoint lacks WriteTimeout, enabling slow-read resource holding

low Possibly Valid low confidence

DOK-100135-FILEBROWSER-RAW-DOWNLOAD-ARCHI-UNAUTHENTICATED-MU · filebrowser · raw-download-archive

Status

Possibly Valid

Unauthenticated reachability and the missing WriteTimeout are proven from source, but the headline multi-range amplification is substantially mitigated by the net/http sumRangesSize guard (stdlib not on disk); residual impact is modest resource-holding, so possibly_valid at reduced (low) severity/confidence with the stdlib precondition named.

Repository / Component

Repository
filebrowser
Component
raw-download-archive

Plain-English Description

Anyone can download files from password-less public share links without logging in, and the server has no time limit on how long a download response may take. A client that reads very slowly can tie up server connections and memory, hurting availability.

Description of the Underlying Issue

publicDlHandler serves non-directory, password-less shares without authentication (http/public.go:120-132; authenticateShareRequest returns 0,nil when PasswordHash is empty, http/public.go:129-132) via rawFileHandler, which passes a seekable *os.File to http.ServeContent (http/raw.go:212-224), so the endpoint honors the Range header including multi-range requests. The HTTP server is configured with only ReadHeaderTimeout:60s and no WriteTimeout, no IdleTimeout, and no MaxHeaderBytes override (default 1 MiB) (cmd/root.go:246-249). filebrowser's own code imposes no range-count cap or response-size limit. With no WriteTimeout, a slow-reading client can hold the response goroutine, file descriptor, and buffers open indefinitely on an endpoint reachable without credentials.

Potential Attack

An unauthenticated attacker who knows (or enumerates) a password-less public share hash sends GET /api/public/dl/<hash> and then reads the response body very slowly (slowloris-style), optionally with a large multi-range Range header to add multipart/byteranges header overhead. Repeating this across many connections holds server resources without ever completing the transfers.

Outcomes of Potential Attack

Accumulation of long-lived response goroutines, open file descriptors, and connection/buffer memory, degrading availability for other users. Because the classic overlapping-range amplification is bounded by the stdlib sumRangesSize guard, the dominant impact is resource holding rather than large data amplification.

Affected Scope

GET /api/public/dl/<hash>[/name] for a non-directory, password-less share -> rawFileHandler -> http.ServeContent (http/raw.go:212-224, http/public.go:120-132); unauthenticated.

Suggested Fix (plain english)

Set write and idle timeouts on the HTTP server, limit the number of byte ranges accepted per request, and rate-limit unauthenticated public downloads.

Suggested Fix (detailed)

On the http.Server (cmd/root.go:246-249) set WriteTimeout and IdleTimeout appropriate for expected download durations, and consider lowering MaxHeaderBytes. Add a cap on the number of ranges parsed/accepted from the Range header before calling http.ServeContent, rejecting requests with excessive ranges (413/416). Apply per-IP rate limiting and connection caps to /api/public/dl. Where large-file downloads require long-lived connections, prefer a streaming approach with an inactivity deadline rather than an unbounded write. Verify by confirming a slow-reading client is disconnected after the idle/write deadline and that excessive-range requests are rejected. Set http.Server WriteTimeout and IdleTimeout, cap the number of accepted ranges (reject excessive-range requests), consider lowering MaxHeaderBytes, and rate-limit unauthenticated public downloads.

Validation

Unauthenticated reachability and the missing WriteTimeout are proven from source, but the headline multi-range amplification is substantially mitigated by the net/http sumRangesSize guard (stdlib not on disk); residual impact is modest resource-holding, so possibly_valid at reduced (low) severity/confidence with the stdlib precondition named.

first_opinion

Full Evidence

http/raw.go:212-224 rawFileHandler passes a seekable *os.File to http.ServeContent (:222), which honors the Range header
http/public.go:120-127 publicDlHandler serves non-dir shares via rawFileHandler; :129-132 authenticateShareRequest returns 0,nil for empty PasswordHash (unauthenticated for password-less shares)
cmd/root.go:246-249 http.Server{ReadHeaderTimeout:60s} only - no WriteTimeout, no MaxHeaderBytes override (default 1 MiB)
Proven fact: publicDlHandler serves password-less shares without authentication and passes a seekable fd to http.ServeContent, which honors multi-range requests.
Proven fact: The HTTP server sets no WriteTimeout and no MaxHeaderBytes override; filebrowser's own code imposes no range-count cap or response-size limit.
Unvalidated fact: The multi-range amplification magnitude depends on net/http serveContent internals (Go stdlib in GOROOT), which are outside the readable scope. Reasoning through both outcomes: modern net/http contains the guard 'if sumRangesSize(ranges) > size { ranges = nil }', which bounds total copied bytes to the file size and defeats classic overlapping-range amplification, leaving only per-part multipart/byteranges header overhead bounded by the 1 MiB request-header limit (tens of MB response). If that guard were absent, amplification would be larger. The dominant disk-provable concern is therefore the missing WriteTimeout enabling slow-read clients to hold response goroutines/fds/buffers (slowloris-style resource holding) on an unauthenticated endpoint.
Unvalidated fact: Reachability additionally requires a password-less public share to exist at runtime.
cmd/root.go:246-249 http.Server{ReadHeaderTimeout:60s} only — no WriteTimeout, no MaxHeaderBytes override (default 1 MiB)
Unvalidated fact: The multi-range amplification magnitude depends on net/http serveContent internals (Go stdlib in GOROOT), outside the readable scope. Modern net/http contains 'if sumRangesSize(ranges) > size { ranges = nil }', bounding total copied bytes to the file size and defeating classic overlapping-range amplification, leaving only per-part multipart/byteranges header overhead bounded by the 1 MiB request-header limit. If that guard were absent, amplification would be larger.
Unvalidated fact: Reachability requires a password-less public share to exist at runtime.
http/raw.go:212-223 rawFileHandler -> http.ServeContent(w,r,file.Name,file.ModTime,fd) on a seekable fd (Range honored)
http/public.go:120-127 publicDlHandler serves non-dir via rawFileHandler with no auth
http/public.go:129-132 authenticateShareRequest returns 0,nil for empty PasswordHash (password-less public reachability)
cmd/root.go:246-249 http.Server{ReadHeaderTimeout:60*time.Second} only — no WriteTimeout, no MaxHeaderBytes (default 1MiB header enables a huge Range header)