The issue is CVE-2026-25889: Authentication Bypass in User Password Update, but when you look at the source code, it almost feels like someone deliberately went out of their way to make the code vulnerable. Of course, that’s not actually what happened.
if d.settings.AuthMethod == auth.MethodJSONAuth {
var sensibleFields = map[string]struct{}{
"all": {},
"username": {},
"password": {},
"scope": {},
"lockPassword": {},
"commands": {},
"perm": {},
}
for _, field := range req.Which {
if _, ok := sensibleFields[field]; ok {
if !users.CheckPwd(req.CurrentPassword, d.user.Password) {
return http.StatusBadRequest, fberrors.ErrCurrentPasswordIncorrect
}
break
}
}
}
So, there’s a map called sensibleFields, and on each request, the Which field is checked to see whether it contains any of the listed values, such as username or password. If it does, the handler requires the current password.
For example, if a user wants to change their password, they should be required to provide their current password. Simple enough.
But here’s a question: what if we change the capitalization in the request?
{
"what": "user",
"which": ["Password"],
"current_password": "CurrentPassword123!",
}
“Password” is used instead of “password”
Since Go map lookups are case-sensitive, "Password" doesn’t match "password". As a result, the current-password check (if _, ok := sensibleFields[field]) is completely skipped.
Now, this alone doesn’t necessarily mean we have an authentication bypass. Skipping the current-password check doesn’t automatically mean the password will be changed.
But but but…
for k, v := range req.Which {
v = cases.Title(language.English, cases.NoLower).String(v)
req.Which[k] = v
if v == "Password" {
if !d.user.Perm.Admin && d.user.LockPassword {
return http.StatusForbidden, nil
}
req.Data.Password, err = users.ValidateAndHashPwd(req.Data.Password, d.settings.MinimumPasswordLength)
if err != nil {
return http.StatusBadRequest, err
}
}
Here, things get interesting.
The code uses cases.Title(language.English).String(v) to capitalize the first letter of the requested field name.
So, if the value is “Password”, it remains “Password”. The subsequent if v == "Password" condition succeeds, and the new password is validated and hashed.
Let’s put the pieces together:
- Normally, user will send
{ "what": "password", ... } - since
req.whichwould be “password”, it’ll go through current password check. - Once done,
cases.Title(language.English, cases.NoLower).String(v)would convertpasswordtoPassword, and then it’ll be checked againstif v == "Password", then it’ll get thenewPassword, run tests likeMinimumPasswordLength, and change the password.
However, by sending “Password” instead of “password”, the request skips the current-password check but still reaches the password-update logic since cases.Title(language.English, cases.NoLower).String(v) would convert "Password" to "Password"
And that’s the issue: the inconsistent handling of capitalization allows the current-password requirement to be bypassed while still changing the password, subject to the handler’s other authorization and validation checks.
Fix
The simplest fix would be to remove the function that capitalizes the first letter, i.e., cases.Title(language.English, cases.NoLower).String(v), and compare v directly against "password" instead.
for k, v := range req.Which {
if v == "password" {
// ...
}
}
Although the current-password check could still be bypassed using "Password", the password-update logic would no longer recognize that value, meaning the password wouldn’t be changed through this particular path.
The File Browser team took a different approach. They introduced strings.ToLower to normalize the values in req.Which to lowercase. This means that values such as "Password" would be converted to "password" before the current-password check, ensuring that the check is triggered as expected.
Credit: dogadmin for the report GHSA-hxw8-4h9j-hq2r.
