Files
seaweedfs/weed/sftpd/auth/password.go
T
Chris LuandDevin ea859a0844 sftp: reject empty passwords in PasswordAuthenticator (#11938)
* sftp: reject empty passwords in PasswordAuthenticator

FileStore.ValidatePassword already refuses a zero-length stored or
supplied password, but the guard belongs at the authenticator too: an
empty password is never valid, and checking before the store call keeps
any user.Store implementation without its own empty-secret check from
matching on zero-length input.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* sftp: keep the brute-force delay on empty-password failures

The early rejection returned before the shared failure path, so an empty
password failed instantly while other failures slept 100-199 ms. Folding
the length check into the store call keeps the store untouched and every
failed attempt equally slow.

---------

Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-21 20:03:14 -07:00

55 lines
1.4 KiB
Go

package auth
import (
"fmt"
"math/rand/v2"
"time"
"github.com/seaweedfs/seaweedfs/weed/sftpd/user"
"golang.org/x/crypto/ssh"
)
// PasswordAuthenticator handles password-based authentication
type PasswordAuthenticator struct {
userStore user.Store
enabled bool
}
// NewPasswordAuthenticator creates a new password authenticator
func NewPasswordAuthenticator(userStore user.Store, enabled bool) *PasswordAuthenticator {
return &PasswordAuthenticator{
userStore: userStore,
enabled: enabled,
}
}
// Enabled returns whether password authentication is enabled
func (a *PasswordAuthenticator) Enabled() bool {
return a.enabled
}
// Authenticate validates a password for a user
func (a *PasswordAuthenticator) Authenticate(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
username := conn.User()
// Check if password auth is enabled
if !a.enabled {
return nil, fmt.Errorf("password authentication disabled")
}
// An empty password is never valid; skip the store so a store without its
// own empty-secret check cannot match on zero-length input.
if len(password) != 0 && a.userStore.ValidatePassword(username, password) {
return &ssh.Permissions{
Extensions: map[string]string{
"username": username,
},
}, nil
}
// Add delay to prevent brute force attacks
time.Sleep(time.Duration(100+rand.IntN(100)) * time.Millisecond)
return nil, fmt.Errorf("authentication failed")
}