Summary
File Browser's public share handlers rebase the share owner's filesystem root to the shared directory and then evaluate descendant paths against the owner's global and per-user rules using the rebased relative path instead of the original path relative to the owner's scope.
As a result, an attacker who knows a public directory share URL can access files and subdirectories that the owner explicitly blocked with rules, as long as those blocked paths are located underneath the shared directory. In the simplest case this is an unauthenticated information disclosure through GET /api/public/share/* and GET /api/public/dl/*.
Details
The public share flow first resolves the original shared path under the owner's filesystem, but then switches d.user.Fs to a new BasePathFs rooted at the shared directory. The follow-up authorization check is still performed by d.Check, which compares the request path to rule strings using prefix matching.
When the share target is a directory, the path passed to d.Check becomes relative to the shared directory, while the rules remain relative to the owner's original scope. A deny rule such as /projects/private therefore no longer matches a public share request for /private/secret.txt, even though the rebased filesystem resolves that request to the real path /projects/private/secret.txt.
Core vulnerable code path:
// http/public.go
if file.IsDir {
basePath = filepath.Clean(link.Path)
filePath = ifPath
}
d.user.Fs = afero.NewBasePathFs(d.user.Fs, basePath)
file, err = files.NewFileInfo(&files.FileOptions{
Fs: d.user.Fs,
Path: filePath,
Expand: true,
Checker: d,
})
// http/data.go and rules/rules.go
func (d *data) Check(path string) bool {
allow := true
for _, rule := range d.settings.Rules {
if rule.Matches(path) {
allow = rule.Allow
}
}
for _, rule := range d.user.Rules {
if rule.Matches(path) {
allow = rule.Allow
}
}
return allow
}
func (r *Rule) Matches(path string) bool {
if path == r.Path {
return true
}
prefix := r.Path
if prefix != "/" && !strings.HasSuffix(prefix, "/") {
prefix += "/"
}
return strings.HasPrefix(path, prefix)
}