From ae2cc378e5ccc0980fcb722ca16078722845e7b5 Mon Sep 17 00:00:00 2001 From: Porcupiney Hairs Date: Tue, 31 May 2022 01:28:55 +0530 Subject: [PATCH 1/5] Golang : Add Query To Detect PAM Authorization Bugs --- go/ql/src/experimental/CWE-285/PamAuthBad.go | 16 +++++ .../experimental/CWE-285/PamAuthBypass.qhelp | 53 ++++++++++++++ .../src/experimental/CWE-285/PamAuthBypass.ql | 69 +++++++++++++++++++ .../CWE-285/PamAuthBypassLocal.ql | 50 ++++++++++++++ go/ql/src/experimental/CWE-285/PamAuthGood.go | 19 +++++ .../CWE-285/PamAuthBypass.expected | 1 + .../experimental/CWE-285/PamAuthBypass.qlref | 1 + go/ql/test/experimental/CWE-285/go.mod | 5 ++ go/ql/test/experimental/CWE-285/main.go | 28 ++++++++ .../vendor/github.com/msteinert/pam/stub.go | 68 ++++++++++++++++++ .../experimental/CWE-285/vendor/modules.txt | 3 + 11 files changed, 313 insertions(+) create mode 100644 go/ql/src/experimental/CWE-285/PamAuthBad.go create mode 100644 go/ql/src/experimental/CWE-285/PamAuthBypass.qhelp create mode 100644 go/ql/src/experimental/CWE-285/PamAuthBypass.ql create mode 100644 go/ql/src/experimental/CWE-285/PamAuthBypassLocal.ql create mode 100644 go/ql/src/experimental/CWE-285/PamAuthGood.go create mode 100644 go/ql/test/experimental/CWE-285/PamAuthBypass.expected create mode 100644 go/ql/test/experimental/CWE-285/PamAuthBypass.qlref create mode 100644 go/ql/test/experimental/CWE-285/go.mod create mode 100644 go/ql/test/experimental/CWE-285/main.go create mode 100644 go/ql/test/experimental/CWE-285/vendor/github.com/msteinert/pam/stub.go create mode 100644 go/ql/test/experimental/CWE-285/vendor/modules.txt diff --git a/go/ql/src/experimental/CWE-285/PamAuthBad.go b/go/ql/src/experimental/CWE-285/PamAuthBad.go new file mode 100644 index 000000000000..2e6b188a0bfe --- /dev/null +++ b/go/ql/src/experimental/CWE-285/PamAuthBad.go @@ -0,0 +1,16 @@ +func bad() error { + t, err := pam.StartFunc("", "username", func(s pam.Style, msg string) (string, error) { + switch s { + case pam.PromptEchoOff: + return string(pass), nil + } + return "", fmt.Errorf("unsupported message style") + }) + if err != nil { + return nil, err + } + + if err := t.Authenticate(0); err != nil { + return nil, fmt.Errorf("Authenticate: %w", err) + } +} diff --git a/go/ql/src/experimental/CWE-285/PamAuthBypass.qhelp b/go/ql/src/experimental/CWE-285/PamAuthBypass.qhelp new file mode 100644 index 000000000000..921cabae1e42 --- /dev/null +++ b/go/ql/src/experimental/CWE-285/PamAuthBypass.qhelp @@ -0,0 +1,53 @@ + + + +

+ Using only a call to + pam.Authenticate + to check the validity of a login can lead to authorization bypass vulnerabilities. +

+

+ A + pam.Authenticate + only verifies the credentials of a user. It does not check if a user has an + appropriate authorization to actually login. This means a user with an expired + login or a password can still access the system. +

+ +
+ + +

+ A call to + pam.Authenticate + should be followed by a call to + pam.AcctMgmt + to check if a user is allowed to login. +

+
+ + +

+ In the following example, the code only checks the credentials of a user. Hence, + in this case, a user with expired credentials can still login. This can be + verified by creating a new user account, expiring it with + chage -E0 `username` + and then trying to log in. +

+ + +

+ This can be avoided by calling + pam.AcctMgmt + call to verify access as has been done in the snippet shown below. +

