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
).
- Using rule ID
- Using category name
fix
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
}
}
fix
package main
import (
"fmt"
"golang.org/x/crypto/ssh"
)
func main() {
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
ssh.Password("password"),
},
// suppress: improper_certificate_validation
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
}
}