mirror of
https://github.com/minio/minio.git
synced 2025-04-28 05:38:00 -04:00
fix: SFTP auth bypass with no pub key in LDAP (#20986)
If a user attempts to authenticate with a key but does not have an sshpubkey attribute in LDAP, the server allows the connection, which means the server trusted the key without reason. This is now fixed, and a test has been added for validation.
This commit is contained in:
parent
6cd8a372cb
commit
4c71f1b4ec
@ -62,7 +62,7 @@ var (
|
|||||||
|
|
||||||
// if the sftp parameter --trusted-user-ca-key is set, then
|
// if the sftp parameter --trusted-user-ca-key is set, then
|
||||||
// the final form of the key file will be set as this variable.
|
// the final form of the key file will be set as this variable.
|
||||||
var caPublicKey ssh.PublicKey
|
var globalSFTPTrustedCAPubkey ssh.PublicKey
|
||||||
|
|
||||||
// https://cs.opensource.google/go/x/crypto/+/refs/tags/v0.22.0:ssh/common.go;l=46
|
// https://cs.opensource.google/go/x/crypto/+/refs/tags/v0.22.0:ssh/common.go;l=46
|
||||||
// preferredKexAlgos specifies the default preference for key-exchange
|
// preferredKexAlgos specifies the default preference for key-exchange
|
||||||
@ -161,8 +161,8 @@ internalAuth:
|
|||||||
return nil, errNoSuchUser
|
return nil, errNoSuchUser
|
||||||
}
|
}
|
||||||
|
|
||||||
if caPublicKey != nil && pass == nil {
|
if globalSFTPTrustedCAPubkey != nil && pass == nil {
|
||||||
err := validateKey(c, key)
|
err := validateClientKeyIsTrusted(c, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errAuthentication
|
return nil, errAuthentication
|
||||||
}
|
}
|
||||||
@ -256,9 +256,18 @@ func processLDAPAuthentication(key ssh.PublicKey, pass []byte, user string) (per
|
|||||||
return nil, errAuthentication
|
return nil, errAuthentication
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Save each attribute to claims.
|
||||||
claims[ldapAttribPrefix+attribKey] = attribValue[0]
|
claims[ldapAttribPrefix+attribKey] = attribValue[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if key != nil {
|
||||||
|
// If a key was provided, we expect the user to have an sshPublicKey
|
||||||
|
// attribute.
|
||||||
|
if _, ok := claims[ldapAttribPrefix+"sshPublicKey"]; !ok {
|
||||||
|
return nil, errAuthentication
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
expiryDur, err := globalIAMSys.LDAPConfig.GetExpiryDuration("")
|
expiryDur, err := globalIAMSys.LDAPConfig.GetExpiryDuration("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -310,8 +319,8 @@ func processLDAPAuthentication(key ssh.PublicKey, pass []byte, user string) (per
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateKey(c ssh.ConnMetadata, clientKey ssh.PublicKey) (err error) {
|
func validateClientKeyIsTrusted(c ssh.ConnMetadata, clientKey ssh.PublicKey) (err error) {
|
||||||
if caPublicKey == nil {
|
if globalSFTPTrustedCAPubkey == nil {
|
||||||
return errors.New("public key authority validation requested but no ca public key specified.")
|
return errors.New("public key authority validation requested but no ca public key specified.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -331,7 +340,7 @@ func validateKey(c ssh.ConnMetadata, clientKey ssh.PublicKey) (err error) {
|
|||||||
// and that certificate type is correct.
|
// and that certificate type is correct.
|
||||||
checker := ssh.CertChecker{}
|
checker := ssh.CertChecker{}
|
||||||
checker.IsUserAuthority = func(k ssh.PublicKey) bool {
|
checker.IsUserAuthority = func(k ssh.PublicKey) bool {
|
||||||
return subtle.ConstantTimeCompare(caPublicKey.Marshal(), k.Marshal()) == 1
|
return subtle.ConstantTimeCompare(globalSFTPTrustedCAPubkey.Marshal(), k.Marshal()) == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = checker.Authenticate(c, clientKey)
|
_, err = checker.Authenticate(c, clientKey)
|
||||||
@ -428,7 +437,7 @@ func startSFTPServer(args []string) {
|
|||||||
allowMACs = filterAlgos(arg, strings.Split(tokens[1], ","), supportedMACs)
|
allowMACs = filterAlgos(arg, strings.Split(tokens[1], ","), supportedMACs)
|
||||||
case "trusted-user-ca-key":
|
case "trusted-user-ca-key":
|
||||||
userCaKeyFile = tokens[1]
|
userCaKeyFile = tokens[1]
|
||||||
case "password-auth":
|
case "disable-password-auth":
|
||||||
disablePassAuth, _ = strconv.ParseBool(tokens[1])
|
disablePassAuth, _ = strconv.ParseBool(tokens[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -457,7 +466,7 @@ func startSFTPServer(args []string) {
|
|||||||
logger.Fatal(fmt.Errorf("invalid arguments passed, trusted user certificate authority public key file is not accessible: %v", err), "unable to start SFTP server")
|
logger.Fatal(fmt.Errorf("invalid arguments passed, trusted user certificate authority public key file is not accessible: %v", err), "unable to start SFTP server")
|
||||||
}
|
}
|
||||||
|
|
||||||
caPublicKey, _, _, _, err = ssh.ParseAuthorizedKey(keyBytes)
|
globalSFTPTrustedCAPubkey, _, _, _, err = ssh.ParseAuthorizedKey(keyBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatal(fmt.Errorf("invalid arguments passed, trusted user certificate authority public key file is not parseable: %v", err), "unable to start SFTP server")
|
logger.Fatal(fmt.Errorf("invalid arguments passed, trusted user certificate authority public key file is not parseable: %v", err), "unable to start SFTP server")
|
||||||
}
|
}
|
||||||
|
@ -91,6 +91,7 @@ func TestSFTPAuthentication(t *testing.T) {
|
|||||||
|
|
||||||
suite.SFTPPublicKeyAuthentication(c)
|
suite.SFTPPublicKeyAuthentication(c)
|
||||||
suite.SFTPFailedPublicKeyAuthenticationInvalidKey(c)
|
suite.SFTPFailedPublicKeyAuthenticationInvalidKey(c)
|
||||||
|
suite.SFTPPublicKeyAuthNoPubKey(c)
|
||||||
|
|
||||||
suite.TearDownSuite(c)
|
suite.TearDownSuite(c)
|
||||||
},
|
},
|
||||||
@ -146,6 +147,32 @@ func (s *TestSuiteIAM) SFTPPublicKeyAuthentication(c *check) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A user without an sshpubkey attribute in LDAP (here: fahim) should not be
|
||||||
|
// able to authenticate.
|
||||||
|
func (s *TestSuiteIAM) SFTPPublicKeyAuthNoPubKey(c *check) {
|
||||||
|
keyBytes, err := os.ReadFile("./testdata/dillon_test_key.pub")
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("could not read test key file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testKey, _, _, _, err := ssh.ParseAuthorizedKey(keyBytes)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("could not parse test key file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newSSHCon := newSSHConnMock("fahim=ldap")
|
||||||
|
_, err = sshPubKeyAuth(newSSHCon, testKey)
|
||||||
|
if err == nil {
|
||||||
|
c.Fatalf("expected error but got none")
|
||||||
|
}
|
||||||
|
|
||||||
|
newSSHCon = newSSHConnMock("fahim")
|
||||||
|
_, err = sshPubKeyAuth(newSSHCon, testKey)
|
||||||
|
if err == nil {
|
||||||
|
c.Fatalf("expected error but got none")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *TestSuiteIAM) SFTPFailedAuthDueToMissingPolicy(c *check) {
|
func (s *TestSuiteIAM) SFTPFailedAuthDueToMissingPolicy(c *check) {
|
||||||
newSSHCon := newSSHConnMock("dillon=ldap")
|
newSSHCon := newSSHConnMock("dillon=ldap")
|
||||||
_, err := sshPasswordAuth(newSSHCon, []byte("dillon"))
|
_, err := sshPasswordAuth(newSSHCon, []byte("dillon"))
|
||||||
@ -275,24 +302,48 @@ func (s *TestSuiteIAM) SFTPValidLDAPLoginWithPassword(c *check) {
|
|||||||
c.Fatalf("policy add error: %v", err)
|
c.Fatalf("policy add error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
userDN := "uid=dillon,ou=people,ou=swengg,dc=min,dc=io"
|
{
|
||||||
userReq := madmin.PolicyAssociationReq{
|
userDN := "uid=dillon,ou=people,ou=swengg,dc=min,dc=io"
|
||||||
Policies: []string{policy},
|
userReq := madmin.PolicyAssociationReq{
|
||||||
User: userDN,
|
Policies: []string{policy},
|
||||||
}
|
User: userDN,
|
||||||
if _, err := s.adm.AttachPolicy(ctx, userReq); err != nil {
|
}
|
||||||
c.Fatalf("Unable to attach policy: %v", err)
|
if _, err := s.adm.AttachPolicyLDAP(ctx, userReq); err != nil {
|
||||||
}
|
c.Fatalf("Unable to attach policy: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
newSSHCon := newSSHConnMock("dillon=ldap")
|
newSSHCon := newSSHConnMock("dillon=ldap")
|
||||||
_, err = sshPasswordAuth(newSSHCon, []byte("dillon"))
|
_, err = sshPasswordAuth(newSSHCon, []byte("dillon"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fatal("Password authentication failed for user (dillon):", err)
|
c.Fatal("Password authentication failed for user (dillon):", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
newSSHCon = newSSHConnMock("dillon")
|
newSSHCon = newSSHConnMock("dillon")
|
||||||
_, err = sshPasswordAuth(newSSHCon, []byte("dillon"))
|
_, err = sshPasswordAuth(newSSHCon, []byte("dillon"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fatal("Password authentication failed for user (dillon):", err)
|
c.Fatal("Password authentication failed for user (dillon):", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
userDN := "uid=fahim,ou=people,ou=swengg,dc=min,dc=io"
|
||||||
|
userReq := madmin.PolicyAssociationReq{
|
||||||
|
Policies: []string{policy},
|
||||||
|
User: userDN,
|
||||||
|
}
|
||||||
|
if _, err := s.adm.AttachPolicyLDAP(ctx, userReq); err != nil {
|
||||||
|
c.Fatalf("Unable to attach policy: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newSSHCon := newSSHConnMock("fahim=ldap")
|
||||||
|
_, err = sshPasswordAuth(newSSHCon, []byte("fahim"))
|
||||||
|
if err != nil {
|
||||||
|
c.Fatal("Password authentication failed for user (fahim):", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newSSHCon = newSSHConnMock("fahim")
|
||||||
|
_, err = sshPasswordAuth(newSSHCon, []byte("fahim"))
|
||||||
|
if err != nil {
|
||||||
|
c.Fatal("Password authentication failed for user (fahim):", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user