VYPR
High severity8.1NVD Advisory· Published Apr 4, 2017· Updated May 13, 2026

CVE-2017-3204

CVE-2017-3204

Description

The Go SSH library (x/crypto/ssh) by default does not verify host keys, facilitating man-in-the-middle attacks. Default behavior changed in commit e4e2799 to require explicitly registering a hostkey verification mechanism.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
golang.org/x/cryptoGo
< 0.0.0-20170330155735-e4e2799dd7aa0.0.0-20170330155735-e4e2799dd7aa

Affected products

2
  • cpe:2.3:a:golang:crypto:*:*:*:*:*:*:*:*
    Range: <=2017-03-17
  • Go/SSH libraryv5
    Range: prior to commit e4e2799

Patches

1
e4e2799dd7aa

ssh: require host key checking in the ClientConfig

https://github.com/golang/cryptoHan-Wen NienhuysMar 29, 2017via ghsa
13 files changed · +193 30
  • ssh/agent/client_test.go+3 1 modified
    @@ -233,7 +233,9 @@ func TestAuth(t *testing.T) {
     		conn.Close()
     	}()
     
    -	conf := ssh.ClientConfig{}
    +	conf := ssh.ClientConfig{
    +		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
    +	}
     	conf.Auth = append(conf.Auth, ssh.PublicKeysCallback(agent.Signers))
     	conn, _, _, err := ssh.NewClientConn(b, "", &conf)
     	if err != nil {
    
  • ssh/agent/example_test.go+8 7 modified
    @@ -6,20 +6,20 @@ package agent_test
     
     import (
     	"log"
    -	"os"
     	"net"
    +	"os"
     
    -        "golang.org/x/crypto/ssh"
    -        "golang.org/x/crypto/ssh/agent"
    +	"golang.org/x/crypto/ssh"
    +	"golang.org/x/crypto/ssh/agent"
     )
     
     func ExampleClientAgent() {
     	// ssh-agent has a UNIX socket under $SSH_AUTH_SOCK
     	socket := os.Getenv("SSH_AUTH_SOCK")
    -        conn, err := net.Dial("unix", socket)
    -        if err != nil {
    -                log.Fatalf("net.Dial: %v", err)
    -        }
    +	conn, err := net.Dial("unix", socket)
    +	if err != nil {
    +		log.Fatalf("net.Dial: %v", err)
    +	}
     	agentClient := agent.NewClient(conn)
     	config := &ssh.ClientConfig{
     		User: "username",
    @@ -29,6 +29,7 @@ func ExampleClientAgent() {
     			// wants it.
     			ssh.PublicKeysCallback(agentClient.Signers),
     		},
    +		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
     	}
     
     	sshc, err := ssh.Dial("tcp", "localhost:22", config)
    
  • ssh/agent/server_test.go+3 1 modified
    @@ -56,7 +56,9 @@ func TestSetupForwardAgent(t *testing.T) {
     		incoming <- conn
     	}()
     
    -	conf := ssh.ClientConfig{}
    +	conf := ssh.ClientConfig{
    +		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
    +	}
     	conn, chans, reqs, err := ssh.NewClientConn(b, "", &conf)
     	if err != nil {
     		t.Fatalf("NewClientConn: %v", err)
    
  • ssh/certs.go+1 1 modified
    @@ -268,7 +268,7 @@ type CertChecker struct {
     	// HostKeyFallback is called when CertChecker.CheckHostKey encounters a
     	// public key that is not a certificate. It must implement host key
     	// validation or else, if nil, all such keys are rejected.
    -	HostKeyFallback func(addr string, remote net.Addr, key PublicKey) error
    +	HostKeyFallback HostKeyCallback
     
     	// IsRevoked is called for each certificate so that revocation checking
     	// can be implemented. It should return true if the given certificate
    
  • ssh/client_auth_test.go+16 3 modified
    @@ -92,6 +92,7 @@ func TestClientAuthPublicKey(t *testing.T) {
     		Auth: []AuthMethod{
     			PublicKeys(testSigners["rsa"]),
     		},
    +		HostKeyCallback: InsecureIgnoreHostKey(),
     	}
     	if err := tryAuth(t, config); err != nil {
     		t.Fatalf("unable to dial remote side: %s", err)
    @@ -104,6 +105,7 @@ func TestAuthMethodPassword(t *testing.T) {
     		Auth: []AuthMethod{
     			Password(clientPassword),
     		},
    +		HostKeyCallback: InsecureIgnoreHostKey(),
     	}
     
     	if err := tryAuth(t, config); err != nil {
    @@ -123,6 +125,7 @@ func TestAuthMethodFallback(t *testing.T) {
     					return "WRONG", nil
     				}),
     		},
    +		HostKeyCallback: InsecureIgnoreHostKey(),
     	}
     
     	if err := tryAuth(t, config); err != nil {
    @@ -141,6 +144,7 @@ func TestAuthMethodWrongPassword(t *testing.T) {
     			Password("wrong"),
     			PublicKeys(testSigners["rsa"]),
     		},
    +		HostKeyCallback: InsecureIgnoreHostKey(),
     	}
     
     	if err := tryAuth(t, config); err != nil {
    @@ -158,6 +162,7 @@ func TestAuthMethodKeyboardInteractive(t *testing.T) {
     		Auth: []AuthMethod{
     			KeyboardInteractive(answers.Challenge),
     		},
    +		HostKeyCallback: InsecureIgnoreHostKey(),
     	}
     
     	if err := tryAuth(t, config); err != nil {
    @@ -203,6 +208,7 @@ func TestAuthMethodRSAandDSA(t *testing.T) {
     		Auth: []AuthMethod{
     			PublicKeys(testSigners["dsa"], testSigners["rsa"]),
     		},
    +		HostKeyCallback: InsecureIgnoreHostKey(),
     	}
     	if err := tryAuth(t, config); err != nil {
     		t.Fatalf("client could not authenticate with rsa key: %v", err)
    @@ -219,6 +225,7 @@ func TestClientHMAC(t *testing.T) {
     			Config: Config{
     				MACs: []string{mac},
     			},
    +			HostKeyCallback: InsecureIgnoreHostKey(),
     		}
     		if err := tryAuth(t, config); err != nil {
     			t.Fatalf("client could not authenticate with mac algo %s: %v", mac, err)
    @@ -254,6 +261,7 @@ func TestClientUnsupportedKex(t *testing.T) {
     		Config: Config{
     			KeyExchanges: []string{"diffie-hellman-group-exchange-sha256"}, // not currently supported
     		},
    +		HostKeyCallback: InsecureIgnoreHostKey(),
     	}
     	if err := tryAuth(t, config); err == nil || !strings.Contains(err.Error(), "common algorithm") {
     		t.Errorf("got %v, expected 'common algorithm'", err)
    @@ -273,7 +281,8 @@ func TestClientLoginCert(t *testing.T) {
     	}
     
     	clientConfig := &ClientConfig{
    -		User: "user",
    +		User:            "user",
    +		HostKeyCallback: InsecureIgnoreHostKey(),
     	}
     	clientConfig.Auth = append(clientConfig.Auth, PublicKeys(certSigner))
     
    @@ -363,6 +372,7 @@ func testPermissionsPassing(withPermissions bool, t *testing.T) {
     		Auth: []AuthMethod{
     			PublicKeys(testSigners["rsa"]),
     		},
    +		HostKeyCallback: InsecureIgnoreHostKey(),
     	}
     	if withPermissions {
     		clientConfig.User = "permissions"
    @@ -409,6 +419,7 @@ func TestRetryableAuth(t *testing.T) {
     			}), 2),
     			PublicKeys(testSigners["rsa"]),
     		},
    +		HostKeyCallback: InsecureIgnoreHostKey(),
     	}
     
     	if err := tryAuth(t, config); err != nil {
    @@ -430,7 +441,8 @@ func ExampleRetryableAuthMethod(t *testing.T) {
     	}
     
     	config := &ClientConfig{
    -		User: user,
    +		HostKeyCallback: InsecureIgnoreHostKey(),
    +		User:            user,
     		Auth: []AuthMethod{
     			RetryableAuthMethod(KeyboardInteractiveChallenge(Cb), NumberOfPrompts),
     		},
    @@ -450,7 +462,8 @@ func TestClientAuthNone(t *testing.T) {
     	serverConfig.AddHostKey(testSigners["rsa"])
     
     	clientConfig := &ClientConfig{
    -		User: user,
    +		User:            user,
    +		HostKeyCallback: InsecureIgnoreHostKey(),
     	}
     
     	c1, c2, err := netPipe()
    
  • ssh/client.go+49 4 modified
    @@ -5,6 +5,7 @@
     package ssh
     
     import (
    +	"bytes"
     	"errors"
     	"fmt"
     	"net"
    @@ -68,6 +69,11 @@ func NewClient(c Conn, chans <-chan NewChannel, reqs <-chan *Request) *Client {
     func NewClientConn(c net.Conn, addr string, config *ClientConfig) (Conn, <-chan NewChannel, <-chan *Request, error) {
     	fullConf := *config
     	fullConf.SetDefaults()
    +	if fullConf.HostKeyCallback == nil {
    +		c.Close()
    +		return nil, nil, nil, errors.New("ssh: must specify HostKeyCallback")
    +	}
    +
     	conn := &connection{
     		sshConn: sshConn{conn: c},
     	}
    @@ -173,6 +179,13 @@ func Dial(network, addr string, config *ClientConfig) (*Client, error) {
     	return NewClient(c, chans, reqs), nil
     }
     
    +// HostKeyCallback is the function type used for verifying server
    +// keys.  A HostKeyCallback must return nil if the host key is OK, or
    +// an error to reject it. It receives the hostname as passed to Dial
    +// or NewClientConn. The remote address is the RemoteAddr of the
    +// net.Conn underlying the the SSH connection.
    +type HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error
    +
     // A ClientConfig structure is used to configure a Client. It must not be
     // modified after having been passed to an SSH function.
     type ClientConfig struct {
    @@ -188,10 +201,12 @@ type ClientConfig struct {
     	// be used during authentication.
     	Auth []AuthMethod
     
    -	// HostKeyCallback, if not nil, is called during the cryptographic
    -	// handshake to validate the server's host key. A nil HostKeyCallback
    -	// implies that all host keys are accepted.
    -	HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error
    +	// HostKeyCallback is called during the cryptographic
    +	// handshake to validate the server's host key. The client
    +	// configuration must supply this callback for the connection
    +	// to succeed. The functions InsecureIgnoreHostKey or
    +	// FixedHostKey can be used for simplistic host key checks.
    +	HostKeyCallback HostKeyCallback
     
     	// ClientVersion contains the version identification string that will
     	// be used for the connection. If empty, a reasonable default is used.
    @@ -209,3 +224,33 @@ type ClientConfig struct {
     	// A Timeout of zero means no timeout.
     	Timeout time.Duration
     }
    +
    +// InsecureIgnoreHostKey returns a function that can be used for
    +// ClientConfig.HostKeyCallback to accept any host key. It should
    +// not be used for production code.
    +func InsecureIgnoreHostKey() HostKeyCallback {
    +	return func(hostname string, remote net.Addr, key PublicKey) error {
    +		return nil
    +	}
    +}
    +
    +type fixedHostKey struct {
    +	key PublicKey
    +}
    +
    +func (f *fixedHostKey) check(hostname string, remote net.Addr, key PublicKey) error {
    +	if f.key == nil {
    +		return fmt.Errorf("ssh: required host key was nil")
    +	}
    +	if !bytes.Equal(key.Marshal(), f.key.Marshal()) {
    +		return fmt.Errorf("ssh: host key mismatch")
    +	}
    +	return nil
    +}
    +
    +// FixedHostKey returns a function for use in
    +// ClientConfig.HostKeyCallback to accept only a specific host key.
    +func FixedHostKey(key PublicKey) HostKeyCallback {
    +	hk := &fixedHostKey{key}
    +	return hk.check
    +}
    
  • ssh/client_test.go+42 0 modified
    @@ -6,13 +6,15 @@ package ssh
     
     import (
     	"net"
    +	"strings"
     	"testing"
     )
     
     func testClientVersion(t *testing.T, config *ClientConfig, expected string) {
     	clientConn, serverConn := net.Pipe()
     	defer clientConn.Close()
     	receivedVersion := make(chan string, 1)
    +	config.HostKeyCallback = InsecureIgnoreHostKey()
     	go func() {
     		version, err := readVersion(serverConn)
     		if err != nil {
    @@ -37,3 +39,43 @@ func TestCustomClientVersion(t *testing.T) {
     func TestDefaultClientVersion(t *testing.T) {
     	testClientVersion(t, &ClientConfig{}, packageVersion)
     }
    +
    +func TestHostKeyCheck(t *testing.T) {
    +	for _, tt := range []struct {
    +		name      string
    +		wantError string
    +		key       PublicKey
    +	}{
    +		{"no callback", "must specify HostKeyCallback", nil},
    +		{"correct key", "", testSigners["rsa"].PublicKey()},
    +		{"mismatch", "mismatch", testSigners["ecdsa"].PublicKey()},
    +	} {
    +		c1, c2, err := netPipe()
    +		if err != nil {
    +			t.Fatalf("netPipe: %v", err)
    +		}
    +		defer c1.Close()
    +		defer c2.Close()
    +		serverConf := &ServerConfig{
    +			NoClientAuth: true,
    +		}
    +		serverConf.AddHostKey(testSigners["rsa"])
    +
    +		go NewServerConn(c1, serverConf)
    +		clientConf := ClientConfig{
    +			User: "user",
    +		}
    +		if tt.key != nil {
    +			clientConf.HostKeyCallback = FixedHostKey(tt.key)
    +		}
    +
    +		_, _, _, err = NewClientConn(c2, "", &clientConf)
    +		if err != nil {
    +			if tt.wantError == "" || !strings.Contains(err.Error(), tt.wantError) {
    +				t.Errorf("%s: got error %q, missing %q", err.Error(), tt.wantError)
    +			}
    +		} else if tt.wantError != "" {
    +			t.Errorf("%s: succeeded, but want error string %q", tt.name, tt.wantError)
    +		}
    +	}
    +}
    
  • ssh/doc.go+3 0 modified
    @@ -14,5 +14,8 @@ others.
     References:
       [PROTOCOL.certkeys]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?rev=HEAD
       [SSH-PARAMETERS]:    http://www.iana.org/assignments/ssh-parameters/ssh-parameters.xml#ssh-parameters-1
    +
    +This package does not fall under the stability promise of the Go language itself,
    +so its API may be changed when pressing needs arise.
     */
     package ssh // import "golang.org/x/crypto/ssh"
    
  • ssh/example_test.go+54 3 modified
    @@ -5,12 +5,16 @@
     package ssh_test
     
     import (
    +	"bufio"
     	"bytes"
     	"fmt"
     	"io/ioutil"
     	"log"
     	"net"
     	"net/http"
    +	"os"
    +	"path/filepath"
    +	"strings"
     
     	"golang.org/x/crypto/ssh"
     	"golang.org/x/crypto/ssh/terminal"
    @@ -90,8 +94,6 @@ func ExampleNewServerConn() {
     	// The incoming Request channel must be serviced.
     	go ssh.DiscardRequests(reqs)
     
    -	// Service the incoming Channel channel.
    -
     	// Service the incoming Channel channel.
     	for newChannel := range chans {
     		// Channels have a type, depending on the application level
    @@ -131,16 +133,59 @@ func ExampleNewServerConn() {
     	}
     }
     
    +func ExampleHostKeyCheck() {
    +	// Every client must provide a host key check.  Here is a
    +	// simple-minded parse of OpenSSH's known_hosts file
    +	host := "hostname"
    +	file, err := os.Open(filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts"))
    +	if err != nil {
    +		log.Fatal(err)
    +	}
    +	defer file.Close()
    +
    +	scanner := bufio.NewScanner(file)
    +	var hostKey ssh.PublicKey
    +	for scanner.Scan() {
    +		fields := strings.Split(scanner.Text(), " ")
    +		if len(fields) != 3 {
    +			continue
    +		}
    +		if strings.Contains(fields[0], host) {
    +			var err error
    +			hostKey, _, _, _, err = ssh.ParseAuthorizedKey(scanner.Bytes())
    +			if err != nil {
    +				log.Fatalf("error parsing %q: %v", fields[2], err)
    +			}
    +			break
    +		}
    +	}
    +
    +	if hostKey == nil {
    +		log.Fatalf("no hostkey for %s", host)
    +	}
    +
    +	config := ssh.ClientConfig{
    +		User:            os.Getenv("USER"),
    +		HostKeyCallback: ssh.FixedHostKey(hostKey),
    +	}
    +
    +	_, err = ssh.Dial("tcp", host+":22", &config)
    +	log.Println(err)
    +}
    +
     func ExampleDial() {
    +	var hostKey ssh.PublicKey
     	// An SSH client is represented with a ClientConn.
     	//
     	// To authenticate with the remote server you must pass at least one
    -	// implementation of AuthMethod via the Auth field in ClientConfig.
    +	// implementation of AuthMethod via the Auth field in ClientConfig,
    +	// and provide a HostKeyCallback.
     	config := &ssh.ClientConfig{
     		User: "username",
     		Auth: []ssh.AuthMethod{
     			ssh.Password("yourpassword"),
     		},
    +		HostKeyCallback: ssh.FixedHostKey(hostKey),
     	}
     	client, err := ssh.Dial("tcp", "yourserver.com:22", config)
     	if err != nil {
    @@ -166,6 +211,7 @@ func ExampleDial() {
     }
     
     func ExamplePublicKeys() {
    +	var hostKey ssh.PublicKey
     	// A public key may be used to authenticate against the remote
     	// server by using an unencrypted PEM-encoded private key file.
     	//
    @@ -188,6 +234,7 @@ func ExamplePublicKeys() {
     			// Use the PublicKeys method for remote authentication.
     			ssh.PublicKeys(signer),
     		},
    +		HostKeyCallback: ssh.FixedHostKey(hostKey),
     	}
     
     	// Connect to the remote server and perform the SSH handshake.
    @@ -199,11 +246,13 @@ func ExamplePublicKeys() {
     }
     
     func ExampleClient_Listen() {
    +	var hostKey ssh.PublicKey
     	config := &ssh.ClientConfig{
     		User: "username",
     		Auth: []ssh.AuthMethod{
     			ssh.Password("password"),
     		},
    +		HostKeyCallback: ssh.FixedHostKey(hostKey),
     	}
     	// Dial your ssh server.
     	conn, err := ssh.Dial("tcp", "localhost:22", config)
    @@ -226,12 +275,14 @@ func ExampleClient_Listen() {
     }
     
     func ExampleSession_RequestPty() {
    +	var hostKey ssh.PublicKey
     	// Create client config
     	config := &ssh.ClientConfig{
     		User: "username",
     		Auth: []ssh.AuthMethod{
     			ssh.Password("password"),
     		},
    +		HostKeyCallback: ssh.FixedHostKey(hostKey),
     	}
     	// Connect to ssh server
     	conn, err := ssh.Dial("tcp", "localhost:22", config)
    
  • ssh/handshake.go+4 6 modified
    @@ -74,7 +74,7 @@ type handshakeTransport struct {
     	startKex chan *pendingKex
     
     	// data for host key checking
    -	hostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error
    +	hostKeyCallback HostKeyCallback
     	dialAddress     string
     	remoteAddr      net.Addr
     
    @@ -614,11 +614,9 @@ func (t *handshakeTransport) client(kex kexAlgorithm, algs *algorithms, magics *
     		return nil, err
     	}
     
    -	if t.hostKeyCallback != nil {
    -		err = t.hostKeyCallback(t.dialAddress, t.remoteAddr, hostKey)
    -		if err != nil {
    -			return nil, err
    -		}
    +	err = t.hostKeyCallback(t.dialAddress, t.remoteAddr, hostKey)
    +	if err != nil {
    +		return nil, err
     	}
     
     	return result, nil
    
  • ssh/handshake_test.go+1 0 modified
    @@ -436,6 +436,7 @@ func testHandshakeErrorHandlingN(t *testing.T, readLimit, writeLimit int, couple
     	clientConf.SetDefaults()
     	clientConn := newHandshakeTransport(&errorKeyingTransport{b, -1, -1}, &clientConf, []byte{'a'}, []byte{'b'})
     	clientConn.hostKeyAlgorithms = []string{key.PublicKey().Type()}
    +	clientConn.hostKeyCallback = InsecureIgnoreHostKey()
     	go clientConn.readLoop()
     	go clientConn.kexLoop()
     
    
  • ssh/session_test.go+7 3 modified
    @@ -59,7 +59,8 @@ func dial(handler serverType, t *testing.T) *Client {
     	}()
     
     	config := &ClientConfig{
    -		User: "testuser",
    +		User:            "testuser",
    +		HostKeyCallback: InsecureIgnoreHostKey(),
     	}
     
     	conn, chans, reqs, err := NewClientConn(c2, "", config)
    @@ -641,7 +642,8 @@ func TestSessionID(t *testing.T) {
     	}
     	serverConf.AddHostKey(testSigners["ecdsa"])
     	clientConf := &ClientConfig{
    -		User: "user",
    +		HostKeyCallback: InsecureIgnoreHostKey(),
    +		User:            "user",
     	}
     
     	go func() {
    @@ -747,7 +749,9 @@ func TestHostKeyAlgorithms(t *testing.T) {
     
     	// By default, we get the preferred algorithm, which is ECDSA 256.
     
    -	clientConf := &ClientConfig{}
    +	clientConf := &ClientConfig{
    +		HostKeyCallback: InsecureIgnoreHostKey(),
    +	}
     	connect(clientConf, KeyAlgoECDSA256)
     
     	// Client asks for RSA explicitly.
    
  • ssh/test/cert_test.go+2 1 modified
    @@ -36,7 +36,8 @@ func TestCertLogin(t *testing.T) {
     	}
     
     	conf := &ssh.ClientConfig{
    -		User: username(),
    +		User:            username(),
    +		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
     	}
     	conf.Auth = append(conf.Auth, ssh.PublicKeys(certSigner))
     	client, err := s.TryDial(conf)
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

15

News mentions

0

No linked articles in our index yet.