From 8cddc58c85e2853287c8b36ca9113d8bde213cee Mon Sep 17 00:00:00 2001
From: mboelen <michael@cisofy.com>
Date: Wed, 21 Oct 2015 23:11:03 +0200
Subject: [PATCH] Added logging of maximum password retries

---
 plugins/plugin_pam_phase1 | 393 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 393 insertions(+)

diff --git a/plugins/plugin_pam_phase1 b/plugins/plugin_pam_phase1
index 4beea405..06370b5d 100644
--- a/plugins/plugin_pam_phase1
+++ b/plugins/plugin_pam_phase1
@@ -376,7 +376,400 @@ if [ ${PAM_PASSWORD_STRENGTH_TESTED} -eq 1 ]; then
         fi
 fi
 
+#!/bin/sh
 
+#########################################################################
+#
+#    * DO NOT REMOVE *
+#-----------------------------------------------------
+# PLUGIN_AUTHOR=Michael Boelen <michael.boelen@cisofy.com>
+# PLUGIN_CATEGORY=authentication
+# PLUGIN_DATE=2015-10-01
+# PLUGIN_DESC=PAM
+# PLUGIN_NAME=pam
+# PLUGIN_PACKAGE=all
+# PLUGIN_REQUIRED_TESTS=
+# PLUGIN_VERSION=1.0.0
+#-----------------------------------------------------
+#########################################################################
+#
+    PAM_DIRECTORY="/etc/pam.d"
+    # Test        : PLGN-0010
+    # Description : Check PAM configuration
+    if [ -f /etc/pam.conf -o -d /etc/pam.d ]; 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: /etc/pam.d exists"
+                FIND_FILES=`find ${PAM_DIRECTORY} -type f -print`
+                # First check /etc/pam.conf if it exists.
+                #if [ -f /etc/pam.conf ]; then FIND="/etc/pam.conf ${FIND}"; fi
+                for PAM_FILE in ${FIND_FILES}; do
+                    #echo ""
+                    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 }'`
+                            PARSELINE=0
+                            case ${PAM_TYPE} in
+                                "@include")
+                                    FILE=`echo ${LINE} | awk '{ print $2 }'`
+                                    logtext "Result: Found @include. 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"
+                                ;;
+                            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 }'`
+                                case ${PAM_CONTROL_FLAG} in
+                                        "optional"|"required"|"requisite"|"sufficient")
+                                            Debug "Found a common control flag: ${PAM_CONTROL_FLAG} for ${PAM_MODULE}"
+                                        ;;
+                                        "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
+                                # pam_access                                   v
+                                # pam_deny                    v                v
+                                # pam_group                   v
+                                # pam_krb5                    v
+                                # pam_lastlog                 v
+                                # pam_login_access            v
+                                # pam_nologin                 v
+                                # pam_opie                    v
+                                # pam_opieaccess              v
+                                # pam_passwdqc                v
+                                # pam_permit                  v
+                                # pam_rhosts                  v
+                                # pam_rootok                  v
+                                # pam_securetty               v
+                                # pam_self                    v
+                                # pam_ssh                     v
+                                # pam_unix                    v
+
+                                case ${PAM_MODULE_NAME} in
+                                    pam_access) ;;
+                                    pam_cap) ;;
+                                    pam_debug | pam_deny) ;;
+                                    pam_echo| pam_env | pam_exec |  pam_faildelay) ;;
+                                    pam_filter | pam_ftp) ;;
+                                    # 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 -o "${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_issue) ;;
+                                    pam_keyinit | pam_krb5) ;;
+                                    pam_lastlog | pam_limits) ;;
+                                    # 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_permit) ;;
+                                    pam_rootok) ;;
+                                    pam_rhosts) ;;
+                                    pam_securetty) ;;
+                                    pam_self) ;;
+                                    pam_shells) ;;
+                                    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 | pam_pwhistory)
+                                        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
+                                                    # pam_pwhistory / pam_unix
+                                                    remember)
+                                                        # Minimum length (remove 1 if credits are configured, at later stage in function)
+                                                        logtext "Result: password history configured"
+                                                        DigitsOnly ${VALUE}
+                                                        PAM_PASSWORD_HISTORY_AMOUNT=${VALUE}
+                                                        PAM_PASSWORD_HISTORY_ENABLED=1
+                                                        Debug "Found password history enabled with module ${PAM_MODULE_NAME} and password amount ${PAM_PASSWORD_HISTORY_AMOUNT}"
+                                                    ;;
+                                                esac
+                                            done
+                                        fi
+                                    ;;
+                                    pam_unix_acct| pam_unix_auth | pam_unix_passwd | pam_unix_session | pam_unix2) ;;
+                                    pam_vbox) ;;
+                                    pam_warn | pam_wheel) ;;
+                                    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 [ "${MAX_PASSWORD_RETRY}" = "" ]; then MAX_PASSWORD_RETRY=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 oters
+							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_ENABLED}"
+
+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 ] && [ ${CREDITS_L_PASSWORD} -ge 1 ] && [ ${CREDITS_O_PASSWORD} -ge 1 ] && [ ${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
+logtext "[PAM] Password maximum retry: ${MAX_PASSWORD_RETRY}"
+report "max_password_retry=${MAX_PASSWORD_RETRY}"
+
+# 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
+
+logtext "[PAM] Password history enabled: ${PAM_PASSWORD_HISTORY_ENABLED}"
+logtext "[PAM] Password history amount: ${PAM_PASSWORD_HISTORY_AMOUNT}"
+
+
+#EOF
 
 # If auditd is running, but pam_loginuid not, events might not be properly logged
 if [ ${AUDITD_RUNNING} -eq 1 ]; then