Suse
CVE-2026-32758
MEDIUM
Severity by source
AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N
Primary rating from GitHub Advisory.
CVSS VectorGitHub Advisory
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N
Lifecycle Timeline
3DescriptionGitHub Advisory
Description
The resourcePatchHandler in http/resource.go validates the destination path against configured access rules before the path is cleaned/normalized. The rules engine (rules/rules.go) uses literal string prefix matching (strings.HasPrefix) or regex matching against the raw path. The actual file operation (fileutils.Copy, patchAction) subsequently calls path.Clean() which resolves .. sequences, producing a different effective path than the one validated.
This allows an authenticated user with Create or Rename permissions to bypass administrator-configured deny rules by including .. (dot-dot) path traversal sequences in the destination query parameter of a PATCH request.
Steps to Reproduce
1. Verify the rule works normally
# This should return 403 Forbidden
curl -X PATCH \
-H "X-Auth: <alice_jwt>" \
"http://host/api/resources/public/test.txt?action=copy&destination=%2Frestricted%2Fcopied.txt"2. Exploit the bypass
# This should succeed despite the deny rule
curl -X PATCH \
-H "X-Auth: <alice_jwt>" \
"http://host/api/resources/public/test.txt?action=copy&destination=%2Fpublic%2F..%2Frestricted%2Fcopied.txt"3. Result
The file test.txt is copied to /restricted/copied.txt despite the deny rule for /restricted/.
Root Cause Analysis
In http/resource.go:209-257:
dst := r.URL.Query().Get("destination") // line 212
dst, err := url.QueryUnescape(dst) // line 214 - dst contains ".."
if !d.Check(src) || !d.Check(dst) { // line 215 - CHECK ON UNCLEANED PATH
return http.StatusForbidden, nil
}In rules/rules.go:29-35:
func (r *Rule) Matches(path string) bool {
if r.Regex {
return r.Regexp.MatchString(path) // regex on literal path
}
return strings.HasPrefix(path, r.Path) // prefix on literal path
}In fileutils/copy.go:12-17:
func Copy(afs afero.Fs, src, dst string, ...) error {
if dst = path.Clean("/" + dst); dst == "" { // CLEANING HAPPENS HERE, AFTER CHECK
return os.ErrNotExist
}The rules check sees /public/../restricted/copied.txt (no match for /restricted/ prefix). The file operation resolves it to /restricted/copied.txt (within the restricted path).
Secondary Issue
In the same handler, the error from url.QueryUnescape is checked after d.Check() runs (lines 214-220), meaning the rules check executes on a potentially malformed string if unescaping fails.
Impact
An authenticated user with Copy (Create) or Rename permission can write or move files into any path within their scope that is protected by deny rules. This bypasses both:
- Prefix-based rules:
strings.HasPrefixon uncleaned path misses the match - Regex-based rules: Standard patterns like
^/restricted/.*fail on uncleaned path
Cannot be used to:
- Escape the user's BasePathFs scope (afero prevents this)
- Read from restricted paths (GET handler uses cleaned
r.URL.Path)
Suggested Fix
Clean the destination path before the rules check:
dst, err := url.QueryUnescape(dst)
if err != nil {
return errToStatus(err), err
}
dst = path.Clean("/" + dst)
src = path.Clean("/" + src)
if !d.Check(src) || !d.Check(dst) {
return http.StatusForbidden, nil
}
if dst == "/" || src == "/" {
return http.StatusForbidden, nil
}AnalysisAI
Path traversal in the resourcePatchHandler allows authenticated users with Create or Rename permissions to bypass access control rules by injecting path traversal sequences (..\) into PATCH requests, since validation occurs before path normalization. An attacker can exploit this to copy or rename files to restricted directories that should be protected by administrator-configured deny rules. No patch is currently available.
Technical ContextAI
The vulnerability stems from a time-of-check-time-of-use (TOCTOU) flaw in the FileBrowser resource handler (pkg:go/github.com_filebrowser_filebrowser_v2). The application validates access rules in http/resource.go against the raw, unencoded destination path before path normalization occurs. The rules engine in rules/rules.go uses either literal string prefix matching via strings.HasPrefix() or regex pattern matching against the uncleaned path. However, the actual file operation in fileutils/copy.go invokes path.Clean() after the validation check, which resolves path traversal sequences like .. to their canonical forms. This creates a mismatch: a path like /public/../restricted/file.txt fails the prefix rule check for /restricted/ but normalizes to /restricted/file.txt during the copy operation. The root cause is classified under CWE-22 (Improper Limitation of a Pathname to a Restricted Directory).
RemediationAI
Upgrade FileBrowser to the patched version specified in the official security advisory at https://github.com/filebrowser/filebrowser/security/advisories/GHSA-9f3r-2vgw-m8xp. The recommended fix is to normalize all paths using path.Clean() immediately after URL unescaping and before any access control checks, as detailed in the CVE description. Organizations unable to patch immediately should implement the following workarounds: restrict PATCH request access via reverse proxy to trusted users only, disable Copy/Rename functionality for users who do not require it by modifying FileBrowser's permission configuration, and monitor access logs for PATCH requests containing ../ sequences in the destination parameter. Additionally, ensure FileBrowser runs with minimal filesystem permissions and use OS-level access controls to further restrict the scope of any successful bypass.
Same weakness CWE-22 – Path Traversal
View allSame technique Path Traversal
View allVendor StatusVendor
SUSE
Severity: Medium| Product | Status |
|---|---|
| openSUSE Leap 15.6 | Fixed |
| SUSE Linux Enterprise Module for Package Hub 15 SP5 | Fixed |
| SUSE Linux Enterprise Module for Package Hub 15 SP6 | Fixed |
| openSUSE Leap 15.5 | Fixed |
Share
External POC / Exploit Code
Leaving vuln.today
GHSA-9f3r-2vgw-m8xp