From 51f8d36daf928889a57d9ba133f577658a5167dd Mon Sep 17 00:00:00 2001 From: lyc8503 Date: Sat, 14 Jan 2023 20:39:59 +0800 Subject: [PATCH] Add SSH outbound host key validation --- docs/configuration/outbound/ssh.md | 5 +++++ docs/configuration/outbound/ssh.zh.md | 5 +++++ option/ssh.go | 1 + outbound/ssh.go | 12 ++++++++++++ 4 files changed, 23 insertions(+) diff --git a/docs/configuration/outbound/ssh.md b/docs/configuration/outbound/ssh.md index cf948f45..290cea28 100644 --- a/docs/configuration/outbound/ssh.md +++ b/docs/configuration/outbound/ssh.md @@ -14,6 +14,7 @@ "private_key_passphrase": "", "host_key_algorithms": [], "client_version": "SSH-2.0-OpenSSH_7.4p1", + "host_key": "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdH...", ... // Dial Fields } @@ -59,6 +60,10 @@ Host key algorithms. Client version. Random version will be used if empty. +#### host_key + +Remote SSH host key. Accept any host key if empty. + ### Dial Fields See [Dial Fields](/configuration/shared/dial) for details. diff --git a/docs/configuration/outbound/ssh.zh.md b/docs/configuration/outbound/ssh.zh.md index e14e9fd4..ca272002 100644 --- a/docs/configuration/outbound/ssh.zh.md +++ b/docs/configuration/outbound/ssh.zh.md @@ -14,6 +14,7 @@ "private_key_passphrase": "", "host_key_algorithms": [], "client_version": "SSH-2.0-OpenSSH_7.4p1", + "host_key": "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdH...", ... // 拨号字段 } @@ -59,6 +60,10 @@ SSH 用户, 默认使用 root。 客户端版本,默认使用随机值。 +#### host_key + +指定主机密钥,留空接受所有主机密钥. + ### 拨号字段 参阅 [拨号字段](/zh/configuration/shared/dial/)。 diff --git a/option/ssh.go b/option/ssh.go index 30321705..c4b0fd7c 100644 --- a/option/ssh.go +++ b/option/ssh.go @@ -10,4 +10,5 @@ type SSHOutboundOptions struct { PrivateKeyPassphrase string `json:"private_key_passphrase,omitempty"` HostKeyAlgorithms Listable[string] `json:"host_key_algorithms,omitempty"` ClientVersion string `json:"client_version,omitempty"` + HostKey string `json:"host_key,omitempty"` } diff --git a/outbound/ssh.go b/outbound/ssh.go index 33579a7e..401f3a59 100644 --- a/outbound/ssh.go +++ b/outbound/ssh.go @@ -2,7 +2,9 @@ package outbound import ( "context" + "encoding/base64" "math/rand" + "errors" "net" "os" "strconv" @@ -38,6 +40,7 @@ type SSH struct { clientAccess sync.Mutex clientConn net.Conn client *ssh.Client + hostKey string } func NewSSH(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SSHOutboundOptions) (*SSH, error) { @@ -55,6 +58,7 @@ func NewSSH(ctx context.Context, router adapter.Router, logger log.ContextLogger user: options.User, hostKeyAlgorithms: options.HostKeyAlgorithms, clientVersion: options.ClientVersion, + hostKey: options.HostKey, } if outbound.serverAddr.Port == 0 { outbound.serverAddr.Port = 22 @@ -104,6 +108,11 @@ func randomVersion() string { return version } +func keyString(k ssh.PublicKey) string { + // e.g. "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY...." + return k.Type() + " " + base64.StdEncoding.EncodeToString(k.Marshal()) +} + func (s *SSH) connect() (*ssh.Client, error) { if s.client != nil { return s.client, nil @@ -126,6 +135,9 @@ func (s *SSH) connect() (*ssh.Client, error) { ClientVersion: s.clientVersion, HostKeyAlgorithms: s.hostKeyAlgorithms, HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { + if s.hostKey != "" && s.hostKey != keyString(key) { + return errors.New("SSH Host key mismatch, expected: " + keyString(key) + " got: " + s.hostKey) + } return nil }, }