mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-08 15:41:15 +02:00
sftp: reject an empty password (#11095)
ValidatePassword compared the stored and supplied passwords with subtle.ConstantTimeCompare, which returns 1 for two zero-length slices. A user provisioned for public-key-only auth has an empty stored password, so an empty supplied password authenticated as that user whenever "password" was among the enabled auth methods (the default). Treat an empty stored or supplied password as a non-match. Claude-Session: https://claude.ai/code/session_011QqNaxZwnpHgMoAZNp3RkY
This commit is contained in:
@@ -154,6 +154,11 @@ func (s *FileStore) ValidatePassword(username string, password []byte) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An empty stored or supplied password is never a match: a public-key-only
|
||||||
|
// user has no password, and equal-length zero slices would otherwise compare equal.
|
||||||
|
if len(user.Password) == 0 || len(password) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
// Compare plaintext password using constant time comparison for security
|
// Compare plaintext password using constant time comparison for security
|
||||||
return subtle.ConstantTimeCompare([]byte(user.Password), password) == 1
|
return subtle.ConstantTimeCompare([]byte(user.Password), password) == 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func newTestStore(users ...*User) *FileStore {
|
||||||
|
s := &FileStore{users: make(map[string]*User)}
|
||||||
|
for _, u := range users {
|
||||||
|
s.users[u.Username] = u
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidatePasswordRejectsEmpty(t *testing.T) {
|
||||||
|
s := newTestStore(
|
||||||
|
&User{Username: "keyonly", Password: "", PublicKeys: []string{"ssh-ed25519 AAAA"}},
|
||||||
|
&User{Username: "haspass", Password: "s3cret"},
|
||||||
|
)
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
username string
|
||||||
|
password string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{"keyonly", "", false}, // public-key-only user must not accept an empty password
|
||||||
|
{"keyonly", "wrong", false},
|
||||||
|
{"haspass", "", false}, // a real password is never matched by an empty one
|
||||||
|
{"haspass", "s3cret", true},
|
||||||
|
{"haspass", "wrong", false},
|
||||||
|
{"missing", "", false},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
if got := s.ValidatePassword(c.username, []byte(c.password)); got != c.want {
|
||||||
|
t.Errorf("ValidatePassword(%q, %q) = %v, want %v", c.username, c.password, got, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user