#!/bin/sh ######################################################################### # # * DO NOT REMOVE * #----------------------------------------------------- # PLUGIN_AUTHOR=Michael Boelen # PLUGIN_CATEGORY=authentication # PLUGIN_DATE=2020-03-21 # PLUGIN_DESC=PAM # PLUGIN_NAME=pam # PLUGIN_PACKAGE=all # PLUGIN_REQUIRED_TESTS= # PLUGIN_VERSION=1.0.5 #----------------------------------------------------- ######################################################################### # # Variables CREDITS_D_PASSWORD="" CREDITS_L_PASSWORD="" CREDITS_O_PASSWORD="" CREDITS_U_PASSWORD="" MAX_PASSWORD_RETRY="" MIN_PASSWORD_CLASS="" PAM_DIRECTORY="${ROOTDIR}etc/pam.d" # ######################################################################### # # Test : PLGN-0008 # Description : Check PAM configuration FILE="${ROOTDIR}etc/security/pwquality.conf" if [ -f ${FILE} ]; then PREQS_MET="YES"; else PREQS_MET="NO"; fi Register --test-no PLGN-0008 --preqs-met ${PREQS_MET} --weight L --network NO --description "Check PAM configuration (pwquality.conf)" --progress if [ ${SKIPTEST} -eq 0 ]; then for LINE in $(${GREPBINARY} -v "^#" ${FILE} | ${TRBINARY} -d " "); do for I in ${LINE}; do OPTION=$(echo ${I} | ${AWKBINARY} -F= '{ print $1 }') VALUE=$(echo ${I} | ${AWKBINARY} -F= '{ print $2 }') case ${OPTION} in minlen) DigitsOnly ${VALUE} MIN_PASSWORD_LENGTH=${VALUE} ;; retry) DigitsOnly ${VALUE} MAX_PASSWORD_RETRY=${VALUE} ;; minclass) MIN_PASSWORD_CLASS=${VALUE} ;; dcredit) CREDITS_D_PASSWORD=${VALUE} ;; lcredit) CREDITS_L_PASSWORD=${VALUE} ;; ocredit) CREDITS_O_PASSWORD=${VALUE} ;; ucredit) CREDITS_U_PASSWORD=${VALUE} ;; esac done done fi # ######################################################################### # # Test : PLGN-0010 # Description : Check PAM configuration if [ -f ${ROOTDIR}etc/pam.conf -o -d ${PAM_DIRECTORY} ]; then PREQS_MET="YES"; else PREQS_MET="NO"; fi Register --test-no PLGN-0010 --preqs-met ${PREQS_MET} --weight L --network NO --description "Check PAM configuration" --progress if [ ${SKIPTEST} -eq 0 ]; then FOUNDPROBLEM=0 # Check if the PAM directory structure exists if [ -d ${PAM_DIRECTORY} ]; then LogText "Result: ${PAM_DIRECTORY} exists" if [ ! "${OS}" = "FreeBSD" -a ! "${OS}" = "NetBSD" ]; then FIND_FILES=$(find ${PAM_DIRECTORY} \! -name "*.pam-old" -type f -print) else if [ -f ${PAM_DIRECTORY}/README ]; then LogText "Skipped checking ${OS} ${PAM_DIRECTORY}/README as a PAM file" fi FIND_FILES=$(find ${PAM_DIRECTORY} \! -name "README" \! -name "*.pam-old" -type f -print) fi for PAM_FILE in ${FIND_FILES}; do LogText "Now checking PAM file ${PAM_FILE}" while read line; do # Strip empty lines, commented lines, tabs, line breaks (\), then finally remove all double spaces LINE=$(echo $line | grep -v "^#" | grep -v "^$" | tr '\011' ' ' | sed 's/\\\n/ /' | sed 's/ / /g' | sed 's/ #\(.*\)$//') if [ ! "${LINE}" = "" ]; then PAM_SERVICE=$(echo ${PAM_FILE} | awk -F/ '{ print $NF }') PAM_CONTROL_FLAG="-" PAM_CONTROL_OPTIONS="-" PAM_MODULE="-" PAM_MODULE_OPTIONS="-" PAM_TYPE=$(echo ${LINE} | awk '{ print $1 }' | sed 's/^ *-//g') PARSELINE=0 case ${PAM_TYPE} in "@include") FILE=$(echo ${LINE} | awk '{ print $2 }') Debug "Result: Found @include in ${PAM_FILE}. Does include PAM settings from file ${FILE} (which is individually processed)" ;; "account") PARSELINE=1 ;; "auth") PARSELINE=1 ;; "password") PARSELINE=1 ;; "session") PARSELINE=1 ;; *) LogText "Exception: Unknown PAM type found (${PAM_TYPE})" ;; esac if [ ${PARSELINE} -eq 1 ]; then MULTIPLE_OPTIONS=$(echo ${LINE} | awk '$2 ~ /^\[/') if [ ! "${MULTIPLE_OPTIONS}" = "" ]; then # Needs more parsing, depending on the options found PAM_CONTROL_OPTIONS=$(echo ${LINE} | sed "s/^.*\[//" | sed "s/\].*$//") LogText "Result: Found brackets in line, indicating multiple options for control flags: ${PAM_CONTROL_OPTIONS}" LINE=$(echo ${LINE} | sed "s/ \[.*\] / other /") fi PAM_MODULE=$(echo ${LINE} | awk '{ print $3 }') PAM_MODULE_OPTIONS=$(echo ${LINE} | cut -d ' ' -f 4-) PAM_CONTROL_FLAG=$(echo ${LINE} | awk '{ print $2 }') if [ ${PAM_CONTROL_FLAG} = "include" ]; then FILE=$(echo ${LINE} | awk '{ print $3 }') Debug "Result: Found include in ${PAM_FILE}. Does include PAM settings from file ${FILE} (which is individually processed)" PARSELINE=0 fi fi if [ ${PARSELINE} -eq 1 ]; then case ${PAM_CONTROL_FLAG} in "optional"|"required"|"requisite"|"sufficient") #Debug "Found a common control flag: ${PAM_CONTROL_FLAG} for ${PAM_MODULE}" X=0 # do nothing ;; "other") LogText "Result: brackets used, ignoring control flags" ;; *) LogText "Unknown control flag found (${PAM_CONTROL_FLAG})" ;; esac if [ ! "${PAM_MODULE_OPTIONS}" = "" ]; then LogText "Result: using module ${PAM_MODULE} (${PAM_CONTROL_FLAG}) with options ${PAM_MODULE_OPTIONS}" else PAM_MODULE_OPTIONS="-" LogText "Result: using module ${PAM_MODULE} (${PAM_CONTROL_FLAG}) without options configured" fi PAM_MODULE_NAME=$(echo ${PAM_MODULE} | sed 's/.so$//') # # Specific PAMs are commonly seen on these platforms: # # FreeBSD Linux macOS NetBSD # pam_access v # pam_afpmount v # pam_afslog v # pam_deny v v v v # pam_env v # pam_chroot v v # pam_echo v ? v # pam_exec v ? v # pam_ftpusers v # pam_group v v v # pam_guest v # pam_krb5 v v v # pam_ksu v v # pam_lastlog v v # pam_launchd v # pam_login_access v v # pam_mount v # pam_nologin v v v # pam_ntlm v # pam_opendirectory v # pam_opie v # pam_opieaccess v # pam_passwdqc v # pam_permit v v v # pam_radius v v # pam_rhosts v v # pam_rootok v v v # pam_sacl v # pam_securetty v v v # pam_securityserver v # pam_self v v # pam_skey v # pam_ssh v v # pam_tacplus v # pam_unix v v v # pam_uwtmp v # pam_wheel v # pam_winbind v case ${PAM_MODULE_NAME} in pam_access) ;; pam_afpmount | pam_afslog) ;; pam_cap) ;; pam_debug | pam_deny) ;; pam_echo| pam_env | pam_exec | pam_faildelay) ;; pam_filter | pam_ftp | pam_ftpusers) ;; # Google Authenticator / YubiKey # Common to find it only enabled for SSH pam_google_authenticator | pam_yubico) LogText "Result: found pam_google_authenticator" if [ "${PAM_CONTROL_FLAG}" = "required" ]; then PAM_2F_AUTH_ENABLED=1 PAM_2F_AUTH_REQUIRED=1 Report "authentication_2f_provider[]=${PAM_MODULE_NAME}" Report "authentication_2f_service[]=${PAM_SERVICE}" elif [ "${PAM_CONTROL_FLAG}" = "sufficient" ]; then PAM_2F_AUTH_ENABLED=1 Report "authentication_2f_provider[]=${PAM_MODULE_NAME}" Report "authentication_2f_service[]=${PAM_SERVICE}" else LogText "exception: found 2F authenticator enabled with uncommon control flag: ${PAM_CONTROL_FLAG}" fi ;; pam_group) ;; pam_guest) ;; pam_issue) ;; pam_keyinit | pam_krb5 | pam_ksu) ;; pam_launchd) ;; pam_lastlog | pam_limits) ;; pam_login_access) ;; # Log UID for auditd pam_loginuid) PAM_LOGINUID_FOUND=1 ;; pam_listfile | pam_localuser) ;; pam_mail | pam_mkhomedir | pam_motd) ;; pam_namespace | pam_nologin | pam_ntlm) ;; pam_opendirectory) ;; pam_permit) ;; # Password history - Can be configured via pam_unix or pam_pwhistory pam_pwhistory) LogText "Result: found ${PAM_MODULE} module (password history)" # set default for having pam_pwhistory enabled PAM_PASSWORD_PWHISTORY_ENABLED=1 if [ "${PAM_PASSWORD_PWHISTORY_AMOUNT}" = "" ]; then PAM_PASSWORD_PWHISTORY_AMOUNT=10; fi if [ ! "${PAM_MODULE_OPTIONS}" = "" ]; then for I in ${PAM_MODULE_OPTIONS}; do OPTION=$(echo ${I} | awk -F= '{ print $1 }') VALUE=$(echo ${I} | awk -F= '{ print $2 }') CREDITS_CONFIGURED=0 case ${OPTION} in remember) LogText "Result: password history (remember) configured for pam_pwhistory" DigitsOnly ${VALUE} PAM_PASSWORD_PWHISTORY_AMOUNT=${VALUE} Debug "Found password history enabled with module ${PAM_MODULE_NAME} and password amount ${PAM_PASSWORD_PWHISTORY_AMOUNT}" ;; esac done fi ;; pam_radius) ;; pam_rhosts) ;; pam_rootok) ;; pam_sacl) ;; pam_securetty) ;; pam_securityserver) ;; pam_self) ;; pam_selinux) ;; pam_shells) ;; pam_skey) ;; pam_ssh) LogText "Result: found ${PAM_MODULE} module (SSH authentication/session management)" ReportWarning ${TEST_NO} "Potential security risks using of pam_ssh(8) module." ;; pam_stress | pam_succeed_if | pam_systemd) ;; pam_time | pam_timestamp) ;; pam_umask) ;; # Password history - Can be configured via pam_unix or pam_pwhistory pam_unix) LogText "Result: found ${PAM_MODULE} module (generic)" if [ ! "${PAM_MODULE_OPTIONS}" = "" ]; then for I in ${PAM_MODULE_OPTIONS}; do OPTION=$(echo ${I} | awk -F= '{ print $1 }') VALUE=$(echo ${I} | awk -F= '{ print $2 }') CREDITS_CONFIGURED=0 case ${OPTION} in remember) LogText "Result: password history configured for pam_unix" DigitsOnly ${VALUE} PAM_PASSWORD_UXHISTORY_AMOUNT=${VALUE} PAM_PASSWORD_UXHISTORY_ENABLED=1 Debug "Found password history enabled with module ${PAM_MODULE_NAME} and password amount ${PAM_PASSWORD_UXHISTORY_AMOUNT}" ;; esac done fi ;; pam_unix_acct| pam_unix_auth | pam_unix_passwd | pam_unix_session | pam_unix2) ;; pam_uwtmp) ;; pam_vbox) ;; pam_warn | pam_wheel) ;; pam_winbind) ;; pam_xauth) ;; # Password strength testing pam_cracklib | pam_pwquality) LogText "Result: found module ${PAM_MODULE} for password strength testing" # Set default values if [ "${CREDITS_D_PASSWORD}" = "" ]; then CREDITS_D_PASSWORD=1; fi if [ "${CREDITS_L_PASSWORD}" = "" ]; then CREDITS_L_PASSWORD=1; fi if [ "${CREDITS_O_PASSWORD}" = "" ]; then CREDITS_O_PASSWORD=1; fi if [ "${CREDITS_U_PASSWORD}" = "" ]; then CREDITS_U_PASSWORD=1; fi if [ "${MIN_PASSWORD_CLASS}" = "" ]; then MIN_PASSWORD_CLASS=0; fi if [ "${MIN_PASSWORD_LENGTH}" = "" ]; then MIN_PASSWORD_LENGTH=6; fi PAM_PASSWORD_STRENGTH_TESTED=1 if [ ! "${PAM_MODULE_OPTIONS}" = "" ]; then Debug "Module options configured" for I in ${PAM_MODULE_OPTIONS}; do OPTION=$(echo ${I} | awk -F= '{ print $1 }') Debug ${OPTION} VALUE=$(echo ${I} | awk -F= '{ print $2 }') CREDITS_CONFIGURED=0 case ${OPTION} in minlen) # Minimum length (remove 1 if credits are configured, at later stage in function) LogText "Result: minlen configured" DigitsOnly ${VALUE} MIN_PASSWORD_LENGTH=${VALUE} ;; retry) # Maximum password retry LogText "Result: Max password Retry configured" DigitsOnly ${VALUE} MAX_PASSWORD_RETRY=${VALUE} ;; minclass) # Minimum number of class required out of upper, lower, digit and others LogText "Result: Min number of password class is configured" MIN_PASSWORD_CLASS=${VALUE} ;; dcredit) CREDITS_D_PASSWORD=${VALUE} ;; lcredit) CREDITS_L_PASSWORD=${VALUE} ;; ocredit) CREDITS_O_PASSWORD=${VALUE} ;; ucredit) CREDITS_U_PASSWORD=${VALUE} ;; *) LogText "Result: unknown option found: ${OPTION} with value ${VALUE}" ;; esac done fi ;; pam_tally | pam_tally2) if [ "${PAM_CONTROL_FLAG}" = "required" ]; then LogText "Result: found a required module for countering brute force cracking attempts" Report "pam_auth_brute_force_protection_module[]=${PAM_MODULE_NAME}" PAM_AUTH_BRUTE_FORCE_PROTECTION=1 fi if [ ! "${PAM_MODULE_OPTIONS}" = "" ]; then for I in ${PAM_MODULE_OPTIONS}; do OPTION=$(echo ${I} | awk -F= '{ print $1 }') VALUE=$(echo ${I} | awk -F= '{ print $2 }') case ${OPTION} in deny) AUTH_BLOCK_BAD_LOGIN_ATTEMPTS="${VALUE}" ;; unlock_time) AUTH_UNLOCK_TIME="${VALUE}" ;; esac done fi ;; "-") LogText "NOTE: this module is not parsed, as it uses an unknown control flag or type" ;; *) LogText "Result: found pluggable authentication module ${PAM_MODULE}, which is unknown" ;; esac fi #Debug "Service: ${PAM_SERVICE}" #Debug "Type: ${PAM_TYPE}" #Debug "Control: ${PAM_CONTROL_FLAG}" #Debug "Control options: ${PAM_CONTROL_OPTIONS}" #Debug "Module: ${PAM_MODULE_NAME}" #Debug "Module options: ${PAM_MODULE_OPTIONS}" fi done < ${PAM_FILE} #ParsePAMLine ${J} #StoreSetting "pam" " done fi fi # ################################################################################# # # /etc/security/opasswd should exist when: # password history is enabled via pam_unix # pam_cracklib or pam_pwquality is used # In that case, the file should be owned by root, with 440/640/660 permissions LogText "[PAM] PAM 2F authentication enabled: ${PAM_2F_AUTH_ENABLED}" Report "authentication_two_factor_enabled=${PAM_2F_AUTH_ENABLED}" LogText "[PAM] PAM 2F authentication required: ${PAM_2F_AUTH_REQUIRED}" Report "authentication_two_factor_required=${PAM_2F_AUTH_REQUIRED}" if [ ! "${AUTH_UNLOCK_TIME}" = "-1" ]; then LogText "[PAM] Authentication unlock time: ${AUTH_UNLOCK_TIME}" Report "authentication_unlock_time=${AUTH_UNLOCK_TIME}" else LogText "[PAM] Authentication unlock time: not configured" fi LogText "[PAM] Password brute force protection: ${PAM_AUTH_BRUTE_FORCE_PROTECTION}" if [ ${PAM_AUTH_BRUTE_FORCE_PROTECTION} -eq 1 ]; then Report "authentication_brute_force_protection=1" fi if [ ! "${MIN_PASSWORD_LENGTH}" = "-1" ]; then LogText "[PAM] Minimum password length: ${MIN_PASSWORD_LENGTH}" Report "minimum_password_length=${MIN_PASSWORD_LENGTH}" else LogText "[PAM] Minimum password length: not configured" fi LogText "[PAM] Password strength testing enabled: ${PAM_PASSWORD_STRENGTH_TESTED}" if [ ${PAM_PASSWORD_STRENGTH_TESTED} -eq 1 ]; then Report "password_strength_tested=1" if [ ${CREDITS_D_PASSWORD} -ge 1 -a ${CREDITS_L_PASSWORD} -ge 1 -a ${CREDITS_O_PASSWORD} -ge 1 -a ${CREDITS_U_PASSWORD} -ge 1 ]; then # Show how many password class are required out of 4 LogText "[PAM] Minimum password class out of 4: ${MIN_PASSWORD_CLASS}" Report "min_password_class=${MIN_PASSWORD_CLASS}" else LogText "[PAM] Minimum password class setting of ${MIN_PASSWORD_CLASS} out of 4 is ignored since at least 1 class are forced" Report "min_password_class=ignored" fi # Digits if [ ${CREDITS_D_PASSWORD} -lt 0 ]; then CREDITS_D_PASSWORD=$(echo ${CREDITS_D_PASSWORD} | cut -b 2-) LogText "[PAM] Minimum number of Digital characters required: ${CREDITS_D_PASSWORD}" Report "password_min_digital_required=${CREDITS_D_PASSWORD}" elif [ ${CREDITS_D_PASSWORD} -ge 0 ]; then LogText "[PAM] Maximum credit for Digital characters: ${CREDITS_D_PASSWORD}" Report "password_max_digital_credit=${CREDITS_D_PASSWORD}" fi # Lowercase if [ ${CREDITS_L_PASSWORD} -lt 0 ]; then CREDITS_L_PASSWORD=$(echo ${CREDITS_L_PASSWORD} | cut -b 2-) LogText "[PAM] Minimum number of Lowercase characters required: ${CREDITS_L_PASSWORD}" Report "password_min_l_required=${CREDITS_L_PASSWORD}" elif [ ${CREDITS_L_PASSWORD} -ge 0 ]; then LogText "[PAM] Maximum credit for Lowercase characters: ${CREDITS_L_PASSWORD}" Report "password_max_l_credit=${CREDITS_L_PASSWORD}" fi # Other characters if [ ${CREDITS_O_PASSWORD} -lt 0 ]; then CREDITS_O_PASSWORD=$(echo ${CREDITS_O_PASSWORD} | cut -b 2-) LogText "[PAM] Minimum number of Other characters required: ${CREDITS_O_PASSWORD}" Report "password_min_other_required=${CREDITS_O_PASSWORD}" elif [ ${CREDITS_O_PASSWORD} -ge 0 ]; then LogText "[PAM] Maximum credit for Other characters: ${CREDITS_O_PASSWORD}" Report "password_max_other_credit=${CREDITS_O_PASSWORD}" fi # Uppercase if [ ${CREDITS_U_PASSWORD} -lt 0 ]; then CREDITS_U_PASSWORD=$(echo ${CREDITS_U_PASSWORD} | cut -b 2-) LogText "[PAM] Minimum number of Uppercase characters required: ${CREDITS_U_PASSWORD}" Report "password_min_u_required=${CREDITS_U_PASSWORD}" elif [ ${CREDITS_U_PASSWORD} -ge 0 ]; then LogText "[PAM] Maximum credit for Uppercase characters: ${CREDITS_U_PASSWORD}" Report "password_max_u_credit=${CREDITS_U_PASSWORD}" fi fi # Show how many retries are allowed to change password if [ ! -z "${MAX_PASSWORD_RETRY}" ]; then LogText "[PAM] Password maximum retry: ${MAX_PASSWORD_RETRY}" Report "max_password_retry=${MAX_PASSWORD_RETRY}" else LogText "[PAM] Password maximum retry: Not configured" fi # If auditd is running, but pam_loginuid not, events might not be properly logged if [ ${AUDITD_RUNNING} -eq 1 ]; then if [ ${PAM_LOGINUID_FOUND} -eq 0 ]; then Report "pam_issue[]=pam_loginuid is missing" fi fi if [ ${PAM_PASSWORD_PWHISTORY_ENABLED} -eq 1 ]; then LogText "[PAM] Password history with pam_pwhistory enabled: ${PAM_PASSWORD_PWHISTORY_ENABLED}" LogText "[PAM] Password history with pam_pwhistory amount: ${PAM_PASSWORD_PWHISTORY_AMOUNT}" Report "password_history_amount=${PAM_PASSWORD_PWHISTORY_AMOUNT}" else LogText "[PAM] Password history with pam_pwhistory IS NOT enabled" fi if [ ${PAM_PASSWORD_UXHISTORY_ENABLED} -eq 1 ]; then LogText "[PAM] Password history with pam_unix enabled: ${PAM_PASSWORD_UXHISTORY_ENABLED}" LogText "[PAM] Password history with pam_unix amount: ${PAM_PASSWORD_UXHISTORY_AMOUNT}" Report "password_history_amount=${PAM_PASSWORD_UXHISTORY_AMOUNT}" else LogText "[PAM] Password history with pam_unix IS NOT enabled" fi #EOF