Skip to content

Commit 614a765

Browse files
authored
Merge pull request #8775 from porcupineyhairs/cpam
CPP: PAM Authorization Bypass
2 parents 5c6e517 + 06edb3f commit 614a765

File tree

7 files changed

+188
-0
lines changed

7 files changed

+188
-0
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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
11+
<code>pam_authenticate</code>
12+
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.
13+
</p>
14+
15+
</overview>
16+
17+
<recommendation>
18+
<p>
19+
A call to
20+
<code>pam_authenticate</code>
21+
should be followed by a call to
22+
<code>pam_acct_mgmt</code>
23+
to check if a user is allowed to login.
24+
</p>
25+
</recommendation>
26+
27+
<example>
28+
<p>
29+
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
30+
<code>chage -E0 `username` </code>
31+
and then trying to log in.
32+
</p>
33+
<sample src="PamAuthorizationBad.cpp" />
34+
35+
<p>
36+
This can be avoided by calling
37+
<code>pam_acct_mgmt</code>
38+
call to verify access as has been done in the snippet shown below.
39+
</p>
40+
<sample src="PamAuthorizationGood.cpp" />
41+
</example>
42+
43+
<references>
44+
<li>
45+
Man-Page:
46+
<a href="https://man7.org/linux/man-pages/man3/pam_acct_mgmt.3.html">pam_acct_mgmt</a>
47+
</li>
48+
</references>
49+
</qhelp>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* @name PAM Authorization bypass
3+
* @description Only using `pam_authenticate` call to authenticate users can lead to authorization vulnerabilities.
4+
* @kind problem
5+
* @problem.severity error
6+
* @id cpp/pam-auth-bypass
7+
* @tags security
8+
* external/cwe/cwe-285
9+
*/
10+
11+
import cpp
12+
import semmle.code.cpp.dataflow.DataFlow
13+
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
14+
15+
private class PamAuthCall extends FunctionCall {
16+
PamAuthCall() {
17+
exists(Function f | f.hasName("pam_authenticate") | f.getACallToThisFunction() = this)
18+
}
19+
}
20+
21+
private class PamActMgmtCall extends FunctionCall {
22+
PamActMgmtCall() {
23+
exists(Function f | f.hasName("pam_acct_mgmt") | f.getACallToThisFunction() = this)
24+
}
25+
}
26+
27+
from PamAuthCall pa, Expr handle
28+
where
29+
pa.getArgument(0) = handle and
30+
not exists(PamActMgmtCall pac |
31+
globalValueNumber(handle) = globalValueNumber(pac.getArgument(0)) or
32+
DataFlow::localExprFlow(handle, pac.getArgument(0))
33+
)
34+
select pa, "This PAM authentication call may be lead to an authorization bypass."
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
bool PamAuthGood(const std::string &username_in,
2+
const std::string &password_in,
3+
std::string &authenticated_username)
4+
{
5+
6+
struct pam_handle *pamh = nullptr; /* pam session handle */
7+
8+
const char *username = username_in.c_str();
9+
int err = pam_start("test", username,
10+
0, &pamh);
11+
if (err != PAM_SUCCESS)
12+
{
13+
return false;
14+
}
15+
16+
err = pam_authenticate(pamh, 0); // BAD
17+
if (err != PAM_SUCCESS)
18+
return err;
19+
return true;
20+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
bool PamAuthGood(const std::string &username_in,
2+
const std::string &password_in,
3+
std::string &authenticated_username)
4+
{
5+
6+
struct pam_handle *pamh = nullptr; /* pam session handle */
7+
8+
const char *username = username_in.c_str();
9+
int err = pam_start("test", username,
10+
0, &pamh);
11+
if (err != PAM_SUCCESS)
12+
{
13+
return false;
14+
}
15+
16+
err = pam_authenticate(pamh, 0);
17+
if (err != PAM_SUCCESS)
18+
return err;
19+
20+
err = pam_acct_mgmt(pamh, 0); // GOOD
21+
if (err != PAM_SUCCESS)
22+
return err;
23+
return true;
24+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
| test.cpp:29:11:29:26 | call to pam_authenticate | This PAM authentication call may be lead to an authorization bypass. |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
experimental/Security/CWE/CWE-285/PamAuthorization.ql
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#include "../../../../../library-tests/dataflow/taint-tests/stl.h"
2+
3+
using namespace std;
4+
5+
#define PAM_SUCCESS 1
6+
7+
typedef struct pam_handle
8+
{
9+
};
10+
int pam_start(std::string servicename, std::string username, int a, struct pam_handle **);
11+
int pam_authenticate(struct pam_handle *, int e);
12+
int pam_acct_mgmt(struct pam_handle *, int e);
13+
14+
bool PamAuthBad(const std::string &username_in,
15+
const std::string &password_in,
16+
std::string &authenticated_username)
17+
{
18+
19+
struct pam_handle *pamh = nullptr; /* pam session handle */
20+
21+
const char *username = username_in.c_str();
22+
int err = pam_start("test", username,
23+
0, &pamh);
24+
if (err != PAM_SUCCESS)
25+
{
26+
return false;
27+
}
28+
29+
err = pam_authenticate(pamh, 0);
30+
if (err != PAM_SUCCESS)
31+
return err;
32+
33+
return true;
34+
}
35+
36+
bool PamAuthGood(const std::string &username_in,
37+
const std::string &password_in,
38+
std::string &authenticated_username)
39+
{
40+
41+
struct pam_handle *pamh = nullptr; /* pam session handle */
42+
43+
const char *username = username_in.c_str();
44+
int err = pam_start("test", username,
45+
0, &pamh);
46+
if (err != PAM_SUCCESS)
47+
{
48+
return false;
49+
}
50+
51+
err = pam_authenticate(pamh, 0);
52+
if (err != PAM_SUCCESS)
53+
return err;
54+
55+
err = pam_acct_mgmt(pamh, 0);
56+
if (err != PAM_SUCCESS)
57+
return err;
58+
return true;
59+
}

0 commit comments

Comments
 (0)