Skip to content

Commit 9b7597b

Browse files
authored
Merge pull request github#9377 from porcupineyhairs/goPam
Golang : Add Query To Detect PAM Authorization Bugs
2 parents f598b26 + d4f9c75 commit 9b7597b

File tree

10 files changed

+258
-0
lines changed

10 files changed

+258
-0
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
func bad() error {
2+
t, err := pam.StartFunc("", "username", func(s pam.Style, msg string) (string, error) {
3+
switch s {
4+
case pam.PromptEchoOff:
5+
return string(pass), nil
6+
}
7+
return "", fmt.Errorf("unsupported message style")
8+
})
9+
if err != nil {
10+
return nil, err
11+
}
12+
13+
if err := t.Authenticate(0); err != nil {
14+
return nil, fmt.Errorf("Authenticate: %w", err)
15+
}
16+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
2+
<qhelp>
3+
<overview>
4+
<p>
5+
Using only a call to
6+
<code>pam.Authenticate</code>
7+
to check the validity of a login can lead to authorization bypass vulnerabilities.
8+
</p>
9+
<p>
10+
A <code>pam.Authenticate</code> call
11+
only verifies the credentials of a user. It does not check if a user has an
12+
appropriate authorization to actually login. This means a user with an expired
13+
login or a password can still access the system.
14+
</p>
15+
16+
</overview>
17+
18+
<recommendation>
19+
<p>
20+
A call to
21+
<code>pam.Authenticate</code>
22+
should be followed by a call to
23+
<code>pam.AcctMgmt</code>
24+
to check if a user is allowed to login.
25+
</p>
26+
</recommendation>
27+
28+
<example>
29+
<p>
30+
In the following example, the code only checks the credentials of a user. Hence,
31+
in this case, a user with expired credentials can still login. This can be
32+
verified by creating a new user account, expiring it with
33+
<code>chage -E0 `username` </code>
34+
and then trying to log in.
35+
</p>
36+
<sample src="PamAuthBad.go" />
37+
38+
<p>
39+
This can be avoided by calling
40+
<code>pam.AcctMgmt</code>
41+
call to verify access as has been done in the snippet shown below.
42+
</p>
43+
<sample src="PamAuthGood.go" />
44+
</example>
45+
46+
<references>
47+
<li>
48+
Man-Page:
49+
<a href="https://man7.org/linux/man-pages/man3/pam_acct_mgmt.3.html">pam_acct_mgmt</a>
50+
</li>
51+
</references>
52+
</qhelp>
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* @name PAM authorization bypass due to incorrect usage
3+
* @description Not using `pam.AcctMgmt` after `pam.Authenticate` to check the validity of a login can lead to authorization bypass.
4+
* @kind problem
5+
* @problem.severity warning
6+
* @id go/unreachable-statement
7+
* @tags maintainability
8+
* correctness
9+
* external/cwe/cwe-561
10+
* external/cwe/cwe-285
11+
* @precision very-high
12+
*/
13+
14+
import go
15+
16+
predicate isInTestFile(Expr r) {
17+
r.getFile().getAbsolutePath().matches("%test%") and
18+
not r.getFile().getAbsolutePath().matches("%/ql/test/%")
19+
}
20+
21+
class PamAuthenticate extends Method {
22+
PamAuthenticate() {
23+
this.hasQualifiedName("github.com/msteinert/pam", "Transaction", "Authenticate")
24+
}
25+
}
26+
27+
class PamAcctMgmt extends Method {
28+
PamAcctMgmt() { this.hasQualifiedName("github.com/msteinert/pam", "Transaction", "AcctMgmt") }
29+
}
30+
31+
class PamStartFunc extends Function {
32+
PamStartFunc() { this.hasQualifiedName("github.com/msteinert/pam", ["StartFunc", "Start"]) }
33+
}
34+
35+
class PamStartToAcctMgmtConfig extends TaintTracking::Configuration {
36+
PamStartToAcctMgmtConfig() { this = "PAM auth bypass (Start to AcctMgmt)" }
37+
38+
override predicate isSource(DataFlow::Node source) {
39+
exists(PamStartFunc p | p.getACall().getResult(0) = source)
40+
}
41+
42+
override predicate isSink(DataFlow::Node sink) {
43+
exists(PamAcctMgmt p | p.getACall().getReceiver() = sink)
44+
}
45+
}
46+
47+
class PamStartToAuthenticateConfig extends TaintTracking::Configuration {
48+
PamStartToAuthenticateConfig() { this = "PAM auth bypass (Start to Authenticate)" }
49+
50+
override predicate isSource(DataFlow::Node source) {
51+
exists(PamStartFunc p | p.getACall().getResult(0) = source)
52+
}
53+
54+
override predicate isSink(DataFlow::Node sink) {
55+
exists(PamAuthenticate p | p.getACall().getReceiver() = sink)
56+
}
57+
}
58+
59+
from
60+
PamStartToAcctMgmtConfig acctMgmtConfig, PamStartToAuthenticateConfig authConfig,
61+
DataFlow::Node source, DataFlow::Node sink
62+
where
63+
not isInTestFile(source.asExpr()) and
64+
(authConfig.hasFlow(source, sink) and not acctMgmtConfig.hasFlow(source, _))
65+
select source, "This Pam transaction may not be secure."
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
func good() error {
2+
t, err := pam.StartFunc("", "username", func(s pam.Style, msg string) (string, error) {
3+
switch s {
4+
case pam.PromptEchoOff:
5+
return string(pass), nil
6+
}
7+
return "", fmt.Errorf("unsupported message style")
8+
})
9+
if err != nil {
10+
return nil, err
11+
}
12+
13+
if err := t.Authenticate(0); err != nil {
14+
return nil, fmt.Errorf("Authenticate: %w", err)
15+
}
16+
if err := t.AcctMgmt(0); err != nil {
17+
return nil, fmt.Errorf("AcctMgmt: %w", err)
18+
}
19+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
| main.go:10:2:12:3 | ... := ...[0] | This Pam transaction may not be secure. |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
experimental/CWE-285/PamAuthBypass.ql
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module main
2+
3+
go 1.18
4+
5+
require github.com/msteinert/pam v1.0.0
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package main
2+
3+
//go:generate depstubber -vendor github.com/msteinert/pam Style,Transaction StartFunc
4+
5+
import (
6+
"github.com/msteinert/pam"
7+
)
8+
9+
func bad() error {
10+
t, _ := pam.StartFunc("", "", func(s pam.Style, msg string) (string, error) {
11+
return "", nil
12+
})
13+
return t.Authenticate(0)
14+
15+
}
16+
17+
func good() error {
18+
t, err := pam.StartFunc("", "", func(s pam.Style, msg string) (string, error) {
19+
return "", nil
20+
})
21+
err = t.Authenticate(0)
22+
if err != nil {
23+
return err
24+
}
25+
return t.AcctMgmt(0)
26+
}
27+
28+
func main() {}

go/ql/test/experimental/CWE-285/vendor/github.com/msteinert/pam/stub.go

Lines changed: 68 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# github.com/msteinert/pam v1.0.0
2+
## explicit
3+
github.com/msteinert/pam

0 commit comments

Comments
 (0)