+ +
+ + +
  • + Man-Page: + pam_acct_mgmt +
  • +
    +
    \ No newline at end of file diff --git a/go/ql/src/experimental/CWE-285/PamAuthBypass.ql b/go/ql/src/experimental/CWE-285/PamAuthBypass.ql new file mode 100644 index 000000000000..c99bc16faa24 --- /dev/null +++ b/go/ql/src/experimental/CWE-285/PamAuthBypass.ql @@ -0,0 +1,69 @@ +/** + * @name PAM authorization bypass due to incorrect usage + * @description Not using `pam.AcctMgmt` after `pam.Authenticate` to check the validity of a login can lead to authorization bypass. + * @kind problem + * @problem.severity warning + * @id go/unreachable-statement + * @tags maintainability + * correctness + * external/cwe/cwe-561 + * external/cwe/cwe-285 + * @precision very-high + */ + +import go + +predicate isInTestFile(Expr r) { + r.getFile().getAbsolutePath().matches("%test%") and + not r.getFile().getAbsolutePath().matches("%/ql/test/%") +} + +class PamAuthenticate extends Method { + PamAuthenticate() { + this.hasQualifiedName("github.com/msteinert/pam", "Transaction", "Authenticate") + } +} + +class PamAcctMgmt extends Method { + PamAcctMgmt() { this.hasQualifiedName("github.com/msteinert/pam", "Transaction", "AcctMgmt") } +} + +class PamTransaction extends StructType { + PamTransaction() { this.hasQualifiedName("github.com/msteinert/pam", "Transaction") } +} + +class PamStartFunc extends Function { + PamStartFunc() { this.hasQualifiedName("github.com/msteinert/pam", ["StartFunc", "Start"]) } +} + +class PamAuthBypassConfiguration extends TaintTracking::Configuration { + PamAuthBypassConfiguration() { this = "PAM auth bypass" } + + override predicate isSource(DataFlow::Node source) { + exists(PamStartFunc p | p.getACall().getResult(0) = source) + } + + override predicate isSink(DataFlow::Node sink) { + exists(PamAcctMgmt p | p.getACall().getReceiver() = sink) + } +} + +class PamAuthBypassConfig extends TaintTracking::Configuration { + PamAuthBypassConfig() { this = "PAM auth bypass2" } + + override predicate isSource(DataFlow::Node source) { + exists(PamStartFunc p | p.getACall().getResult(0) = source) + } + + override predicate isSink(DataFlow::Node sink) { + exists(PamAuthenticate p | p.getACall().getReceiver() = sink) + } +} + +from + PamAuthBypassConfiguration config, PamAuthBypassConfig config2, DataFlow::Node source, + DataFlow::Node sink +where + not isInTestFile(source.asExpr()) and + (config2.hasFlow(source, sink) and not config.hasFlow(source, _)) +select source, "This Pam transaction may not be secure." diff --git a/go/ql/src/experimental/CWE-285/PamAuthBypassLocal.ql b/go/ql/src/experimental/CWE-285/PamAuthBypassLocal.ql new file mode 100644 index 000000000000..d75e47981fe2 --- /dev/null +++ b/go/ql/src/experimental/CWE-285/PamAuthBypassLocal.ql @@ -0,0 +1,50 @@ +/** + * @name PAM authorization bypass due to incorrect usage + * @description Not using `pam.AcctMgmt` after `pam.Authenticate` to check the validity of a login can lead to authorization bypass. + * @kind problem + * @problem.severity warning + * @id go/unreachable-statement + * @tags maintainability + * correctness + * external/cwe/cwe-561 + * external/cwe/cwe-285 + * @precision very-high + */ + +import go + +predicate isInTestFile(Expr r) { + r.getFile().getAbsolutePath().matches("%test%") and + not r.getFile().getAbsolutePath().matches("%/ql/test/%") +} + +class PamAuthenticate extends Method { + PamAuthenticate() { + this.hasQualifiedName("github.com/msteinert/pam", "Transaction", "Authenticate") + } +} + +class PamAcctMgmt extends Method { + PamAcctMgmt() { this.hasQualifiedName("github.com/msteinert/pam", "Transaction", "AcctMgmt") } +} + +class PamTransaction extends StructType { + PamTransaction() { this.hasQualifiedName("github.com/msteinert/pam", "Transaction") } +} + +class PamStartFunc extends Function { + PamStartFunc() { this.hasQualifiedName("github.com/msteinert/pam", ["StartFunc", "Start"]) } +} + +from + DataFlow::Node source, DataFlow::Node sink, PamAuthenticate auth, PamStartFunc start, + PamAcctMgmt actmgt +where + not isInTestFile(source.asExpr()) and + ( + start.getACall().getResult(0) = source and + sink = auth.getACall().getReceiver() and + DataFlow::localFlow(source, sink) and + not DataFlow::localFlow(source, actmgt.getACall().getReceiver()) + ) +select source, "This Pam transaction may not check authorization correctly." diff --git a/go/ql/src/experimental/CWE-285/PamAuthGood.go b/go/ql/src/experimental/CWE-285/PamAuthGood.go new file mode 100644 index 000000000000..08924b18525c --- /dev/null +++ b/go/ql/src/experimental/CWE-285/PamAuthGood.go @@ -0,0 +1,19 @@ +func good() error { + t, err := pam.StartFunc("", "username", func(s pam.Style, msg string) (string, error) { + switch s { + case pam.PromptEchoOff: + return string(pass), nil + } + return "", fmt.Errorf("unsupported message style") + }) + if err != nil { + return nil, err + } + + if err := t.Authenticate(0); err != nil { + return nil, fmt.Errorf("Authenticate: %w", err) + } + if err := t.AcctMgmt(0); err != nil { + return nil, fmt.Errorf("AcctMgmt: %w", err) + } +} diff --git a/go/ql/test/experimental/CWE-285/PamAuthBypass.expected b/go/ql/test/experimental/CWE-285/PamAuthBypass.expected new file mode 100644 index 000000000000..23441b3361e5 --- /dev/null +++ b/go/ql/test/experimental/CWE-285/PamAuthBypass.expected @@ -0,0 +1 @@ +| main.go:10:2:12:3 | ... := ...[0] | This Pam transaction may not be secure. | \ No newline at end of file diff --git a/go/ql/test/experimental/CWE-285/PamAuthBypass.qlref b/go/ql/test/experimental/CWE-285/PamAuthBypass.qlref new file mode 100644 index 000000000000..8a1d5b259e0b --- /dev/null +++ b/go/ql/test/experimental/CWE-285/PamAuthBypass.qlref @@ -0,0 +1 @@ +experimental/CWE-285/PamAuthBypass.ql \ No newline at end of file diff --git a/go/ql/test/experimental/CWE-285/go.mod b/go/ql/test/experimental/CWE-285/go.mod new file mode 100644 index 000000000000..36e39ab93fb1 --- /dev/null +++ b/go/ql/test/experimental/CWE-285/go.mod @@ -0,0 +1,5 @@ +module main + +go 1.18 + +require github.com/msteinert/pam v1.0.0 \ No newline at end of file diff --git a/go/ql/test/experimental/CWE-285/main.go b/go/ql/test/experimental/CWE-285/main.go new file mode 100644 index 000000000000..b0607a74a410 --- /dev/null +++ b/go/ql/test/experimental/CWE-285/main.go @@ -0,0 +1,28 @@ +package main + +//go:generate depstubber -vendor github.com/msteinert/pam Style,Transaction StartFunc + +import ( + "github.com/msteinert/pam" +) + +func bad() error { + t, _ := pam.StartFunc("", "", func(s pam.Style, msg string) (string, error) { + return "", nil + }) + return t.Authenticate(0) + +} + +func good() error { + t, err := pam.StartFunc("", "", func(s pam.Style, msg string) (string, error) { + return "", nil + }) + err = t.Authenticate(0) + if err != nil { + return err + } + return t.AcctMgmt(0) +} + +func main() {} diff --git a/go/ql/test/experimental/CWE-285/vendor/github.com/msteinert/pam/stub.go b/go/ql/test/experimental/CWE-285/vendor/github.com/msteinert/pam/stub.go new file mode 100644 index 000000000000..8451ad5dcc7c --- /dev/null +++ b/go/ql/test/experimental/CWE-285/vendor/github.com/msteinert/pam/stub.go @@ -0,0 +1,68 @@ +// Code generated by depstubber. DO NOT EDIT. +// This is a simple stub for github.com/msteinert/pam, strictly for use in testing. + +// See the LICENSE file for information about the licensing of the original library. +// Source: github.com/msteinert/pam (exports: Style,Transaction; functions: StartFunc) + +// Package pam is a stub of github.com/msteinert/pam, generated by depstubber. +package pam + +type Flags int + +type Item int + +func StartFunc(_ string, _ string, _ func(Style, string) (string, error)) (*Transaction, error) { + return nil, nil +} + +type Style int + +type Transaction struct{} + +func (_ *Transaction) AcctMgmt(_ Flags) error { + return nil +} + +func (_ *Transaction) Authenticate(_ Flags) error { + return nil +} + +func (_ *Transaction) ChangeAuthTok(_ Flags) error { + return nil +} + +func (_ *Transaction) CloseSession(_ Flags) error { + return nil +} + +func (_ *Transaction) Error() string { + return "" +} + +func (_ *Transaction) GetEnv(_ string) string { + return "" +} + +func (_ *Transaction) GetEnvList() (map[string]string, error) { + return nil, nil +} + +func (_ *Transaction) GetItem(_ Item) (string, error) { + return "", nil +} + +func (_ *Transaction) OpenSession(_ Flags) error { + return nil +} + +func (_ *Transaction) PutEnv(_ string) error { + return nil +} + +func (_ *Transaction) SetCred(_ Flags) error { + return nil +} + +func (_ *Transaction) SetItem(_ Item, _ string) error { + return nil +} diff --git a/go/ql/test/experimental/CWE-285/vendor/modules.txt b/go/ql/test/experimental/CWE-285/vendor/modules.txt new file mode 100644 index 000000000000..5ad327e8bb5b --- /dev/null +++ b/go/ql/test/experimental/CWE-285/vendor/modules.txt @@ -0,0 +1,3 @@ +# github.com/msteinert/pam v1.0.0 +## explicit +github.com/msteinert/pam \ No newline at end of file From 5c5e978d30adf0f3a3fd81696eab6fe69c348b8a Mon Sep 17 00:00:00 2001 From: Porcupiney Hairs Date: Tue, 31 May 2022 03:53:02 +0530 Subject: [PATCH 2/5] Remove local data flow query --- .../CWE-285/PamAuthBypassLocal.ql | 50 ------------------- 1 file changed, 50 deletions(-) delete mode 100644 go/ql/src/experimental/CWE-285/PamAuthBypassLocal.ql diff --git a/go/ql/src/experimental/CWE-285/PamAuthBypassLocal.ql b/go/ql/src/experimental/CWE-285/PamAuthBypassLocal.ql deleted file mode 100644 index d75e47981fe2..000000000000 --- a/go/ql/src/experimental/CWE-285/PamAuthBypassLocal.ql +++ /dev/null @@ -1,50 +0,0 @@ -/** - * @name PAM authorization bypass due to incorrect usage - * @description Not using `pam.AcctMgmt` after `pam.Authenticate` to check the validity of a login can lead to authorization bypass. - * @kind problem - * @problem.severity warning - * @id go/unreachable-statement - * @tags maintainability - * correctness - * external/cwe/cwe-561 - * external/cwe/cwe-285 - * @precision very-high - */ - -import go - -predicate isInTestFile(Expr r) { - r.getFile().getAbsolutePath().matches("%test%") and - not r.getFile().getAbsolutePath().matches("%/ql/test/%") -} - -class PamAuthenticate extends Method { - PamAuthenticate() { - this.hasQualifiedName("github.com/msteinert/pam", "Transaction", "Authenticate") - } -} - -class PamAcctMgmt extends Method { - PamAcctMgmt() { this.hasQualifiedName("github.com/msteinert/pam", "Transaction", "AcctMgmt") } -} - -class PamTransaction extends StructType { - PamTransaction() { this.hasQualifiedName("github.com/msteinert/pam", "Transaction") } -} - -class PamStartFunc extends Function { - PamStartFunc() { this.hasQualifiedName("github.com/msteinert/pam", ["StartFunc", "Start"]) } -} - -from - DataFlow::Node source, DataFlow::Node sink, PamAuthenticate auth, PamStartFunc start, - PamAcctMgmt actmgt -where - not isInTestFile(source.asExpr()) and - ( - start.getACall().getResult(0) = source and - sink = auth.getACall().getReceiver() and - DataFlow::localFlow(source, sink) and - not DataFlow::localFlow(source, actmgt.getACall().getReceiver()) - ) -select source, "This Pam transaction may not check authorization correctly." From 8b32eaf05c737768189789584187eeca9eb1f504 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Tue, 31 May 2022 11:05:40 +0100 Subject: [PATCH 3/5] Copyedits --- go/ql/src/experimental/CWE-285/PamAuthBypass.qhelp | 3 +-- go/ql/src/experimental/CWE-285/PamAuthBypass.ql | 12 ++++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/go/ql/src/experimental/CWE-285/PamAuthBypass.qhelp b/go/ql/src/experimental/CWE-285/PamAuthBypass.qhelp index 921cabae1e42..7bbe34c407a8 100644 --- a/go/ql/src/experimental/CWE-285/PamAuthBypass.qhelp +++ b/go/ql/src/experimental/CWE-285/PamAuthBypass.qhelp @@ -7,8 +7,7 @@ to check the validity of a login can lead to authorization bypass vulnerabilities.

    - A - pam.Authenticate + A pam.Authenticate call only verifies the credentials of a user. It does not check if a user has an appropriate authorization to actually login. This means a user with an expired login or a password can still access the system. diff --git a/go/ql/src/experimental/CWE-285/PamAuthBypass.ql b/go/ql/src/experimental/CWE-285/PamAuthBypass.ql index c99bc16faa24..4775141b86fa 100644 --- a/go/ql/src/experimental/CWE-285/PamAuthBypass.ql +++ b/go/ql/src/experimental/CWE-285/PamAuthBypass.ql @@ -36,8 +36,8 @@ class PamStartFunc extends Function { PamStartFunc() { this.hasQualifiedName("github.com/msteinert/pam", ["StartFunc", "Start"]) } } -class PamAuthBypassConfiguration extends TaintTracking::Configuration { - PamAuthBypassConfiguration() { this = "PAM auth bypass" } +class PamStartToAcctMgmtConfig extends TaintTracking::Configuration { + PamStartToAcctMgmtConfig() { this = "PAM auth bypass (Start to AcctMgmt)" } override predicate isSource(DataFlow::Node source) { exists(PamStartFunc p | p.getACall().getResult(0) = source) @@ -48,8 +48,8 @@ class PamAuthBypassConfiguration extends TaintTracking::Configuration { } } -class PamAuthBypassConfig extends TaintTracking::Configuration { - PamAuthBypassConfig() { this = "PAM auth bypass2" } +class PamStartToAuthenticateConfig extends TaintTracking::Configuration { + PamStartToAuthenticateConfig() { this = "PAM auth bypass (Start to Authenticate)" } override predicate isSource(DataFlow::Node source) { exists(PamStartFunc p | p.getACall().getResult(0) = source) @@ -61,9 +61,9 @@ class PamAuthBypassConfig extends TaintTracking::Configuration { } from - PamAuthBypassConfiguration config, PamAuthBypassConfig config2, DataFlow::Node source, + PamStartToAcctMgmtConfig acctMgmtConfig, PamStartToAuthenticateConfig authConfig, DataFlow::Node source, DataFlow::Node sink where not isInTestFile(source.asExpr()) and - (config2.hasFlow(source, sink) and not config.hasFlow(source, _)) + (authConfig.hasFlow(source, sink) and not acctMgmtConfig.hasFlow(source, _)) select source, "This Pam transaction may not be secure." From cea909f03ecb10222c8a52fafe50eaa5e5edbe8a Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Tue, 31 May 2022 11:14:00 +0100 Subject: [PATCH 4/5] Autoformat --- go/ql/src/experimental/CWE-285/PamAuthBypass.ql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go/ql/src/experimental/CWE-285/PamAuthBypass.ql b/go/ql/src/experimental/CWE-285/PamAuthBypass.ql index 4775141b86fa..34e4e21091ee 100644 --- a/go/ql/src/experimental/CWE-285/PamAuthBypass.ql +++ b/go/ql/src/experimental/CWE-285/PamAuthBypass.ql @@ -61,8 +61,8 @@ class PamStartToAuthenticateConfig extends TaintTracking::Configuration { } from - PamStartToAcctMgmtConfig acctMgmtConfig, PamStartToAuthenticateConfig authConfig, DataFlow::Node source, - DataFlow::Node sink + PamStartToAcctMgmtConfig acctMgmtConfig, PamStartToAuthenticateConfig authConfig, + DataFlow::Node source, DataFlow::Node sink where not isInTestFile(source.asExpr()) and (authConfig.hasFlow(source, sink) and not acctMgmtConfig.hasFlow(source, _)) From d4f9c75315913edb61014a5d88285b13be30981a Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Tue, 31 May 2022 11:14:36 +0100 Subject: [PATCH 5/5] Remove dead code --- go/ql/src/experimental/CWE-285/PamAuthBypass.ql | 4 ---- 1 file changed, 4 deletions(-) diff --git a/go/ql/src/experimental/CWE-285/PamAuthBypass.ql b/go/ql/src/experimental/CWE-285/PamAuthBypass.ql index 34e4e21091ee..06f2904599ed 100644 --- a/go/ql/src/experimental/CWE-285/PamAuthBypass.ql +++ b/go/ql/src/experimental/CWE-285/PamAuthBypass.ql @@ -28,10 +28,6 @@ class PamAcctMgmt extends Method { PamAcctMgmt() { this.hasQualifiedName("github.com/msteinert/pam", "Transaction", "AcctMgmt") } } -class PamTransaction extends StructType { - PamTransaction() { this.hasQualifiedName("github.com/msteinert/pam", "Transaction") } -} - class PamStartFunc extends Function { PamStartFunc() { this.hasQualifiedName("github.com/msteinert/pam", ["StartFunc", "Start"]) } }