Skip to main content

Improper Hostkey Validation Using SSH

GO501
improper_certificate_validation
CWE-295
⛔️ Error
🔒 Professional Plan

The golang.org/x/crypto/ssh package includes a number of standard methods for accessing SSH servers. A client should always verify the host key of the SSH server in order to avoid a number of security risks including:

  • Man-in-the-middle attacks
  • Session hijacking
  • Data theft

In the case of a host key that is unknown to the client, the host key callback should reject the key to cancel the connection.

Examples

package main

import (
"fmt"
"golang.org/x/crypto/ssh"
)

func main() {
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
ssh.Password("password"),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}

serverAddress := "example.com:22"

conn, err := ssh.Dial("tcp", serverAddress, config)
if err != nil {
fmt.Println("Failed to dial:", err)
return
}
defer conn.Close()

session, err := conn.NewSession()
if err != nil {
fmt.Println("Failed to create session:", err)
return
}
defer session.Close()

output, err := session.CombinedOutput("ls -l")
if err != nil {
fmt.Println("Failed to execute command:", err)
return
}
}

Remediation

Implement a HostKeyCallback fucntion in order to reject connection if the host key is unknown to the client.

package main

import (
"fmt"
"golang.org/x/crypto/ssh"
)

func main() {
hostKeyCallback := func(hostname string, remote net.Addr, key ssh.PublicKey) error {
// Here, we hardcode the known host key (for example purposes)
// In a real-world application, you should replace this with your
// actual host key
knownHostPublicKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ..."))
if err != nil {
return err
}

if ssh.KeysEqual(knownHostPublicKey, key) {
return nil // host key matches
}
return fmt.Errorf("unknown host key for %s", hostname)
}

config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
ssh.Password("password"),
},
HostKeyCallback: hostKeyCallback,
}

serverAddress := "example.com:22"

conn, err := ssh.Dial("tcp", serverAddress, config)
if err != nil {
fmt.Println("Failed to dial:", err)
return
}
defer conn.Close()

session, err := conn.NewSession()
if err != nil {
fmt.Println("Failed to create session:", err)
return
}
defer session.Close()

output, err := session.CombinedOutput("ls -l")
if err != nil {
fmt.Println("Failed to execute command:", err)
return
}
}

False Positives

In the case of a false positive the rule can be suppressed. Simply add a trailing or preceding comment line with either the rule ID (GO501) or rule category name (improper_certificate_validation).

Fix Iconfix
package main

import (
"fmt"
"golang.org/x/crypto/ssh"
)

func main() {
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
ssh.Password("password"),
},
// suppress: GO501
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}

serverAddress := "example.com:22"

conn, err := ssh.Dial("tcp", serverAddress, config)
if err != nil {
fmt.Println("Failed to dial:", err)
return
}
defer conn.Close()

session, err := conn.NewSession()
if err != nil {
fmt.Println("Failed to create session:", err)
return
}
defer session.Close()

output, err := session.CombinedOutput("ls -l")
if err != nil {
fmt.Println("Failed to execute command:", err)
return
}
}

See also