Skip to content

Commit dcd5d7a

Browse files
committed
🐛 detect OpenSSH 9.9 and force Key Exchange Algorithms
Signed-off-by: Salim Afiune Maya <afiune@mondoo.com>
1 parent a83cf31 commit dcd5d7a

1 file changed

Lines changed: 47 additions & 16 deletions

File tree

  • providers/os/connection/ssh

providers/os/connection/ssh/ssh.go

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package ssh
66
import (
77
"bytes"
88
"context"
9-
"errors"
109
"io"
1110
"net"
1211
"os"
@@ -16,6 +15,7 @@ import (
1615
"time"
1716

1817
awsconf "github.com/aws/aws-sdk-go-v2/config"
18+
"github.com/cockroachdb/errors"
1919
"github.com/kevinburke/ssh_config"
2020
"github.com/mitchellh/go-homedir"
2121
rawsftp "github.com/pkg/sftp"
@@ -33,7 +33,6 @@ import (
3333
"go.mondoo.com/cnquery/v11/providers/os/connection/ssh/signers"
3434
"go.mondoo.com/cnquery/v11/utils/multierr"
3535
"golang.org/x/crypto/ssh"
36-
"golang.org/x/crypto/ssh/agent"
3736
"golang.org/x/crypto/ssh/knownhosts"
3837
)
3938

@@ -493,27 +492,59 @@ func establishClientConnection(pCfg *inventory.Config, hostKeyCallback ssh.HostK
493492
}
494493
}
495494

496-
log.Debug().Int("methods", len(authMethods)).Str("user", user).Msg("connect to remote ssh")
497-
conn, err := ssh.Dial("tcp", pCfg.Host+":"+strconv.Itoa(int(pCfg.Port)), &ssh.ClientConfig{
495+
addr := pCfg.Host + ":" + strconv.Itoa(int(pCfg.Port))
496+
sshClientConfig := &ssh.ClientConfig{
498497
User: user,
499498
Auth: authMethods,
500499
HostKeyCallback: hostKeyCallback,
501-
})
500+
}
501+
502+
supportsHybrid, err := serverSupportsHybridKEX(addr)
503+
if supportsHybrid {
504+
// force the Key Exchange Algorithm to a compatible one
505+
sshClientConfig.Config = ssh.Config{
506+
KeyExchanges: []string{
507+
"curve25519-sha256",
508+
"curve25519-sha256@libssh.org",
509+
"ecdh-sha2-nistp256",
510+
"diffie-hellman-group14-sha1",
511+
},
512+
}
513+
}
514+
515+
log.Debug().
516+
Int("methods", len(authMethods)).
517+
Str("user", user).
518+
Bool("hybrid_key_exchange", supportsHybrid).
519+
Msg("connect to remote ssh")
520+
conn, err := ssh.Dial("tcp", addr, sshClientConfig)
502521
return conn, closer, err
503522
}
504523

505-
// hasAgentLoadedKey returns if the ssh agent has loaded the key file
506-
// This may not be 100% accurate. The key can be stored in multiple locations with the
507-
// same fingerprint. We cannot determine the fingerprint without decoding the encrypted
508-
// key, `ssh-keygen -lf /Users/chartmann/.ssh/id_rsa` seems to use the ssh agent to
509-
// determine the fingerprint without prompting for the password
510-
func hasAgentLoadedKey(list []*agent.Key, filename string) bool {
511-
for i := range list {
512-
if list[i].Comment == filename {
513-
return true
514-
}
524+
// Detects if the remote server offers hybrid PQ KEX algorithms
525+
func serverSupportsHybridKEX(addr string) (bool, error) {
526+
conn, err := net.DialTimeout("tcp", addr, 5*time.Second)
527+
if err != nil {
528+
return false, errors.Wrap(err, "failed to dial")
515529
}
516-
return false
530+
defer conn.Close()
531+
532+
// Read the server's version string
533+
var buf [256]byte
534+
n, err := conn.Read(buf[:])
535+
if err != nil {
536+
return false, errors.Wrap(err, "failed to read banner")
537+
}
538+
banner := string(buf[:n])
539+
540+
// We'll stop here. Full KEXINIT parsing requires building a custom packet reader.
541+
// For now, assume OpenSSH 9.9+ includes PQ KEX unless we detect otherwise.
542+
if strings.Contains(banner, "OpenSSH_9.9") {
543+
// Naively assume 9.9+ offers hybrid KEX
544+
return true, nil
545+
}
546+
547+
return false, nil
517548
}
518549

519550
// prepareConnection determines the auth methods required for a ssh connection and also prepares any other

0 commit comments

Comments
 (0)