Key exchange without server identity verification

Key exchange without server identity verification

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.New from golang.org/x/crypto/ssh/knownhosts to read a trusted OpenSSH known_hosts file and set HostKeyCallback.
  • 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_hosts files 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

go
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

go
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.New loads 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.

References