Skip to content
This repository was archived by the owner on Jan 5, 2023. It is now read-only.

Commit a89b87f

Browse files
authored
CWE-322 InsecureHostKeyCallback (#234)
1 parent 595866a commit a89b87f

12 files changed

Lines changed: 374 additions & 0 deletions

File tree

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>
7+
The <code>ClientConfig</code> specifying the configuration for establishing a SSH connection has a field <code>HostKeyCallback</code> that must be initialized with a function that validates the host key returned by the server.
8+
</p>
9+
10+
<p>
11+
Not properly verifying the host key returned by a server provides attackers with an opportunity to perform a Machine-in-the-Middle (MitM) attack.
12+
A successful attack can compromise the confidentiality and integrity of the information communicated with the server.
13+
</p>
14+
15+
<p>
16+
The <code>ssh</code> package provides the predefined callback <code>InsecureIgnoreHostKey</code> that can be used during development and testing.
17+
It accepts any provided host key.
18+
This callback, or a semantically similar callback, should not be used in production code.
19+
</p>
20+
</overview>
21+
22+
<recommendation>
23+
<p>
24+
The <code>HostKeyCallback</code> field of <code>ClientConfig</code> should be initialized with a function that validates a host key against an allow list.
25+
If a key is not on a predefined allow list, the connection must be terminated and the failed security operation should be logged.
26+
</p>
27+
<p>
28+
When the allow list contains only a single host key then the function <code>FixedHostKey</code> can be used.
29+
</p>
30+
</recommendation>
31+
32+
<example>
33+
<p>The following example shows the use of <code>InsecureIgnoreHostKey</code> and an insecure host key callback implemention commonly used in non-production code.</p>
34+
35+
<sample src="InsecureHostKeyCallbackExample.go" />
36+
37+
<p>The next example shows a secure implementation using the <code>FixedHostKey</code> that implements an allow-list.</p>
38+
39+
<sample src="SecureHostKeyCallbackExample.go" />
40+
41+
</example>
42+
43+
<references>
44+
<li>
45+
Go Dev:
46+
<a href="https://pkg.go.dev/golang.org/x/crypto/ssh?tab=doc">package ssh</a>.
47+
</li>
48+
</references>
49+
</qhelp>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* @name Use of insecure HostKeyCallback implementation
3+
* @description Detects insecure SSL client configurations with an implementation of the `HostKeyCallback` that accepts all host keys.
4+
* @kind path-problem
5+
* @problem.severity error
6+
* @precision very-high
7+
* @id go/insecure-hostkeycallback
8+
* @tags security
9+
*/
10+
11+
import go
12+
import DataFlow::PathGraph
13+
14+
class InsecureIgnoreHostKey extends Function {
15+
InsecureIgnoreHostKey() {
16+
this.hasQualifiedName("golang.org/x/crypto/ssh", "InsecureIgnoreHostKey")
17+
}
18+
}
19+
20+
/** A callback function value that is insecure when used as a `HostKeyCallback`, because it always returns `nil`. */
21+
class InsecureHostKeyCallbackFunc extends DataFlow::Node {
22+
InsecureHostKeyCallbackFunc() {
23+
// Either a call to InsecureIgnoreHostKey(), which we know returns an insecure callback.
24+
this = any(InsecureIgnoreHostKey f).getACall()
25+
or
26+
// Or a callback function in the source code (named or anonymous) that always returns nil.
27+
forex(DataFlow::ResultNode returnValue |
28+
returnValue = this.(DataFlow::FunctionNode).getAResult()
29+
|
30+
returnValue = Builtin::nil().getARead()
31+
)
32+
}
33+
}
34+
35+
class HostKeyCallbackAssignmentConfig extends DataFlow::Configuration {
36+
HostKeyCallbackAssignmentConfig() { this = "HostKeyCallbackAssignmentConfig" }
37+
38+
override predicate isSource(DataFlow::Node source) {
39+
source instanceof InsecureHostKeyCallbackFunc
40+
}
41+
42+
override predicate isSink(DataFlow::Node sink) {
43+
exists(Field f |
44+
f.hasQualifiedName("golang.org/x/crypto/ssh", "ClientConfig", "HostKeyCallback") and
45+
sink = f.getAWrite().getRhs()
46+
)
47+
}
48+
}
49+
50+
from HostKeyCallbackAssignmentConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
51+
where config.hasFlowPath(source, sink)
52+
select sink, source, sink,
53+
"Configuring SSH ClientConfig with insecure HostKeyCallback implementation from $@.",
54+
source.getNode(), "this source"
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package main
2+
3+
import (
4+
"golang.org/x/crypto/ssh"
5+
"net"
6+
)
7+
8+
func main() {}
9+
10+
func insecureIgnoreHostKey() {
11+
_ = &ssh.ClientConfig{
12+
User: "username",
13+
Auth: []ssh.AuthMethod{nil},
14+
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
15+
}
16+
}
17+
18+
func insecureHostKeyCallback() {
19+
_ = &ssh.ClientConfig{
20+
User: "username",
21+
Auth: []ssh.AuthMethod{nil},
22+
HostKeyCallback: ssh.HostKeyCallback(
23+
func(hostname string, remote net.Addr, key ssh.PublicKey) error {
24+
return nil
25+
}),
26+
}
27+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package main
2+
3+
import (
4+
"golang.org/x/crypto/ssh"
5+
"io/ioutil"
6+
)
7+
8+
func main() {}
9+
10+
func secureHostKeyCallback() {
11+
publicKeyBytes, _ := ioutil.ReadFile("allowed_hostkey.pub")
12+
publicKey, _ := ssh.ParsePublicKey(publicKeyBytes)
13+
14+
_ = &ssh.ClientConfig{
15+
User: "username",
16+
Auth: []ssh.AuthMethod{nil},
17+
HostKeyCallback: ssh.FixedHostKey(publicKey),
18+
}
19+
}

ql/src/semmle/go/dataflow/internal/DataFlowUtil.qll

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,11 @@ abstract class FunctionNode extends Node {
234234
* Gets the dataflow node holding the value of the receiver, if any.
235235
*/
236236
abstract ReceiverNode getReceiver();
237+
238+
/**
239+
* Gets a value returned by the given function via a return statement or an assignment to a result variable.
240+
*/
241+
abstract ResultNode getAResult();
237242
}
238243

239244
/** A representation of a function that is declared in the module scope. */
@@ -260,6 +265,10 @@ class GlobalFunctionNode extends FunctionNode, MkGlobalFunctionNode {
260265
) {
261266
func.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
262267
}
268+
269+
override ResultNode getAResult() {
270+
result.getRoot() = getFunction().(DeclaredFunction).getFuncDecl()
271+
}
263272
}
264273

265274
/** A representation of the function that is defined by a function literal. */
@@ -273,6 +282,8 @@ class FuncLitNode extends FunctionNode, ExprNode {
273282
override ReceiverNode getReceiver() { none() }
274283

275284
override string toString() { result = "function literal" }
285+
286+
override ResultNode getAResult() { result.getRoot() = getExpr() }
276287
}
277288

278289
/** A data flow node that represents a call. */
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
edges
2+
| InsecureHostKeyCallbackExample.go:12:4:14:3 | function literal : signature type | InsecureHostKeyCallbackExample.go:11:20:14:4 | type conversion |
3+
| InsecureHostKeyCallbackExample.go:27:14:30:3 | type conversion : signature type | InsecureHostKeyCallbackExample.go:35:20:35:27 | callback |
4+
| InsecureHostKeyCallbackExample.go:28:3:30:2 | function literal : signature type | InsecureHostKeyCallbackExample.go:27:14:30:3 | type conversion : signature type |
5+
| InsecureHostKeyCallbackExample.go:41:3:43:2 | function literal : signature type | InsecureHostKeyCallbackExample.go:48:20:48:48 | type conversion |
6+
| InsecureHostKeyCallbackExample.go:52:39:52:46 | definition of callback : HostKeyCallback | InsecureHostKeyCallbackExample.go:56:20:56:27 | callback |
7+
| InsecureHostKeyCallbackExample.go:52:39:52:46 | definition of callback : signature type | InsecureHostKeyCallbackExample.go:56:20:56:27 | callback |
8+
| InsecureHostKeyCallbackExample.go:63:22:66:3 | type conversion : signature type | InsecureHostKeyCallbackExample.go:68:35:68:50 | insecureCallback : signature type |
9+
| InsecureHostKeyCallbackExample.go:64:3:66:2 | function literal : signature type | InsecureHostKeyCallbackExample.go:63:22:66:3 | type conversion : signature type |
10+
| InsecureHostKeyCallbackExample.go:68:35:68:50 | insecureCallback : signature type | InsecureHostKeyCallbackExample.go:52:39:52:46 | definition of callback : signature type |
11+
| InsecureHostKeyCallbackExample.go:79:35:79:61 | call to InsecureIgnoreHostKey : HostKeyCallback | InsecureHostKeyCallbackExample.go:52:39:52:46 | definition of callback : HostKeyCallback |
12+
nodes
13+
| InsecureHostKeyCallbackExample.go:11:20:14:4 | type conversion | semmle.label | type conversion |
14+
| InsecureHostKeyCallbackExample.go:12:4:14:3 | function literal : signature type | semmle.label | function literal : signature type |
15+
| InsecureHostKeyCallbackExample.go:22:20:22:46 | call to InsecureIgnoreHostKey | semmle.label | call to InsecureIgnoreHostKey |
16+
| InsecureHostKeyCallbackExample.go:27:14:30:3 | type conversion : signature type | semmle.label | type conversion : signature type |
17+
| InsecureHostKeyCallbackExample.go:28:3:30:2 | function literal : signature type | semmle.label | function literal : signature type |
18+
| InsecureHostKeyCallbackExample.go:35:20:35:27 | callback | semmle.label | callback |
19+
| InsecureHostKeyCallbackExample.go:41:3:43:2 | function literal : signature type | semmle.label | function literal : signature type |
20+
| InsecureHostKeyCallbackExample.go:48:20:48:48 | type conversion | semmle.label | type conversion |
21+
| InsecureHostKeyCallbackExample.go:52:39:52:46 | definition of callback : HostKeyCallback | semmle.label | definition of callback : HostKeyCallback |
22+
| InsecureHostKeyCallbackExample.go:52:39:52:46 | definition of callback : signature type | semmle.label | definition of callback : signature type |
23+
| InsecureHostKeyCallbackExample.go:56:20:56:27 | callback | semmle.label | callback |
24+
| InsecureHostKeyCallbackExample.go:63:22:66:3 | type conversion : signature type | semmle.label | type conversion : signature type |
25+
| InsecureHostKeyCallbackExample.go:64:3:66:2 | function literal : signature type | semmle.label | function literal : signature type |
26+
| InsecureHostKeyCallbackExample.go:68:35:68:50 | insecureCallback : signature type | semmle.label | insecureCallback : signature type |
27+
| InsecureHostKeyCallbackExample.go:79:35:79:61 | call to InsecureIgnoreHostKey : HostKeyCallback | semmle.label | call to InsecureIgnoreHostKey : HostKeyCallback |
28+
#select
29+
| InsecureHostKeyCallbackExample.go:11:20:14:4 | type conversion | InsecureHostKeyCallbackExample.go:12:4:14:3 | function literal : signature type | InsecureHostKeyCallbackExample.go:11:20:14:4 | type conversion | Configuring SSH ClientConfig with insecure HostKeyCallback implementation from $@. | InsecureHostKeyCallbackExample.go:12:4:14:3 | function literal | this source |
30+
| InsecureHostKeyCallbackExample.go:22:20:22:46 | call to InsecureIgnoreHostKey | InsecureHostKeyCallbackExample.go:22:20:22:46 | call to InsecureIgnoreHostKey | InsecureHostKeyCallbackExample.go:22:20:22:46 | call to InsecureIgnoreHostKey | Configuring SSH ClientConfig with insecure HostKeyCallback implementation from $@. | InsecureHostKeyCallbackExample.go:22:20:22:46 | call to InsecureIgnoreHostKey | this source |
31+
| InsecureHostKeyCallbackExample.go:35:20:35:27 | callback | InsecureHostKeyCallbackExample.go:28:3:30:2 | function literal : signature type | InsecureHostKeyCallbackExample.go:35:20:35:27 | callback | Configuring SSH ClientConfig with insecure HostKeyCallback implementation from $@. | InsecureHostKeyCallbackExample.go:28:3:30:2 | function literal | this source |
32+
| InsecureHostKeyCallbackExample.go:48:20:48:48 | type conversion | InsecureHostKeyCallbackExample.go:41:3:43:2 | function literal : signature type | InsecureHostKeyCallbackExample.go:48:20:48:48 | type conversion | Configuring SSH ClientConfig with insecure HostKeyCallback implementation from $@. | InsecureHostKeyCallbackExample.go:41:3:43:2 | function literal | this source |
33+
| InsecureHostKeyCallbackExample.go:56:20:56:27 | callback | InsecureHostKeyCallbackExample.go:64:3:66:2 | function literal : signature type | InsecureHostKeyCallbackExample.go:56:20:56:27 | callback | Configuring SSH ClientConfig with insecure HostKeyCallback implementation from $@. | InsecureHostKeyCallbackExample.go:64:3:66:2 | function literal | this source |
34+
| InsecureHostKeyCallbackExample.go:56:20:56:27 | callback | InsecureHostKeyCallbackExample.go:79:35:79:61 | call to InsecureIgnoreHostKey : HostKeyCallback | InsecureHostKeyCallbackExample.go:56:20:56:27 | callback | Configuring SSH ClientConfig with insecure HostKeyCallback implementation from $@. | InsecureHostKeyCallbackExample.go:79:35:79:61 | call to InsecureIgnoreHostKey | this source |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
experimental/CWE-322/InsecureHostKeyCallback.ql
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package main
2+
3+
import "net"
4+
import "fmt"
5+
import "golang.org/x/crypto/ssh"
6+
7+
func insecureSSHClientConfig() {
8+
_ = &ssh.ClientConfig{
9+
User: "user",
10+
Auth: []ssh.AuthMethod{nil},
11+
HostKeyCallback: ssh.HostKeyCallback(
12+
func(hostname string, remote net.Addr, key ssh.PublicKey) error {
13+
return nil
14+
}),
15+
}
16+
}
17+
18+
func insecureSSHClientConfigAlt() {
19+
_ = &ssh.ClientConfig{
20+
User: "user",
21+
Auth: []ssh.AuthMethod{nil},
22+
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
23+
}
24+
}
25+
26+
func insecureSSHClientConfigLocalFlow() {
27+
callback := ssh.HostKeyCallback(
28+
func(hostname string, remote net.Addr, key ssh.PublicKey) error {
29+
return nil
30+
})
31+
32+
_ = &ssh.ClientConfig{
33+
User: "user",
34+
Auth: []ssh.AuthMethod{nil},
35+
HostKeyCallback: callback,
36+
}
37+
}
38+
39+
func insecureSSHClientConfigLocalFlowAlt() {
40+
callback :=
41+
func(hostname string, remote net.Addr, key ssh.PublicKey) error {
42+
return nil
43+
};
44+
45+
_ = &ssh.ClientConfig{
46+
User: "user",
47+
Auth: []ssh.AuthMethod{nil},
48+
HostKeyCallback: ssh.HostKeyCallback(callback),
49+
}
50+
}
51+
52+
func potentialInsecureSSHClientConfig(callback ssh.HostKeyCallback) {
53+
_ = &ssh.ClientConfig{
54+
User: "user",
55+
Auth: []ssh.AuthMethod{nil},
56+
HostKeyCallback: callback,
57+
}
58+
}
59+
60+
func main() {
61+
fmt.Printf("Hello insecure SSH client config!\n")
62+
63+
insecureCallback := ssh.HostKeyCallback(
64+
func(hostname string, remote net.Addr, key ssh.PublicKey) error {
65+
return nil
66+
})
67+
68+
potentialInsecureSSHClientConfig(insecureCallback)
69+
70+
potentiallySecureCallback := ssh.HostKeyCallback(
71+
func(hostname string, remote net.Addr, key ssh.PublicKey) error {
72+
if hostname == "localhost" {
73+
return nil
74+
}
75+
return fmt.Errorf("ssh: Unexpected host for key")
76+
})
77+
78+
potentialInsecureSSHClientConfig(potentiallySecureCallback)
79+
potentialInsecureSSHClientConfig(ssh.InsecureIgnoreHostKey())
80+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module custom-queries-go-tests/insecure-ssh-client-config
2+
3+
go 1.14
4+
5+
require (
6+
github.com/github/depstubber v0.0.0-20200618063247-cc37722ad494 // indirect
7+
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
8+
)

ql/test/experimental/CWE-322/vendor/golang.org/LICENSE

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)