Description
An SSH client that trusts a server host key without verification does not establish the server's identity. In Go, ssh.InsecureIgnoreHostKey() accepts the key of any server it reaches.
An attacker on the network path may impersonate the SSH server and intercept the connection. Passwords used for authentication and important data sent through that connection may be exposed. Public-key authentication does not itself transmit the client's private key to the server.
Potential impact
- An impersonated server may read or alter the session's contents.
- SSH passwords, tokens, commands and their results may be exposed.
- The malicious peer can receive client commands and fabricate the responses it returns.
Remediation
- Do not use
ssh.InsecureIgnoreHostKey()in production. - Use
knownhosts.Newfromgolang.org/x/crypto/ssh/knownhoststo read a trusted OpenSSHknown_hostsfile and setHostKeyCallback. - In a restricted environment, pin a previously trusted server public key with
ssh.FixedHostKey(). - Distribute and manage the trusted file or key securely. Verify replacement server keys through a trusted process.
- Use test-specific
known_hostsfiles or pinned keys in tests instead of disabling verification.
Examples
These examples compare host-key verification only. Supply passwords through protected configuration rather than using the illustrative password in production. Distribute known_hosts through a trusted channel, and handle home-directory lookup errors in production code.
Before
package main
import (
"log"
"golang.org/x/crypto/ssh"
)
func newSSHClientInsecure(addr, user, password string) (*ssh.Client, error) {
config := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{
ssh.Password(password),
},
// Server host key is not verified
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
client, err := ssh.Dial("tcp", addr, config)
if err != nil {
return nil, err
}
return client, nil
}
func main() {
client, err := newSSHClientInsecure("example.com:22", "deploy", "secret-password")
if err != nil {
log.Fatal(err)
}
defer client.Close()
// ... Perform important operations ...
}
After
package main
import (
"log"
"os"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/knownhosts"
)
func newSSHClientSecure(addr, user, password, knownHostsPath string) (*ssh.Client, error) {
// Configure host-key verification using known_hosts
hostKeyCallback, err := knownhosts.New(knownHostsPath)
if err != nil {
return nil, err
}
config := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{
ssh.Password(password),
},
HostKeyCallback: hostKeyCallback,
}
client, err := ssh.Dial("tcp", addr, config)
if err != nil {
return nil, err
}
return client, nil
}
func main() {
home, _ := os.UserHomeDir()
knownHosts := home + "/.ssh/known_hosts"
client, err := newSSHClientSecure("example.com:22", "deploy", "secret-password", knownHosts)
if err != nil {
log.Fatal(err)
}
defer client.Close()
// ... Perform SSH operations after verifying the host key ...
}
Explanation:
- Before:
HostKeyCallback: ssh.InsecureIgnoreHostKey()accepts an impersonated server without checking its key. Passwords, commands and responses on that connection may be exposed or controlled by the malicious peer. - After:
knownhosts.Newloads the trusted OpenSSH host-key file and provides the verification callback. A mismatched key causes the connection to fail. Authenticating the server protects key exchange and the communication channel against server impersonation.