From 85c751cb7fc44f2d993c109b5b94f0a17265ae78 Mon Sep 17 00:00:00 2001 From: Porcupiney Hairs Date: Sun, 3 Apr 2022 19:02:26 +0530 Subject: [PATCH 1/2] CPP: PAM Authorization Bypass This PR is similar to my other PRs for [Python](https://github.com/github/codeql/pull/8595) and [Golang](https://github.com/github/codeql-go/pull/709). This PR aims to detect instances were an initiated PAM Transaction invokes the `pam_authenticate` method but does not invoke a call to the pam_acct_mgmt` method. This is bad as a call to `pam_authenticate` only verifies the users credentials. It does not check if the user account is still is a valid state. If only a call to `pam_authenticate` is used to verify the user, a user with an expired account password would still be able to login. This can be prevented by calling the `pam_acct_mgmt` function after a `pam_authenticate` function. --- .../CWE/CWE-285/PamAuthorization.qhelp | 49 +++++++++++++++ .../Security/CWE/CWE-285/PamAuthorization.ql | 33 +++++++++++ .../CWE/CWE-285/PamAuthorizationBad.cpp | 20 +++++++ .../CWE/CWE-285/PamAuthorizationGood.cpp | 24 ++++++++ .../CWE/CWE-285/PamAuthorization.expected | 1 + .../CWE/CWE-285/PamAuthorization.qlref | 1 + .../query-tests/Security/CWE/CWE-285/test.cpp | 59 +++++++++++++++++++ 7 files changed, 187 insertions(+) create mode 100644 cpp/ql/src/experimental/Security/CWE/CWE-285/PamAuthorization.qhelp create mode 100644 cpp/ql/src/experimental/Security/CWE/CWE-285/PamAuthorization.ql create mode 100644 cpp/ql/src/experimental/Security/CWE/CWE-285/PamAuthorizationBad.cpp create mode 100644 cpp/ql/src/experimental/Security/CWE/CWE-285/PamAuthorizationGood.cpp create mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-285/PamAuthorization.expected create mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-285/PamAuthorization.qlref create mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-285/test.cpp diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-285/PamAuthorization.qhelp b/cpp/ql/src/experimental/Security/CWE/CWE-285/PamAuthorization.qhelp new file mode 100644 index 000000000000..6880596e3b73 --- /dev/null +++ b/cpp/ql/src/experimental/Security/CWE/CWE-285/PamAuthorization.qhelp @@ -0,0 +1,49 @@ + + + +

+ 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 a expired login or a password can still access the system. +

+ +
+ + +

+ A call to + pam_authenticate + should be followed by a call to + pam_acct_mgmt + 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 expired with expired creds 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_acct_mgmt + 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/cpp/ql/src/experimental/Security/CWE/CWE-285/PamAuthorization.ql b/cpp/ql/src/experimental/Security/CWE/CWE-285/PamAuthorization.ql new file mode 100644 index 000000000000..1b26774d6a0a --- /dev/null +++ b/cpp/ql/src/experimental/Security/CWE/CWE-285/PamAuthorization.ql @@ -0,0 +1,33 @@ +/** + * @name PAM Authorization bypass + * @description Only using `pam_authenticate` call to authenticate users can lead to authorization vulnerabilities. + * @kind problem + * @problem.severity error + * @id cpp/pam-auth-bypass + * @tags security + * external/cwe/cwe-285 + */ +import cpp +import semmle.code.cpp.dataflow.DataFlow +import semmle.code.cpp.valuenumbering.GlobalValueNumbering + +private class PamAuthCall extends FunctionCall { + PamAuthCall() { + exists(Function f | f.hasName("pam_authenticate") | f.getACallToThisFunction() = this) + } +} + +private class PamActMgmtCall extends FunctionCall { + PamActMgmtCall() { + exists(Function f | f.hasName("pam_acct_mgmt") | f.getACallToThisFunction() = this) + } +} + +from PamAuthCall pa, Expr handle +where + pa.getArgument(0) = handle and + not exists(PamActMgmtCall pac | + globalValueNumber(handle) = globalValueNumber(pac.getArgument(0)) or + DataFlow::localExprFlow(handle, pac.getArgument(0)) + ) +select pa, "This PAM authentication call may be lead to an authorization bypass." diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-285/PamAuthorizationBad.cpp b/cpp/ql/src/experimental/Security/CWE/CWE-285/PamAuthorizationBad.cpp new file mode 100644 index 000000000000..cde024cde343 --- /dev/null +++ b/cpp/ql/src/experimental/Security/CWE/CWE-285/PamAuthorizationBad.cpp @@ -0,0 +1,20 @@ +bool PamAuthGood(const std::string &username_in, + const std::string &password_in, + std::string &authenticated_username) +{ + + struct pam_handle *pamh = nullptr; /* pam session handle */ + + const char *username = username_in.c_str(); + int err = pam_start("test", username, + 0, &pamh); + if (err != PAM_SUCCESS) + { + return false; + } + + err = pam_authenticate(pamh, 0); // BAD + if (err != PAM_SUCCESS) + return err; + return true; +} diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-285/PamAuthorizationGood.cpp b/cpp/ql/src/experimental/Security/CWE/CWE-285/PamAuthorizationGood.cpp new file mode 100644 index 000000000000..e4a2ce008c54 --- /dev/null +++ b/cpp/ql/src/experimental/Security/CWE/CWE-285/PamAuthorizationGood.cpp @@ -0,0 +1,24 @@ +bool PamAuthGood(const std::string &username_in, + const std::string &password_in, + std::string &authenticated_username) +{ + + struct pam_handle *pamh = nullptr; /* pam session handle */ + + const char *username = username_in.c_str(); + int err = pam_start("test", username, + 0, &pamh); + if (err != PAM_SUCCESS) + { + return false; + } + + err = pam_authenticate(pamh, 0); + if (err != PAM_SUCCESS) + return err; + + err = pam_acct_mgmt(pamh, 0); // GOOD + if (err != PAM_SUCCESS) + return err; + return true; +} diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-285/PamAuthorization.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-285/PamAuthorization.expected new file mode 100644 index 000000000000..af34f888df2e --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-285/PamAuthorization.expected @@ -0,0 +1 @@ +| test.cpp:29:11:29:26 | call to pam_authenticate | This PAM authentication call may be lead to an authorization bypass. | diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-285/PamAuthorization.qlref b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-285/PamAuthorization.qlref new file mode 100644 index 000000000000..f1135f7d536a --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-285/PamAuthorization.qlref @@ -0,0 +1 @@ +experimental/Security/CWE/CWE-285/PamAuthorization.ql diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-285/test.cpp b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-285/test.cpp new file mode 100644 index 000000000000..e2753f10775e --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-285/test.cpp @@ -0,0 +1,59 @@ +#include "../../../../../library-tests/dataflow/taint-tests/stl.h" + +using namespace std; + +#define PAM_SUCCESS 1 + +typedef struct pam_handle +{ +}; +int pam_start(std::string servicename, std::string username, int a, struct pam_handle **); +int pam_authenticate(struct pam_handle *, int e); +int pam_acct_mgmt(struct pam_handle *, int e); + +bool PamAuthBad(const std::string &username_in, + const std::string &password_in, + std::string &authenticated_username) +{ + + struct pam_handle *pamh = nullptr; /* pam session handle */ + + const char *username = username_in.c_str(); + int err = pam_start("test", username, + 0, &pamh); + if (err != PAM_SUCCESS) + { + return false; + } + + err = pam_authenticate(pamh, 0); + if (err != PAM_SUCCESS) + return err; + + return true; +} + +bool PamAuthGood(const std::string &username_in, + const std::string &password_in, + std::string &authenticated_username) +{ + + struct pam_handle *pamh = nullptr; /* pam session handle */ + + const char *username = username_in.c_str(); + int err = pam_start("test", username, + 0, &pamh); + if (err != PAM_SUCCESS) + { + return false; + } + + err = pam_authenticate(pamh, 0); + if (err != PAM_SUCCESS) + return err; + + err = pam_acct_mgmt(pamh, 0); + if (err != PAM_SUCCESS) + return err; + return true; +} From 06edb3f3a1e4714c9d4b7074f98425fb3faa03ee Mon Sep 17 00:00:00 2001 From: Porcupiney Hairs Date: Thu, 21 Apr 2022 00:23:49 +0530 Subject: [PATCH 2/2] fix formatting issues --- .../experimental/Security/CWE/CWE-285/PamAuthorization.qhelp | 4 ++-- .../src/experimental/Security/CWE/CWE-285/PamAuthorization.ql | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-285/PamAuthorization.qhelp b/cpp/ql/src/experimental/Security/CWE/CWE-285/PamAuthorization.qhelp index 6880596e3b73..debf30dfa65d 100644 --- a/cpp/ql/src/experimental/Security/CWE/CWE-285/PamAuthorization.qhelp +++ b/cpp/ql/src/experimental/Security/CWE/CWE-285/PamAuthorization.qhelp @@ -30,14 +30,14 @@ chage -E0 `username` and then trying to log in.

    - +

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

    - + diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-285/PamAuthorization.ql b/cpp/ql/src/experimental/Security/CWE/CWE-285/PamAuthorization.ql index 1b26774d6a0a..5292a705d93b 100644 --- a/cpp/ql/src/experimental/Security/CWE/CWE-285/PamAuthorization.ql +++ b/cpp/ql/src/experimental/Security/CWE/CWE-285/PamAuthorization.ql @@ -7,6 +7,7 @@ * @tags security * external/cwe/cwe-285 */ + import cpp import semmle.code.cpp.dataflow.DataFlow import semmle.code.cpp.valuenumbering.GlobalValueNumbering