GHSA-7rc3-g7h6-22m7 File Browser: Colliding username normalization gives two users the same home directory
SummaryFileBrowser confines each user to a *scope*: a home directory that acts as the boundary for everything they can read or write. When self-registration and automatic home-directory creation are both enabled (`Signup=true` and `CreateUserDir=true`), a new user's scope is built from their username after it passes through `cleanUsername()`. That function rewrites the name: it strips `..` and replaces every character outside `0-9A-Za-z@_\-.` with `-`.The problem is that this rewrite is **many-to-one**: different usernames can produce the same result, and FileBrowser never checks whether the resulting scope is already taken. So `team/one`, `team one`, and `team-one` all collapse to the same directory name, and whoever registers second is handed the **same home directory** as the first user instead of an isolated one.This breaks per-user isolation. An attacker can pick a username that normalizes onto a victim's directory (for example registering `alice/` or `al..ice` to land in `alice`'s home) and gain full read **and** write access to that victim's files. Because username uniqueness is enforced on the raw name, both accounts coexist normally and neither user is warned that they share storage.Details**1. The home directory is built straight from the cleaned username (`settings/dir.go:30`)**The user's scope is `path.Join(UserHomeBasePath, cleanUsername(username))`.**2. `cleanUsername` collapses distinct inputs to the same output (`settings/dir.go:42-52`)**Because several characters all map to `-` (and `..` is simply deleted), many different usernames produce the same output: `team/one`, `team one`, `team:one`, and `team-one` all become `team-one`, and `a..b` becomes `ab`. Usernames that are unique on their own end up pointing at one shared directory name.**3. No scope-uniqueness check exists**Username uniqueness is enforced on the raw `username` (Storm `id`), but nothing enforces uniqueness of the derived `Scope`. `signupHandler` writes the colliding scope back to the user (`http/auth.go:198-203`) and saves the account; the second registrant simply reuses the first registrant's home directory (`MakeUserDir` calls `MkdirAll`, which is idempotent).PoCTested against `filebrowser/filebrowser:v2.63.15` with `Signup=true` and `CreateUserDir=true` (default `minimumPasswordLength` is 12).**Attack Vector: register a colliding username and read/overwrite another user's files:**Expected output (reproduced on a fresh `filebrowser-test` container, v2.63.15):On disk there is a single shared home directory `/srv/users/teamone-x`.Impact**Cross-user read:** an attacker registering a colliding username can read every file in a victim's home directory.**Cross-user write and tamper:** the attacker can overwrite, rename, or delete the victim's files; the victim transparently sees the tampered content.**Per-user isolation bypass:** the home-directory scoping that is supposed to confine each self-registered user is defeated whenever two usernames normalize to the same value.**Targeted or opportunistic:** an attacker can deliberately craft a username that collides with a known victim (e.g. registering `alice/`, `alice.`, or `al..ice` to land on `alice`'s directory), or collisions can occur accidentally between legitimate users.**Precondition:** requires the administrator to have enabled both `Signup` and `CreateUserDir`.Recommended FixMake the derived scope canonical and enforce its uniqueness. Either reject a signup whose normalized scope already exists, or bind the home directory to the immutable user ID rather than to a normalized username:Alternatively, in `signupHandler`, after computing the scope, reject the registration if any existing user already owns that scope (`store.Users.GetByScope(scope)` ⇒ 409 Conflict). Also reject usernames whose normalized form differs from the raw username, so that `cleanUsername` is never silently lossy.
CVSS v3.1 score: 8.1