The pam_pwquality module's maxclassrepeat parameter controls requirements for
consecutive repeating characters from the same character class. When set to a positive number, it will reject passwords
which contain more than that number of consecutive characters from the same character class. Modify the
maxclassrepeat setting in /etc/security/pwquality.conf to equal
to prevent a run of ( + 1) or more identical characters.
Rationale
Use of a complex password helps to increase the time and resources required to compromise the password.
Password complexity, or strength, is a measure of the effectiveness of a password in resisting
attempts at guessing and brute-force attacks.
Password complexity is one factor of several that determines how long it takes to crack a password. The
more complex a password, the greater the number of possible combinations that need to be tested before the
password is compromised.
ISA-62443-2-1-2009, Security for Industrial Automation and Control Systems Part 2-1: Establishing an Industrial Automation and Control Systems Security Program
# Remediation is applicable only in certain platforms
if rpm --quiet -q pam; then
var_password_pam_maxclassrepeat='<xccdf-1.2:sub xmlns:xccdf-1.2="http://checklists.nist.gov/xccdf/1.2" idref="xccdf_org.ssgproject.content_value_var_password_pam_maxclassrepeat" use="legacy"/>'
if grep -sq maxclassrepeat /etc/security/pwquality.conf.d/*.conf ; then
sed -i "/maxclassrepeat/d" /etc/security/pwquality.conf.d/*.conf
fi
if [ -e "/etc/pam.d/system-auth" ] ; then
PAM_FILE_PATH="/etc/pam.d/system-auth"
if [ -f /usr/bin/authselect ]; then
if ! authselect check; then
echo "
authselect integrity check failed. Remediation aborted!
This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
It is not recommended to manually edit the PAM files when authselect tool is available.
In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
exit 1
fi
CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
# If not already in use, a custom profile is created preserving the enabled features.
if [[ ! $CURRENT_PROFILE == custom/* ]]; then
ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
authselect create-profile hardening -b $CURRENT_PROFILE
CURRENT_PROFILE="custom/hardening"
authselect apply-changes -b --backup=before-hardening-custom-profile
authselect select $CURRENT_PROFILE
for feature in $ENABLED_FEATURES; do
authselect enable-feature $feature;
done
authselect apply-changes -b --backup=after-hardening-custom-profile
fi
PAM_FILE_NAME=$(basename "/etc/pam.d/system-auth")
PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"
authselect apply-changes -b
fi
if grep -qP '^\s*password\s.*\bpam_pwquality.so\s.*\bmaxclassrepeat\b' "$PAM_FILE_PATH"; then
sed -i -E --follow-symlinks 's/(.*password.*pam_pwquality.so.*)\bmaxclassrepeat\b=?[[:alnum:]]*(.*)/\1\2/g' "$PAM_FILE_PATH"
fi
if [ -f /usr/bin/authselect ]; then
authselect apply-changes -b
fi
else
echo "/etc/pam.d/system-auth was not found" >&2
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^maxclassrepeat")
# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_password_pam_maxclassrepeat"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^maxclassrepeat\\>" "/etc/security/pwquality.conf"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
LC_ALL=C sed -i --follow-symlinks "s/^maxclassrepeat\\>.*/$escaped_formatted_output/gi" "/etc/security/pwquality.conf"
else
if [[ -s "/etc/security/pwquality.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/security/pwquality.conf" || true)" ]]; then
LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/security/pwquality.conf"
fi
printf '%s\n' "$formatted_output" >> "/etc/security/pwquality.conf"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation - Ansible
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-OL08-00-020140
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_maxclassrepeat
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: XCCDF Value var_password_pam_maxclassrepeat # promote to variable
set_fact:
var_password_pam_maxclassrepeat: !!str <xccdf-1.2:sub xmlns:xccdf-1.2="http://checklists.nist.gov/xccdf/1.2" idref="xccdf_org.ssgproject.content_value_var_password_pam_maxclassrepeat" use="legacy"/>
tags:
- always
- name: Ensure PAM Enforces Password Requirements - Maximum Consecutive Repeating
Characters from Same Character Class - Find pwquality.conf.d files
ansible.builtin.find:
paths: /etc/security/pwquality.conf.d/
patterns: '*.conf'
register: pwquality_conf_d_files
when: '"pam" in ansible_facts.packages'
tags:
- DISA-STIG-OL08-00-020140
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_maxclassrepeat
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Ensure PAM Enforces Password Requirements - Maximum Consecutive Repeating
Characters from Same Character Class - Ensure maxclassrepeat is not set in pwquality.conf.d
ansible.builtin.lineinfile:
path: '{{ item.path }}'
regexp: ^\s*\bmaxclassrepeat\b.*
state: absent
with_items: '{{ pwquality_conf_d_files.files }}'
when: '"pam" in ansible_facts.packages'
tags:
- DISA-STIG-OL08-00-020140
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_maxclassrepeat
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Ensure PAM Enforces Password Requirements - Maximum Consecutive Repeating
Characters from Same Character Class - Check if /etc/pam.d/system-auth file is
present
ansible.builtin.stat:
path: /etc/pam.d/system-auth
register: result_pam_file_present
when: '"pam" in ansible_facts.packages'
tags:
- DISA-STIG-OL08-00-020140
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_maxclassrepeat
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Ensure PAM Enforces Password Requirements - Maximum Consecutive Repeating
Characters from Same Character Class - Check the proper remediation for the system
block:
- name: Ensure PAM Enforces Password Requirements - Maximum Consecutive Repeating
Characters from Same Character Class - Define the PAM file to be edited as a
local fact
ansible.builtin.set_fact:
pam_file_path: /etc/pam.d/system-auth
- name: Ensure PAM Enforces Password Requirements - Maximum Consecutive Repeating
Characters from Same Character Class - Check if system relies on authselect
tool
ansible.builtin.stat:
path: /usr/bin/authselect
register: result_authselect_present
- name: Ensure PAM Enforces Password Requirements - Maximum Consecutive Repeating
Characters from Same Character Class - Ensure authselect custom profile is used
if authselect is present
block:
- name: Ensure PAM Enforces Password Requirements - Maximum Consecutive Repeating
Characters from Same Character Class - Check integrity of authselect current
profile
ansible.builtin.command:
cmd: authselect check
register: result_authselect_check_cmd
changed_when: false
failed_when: false
- name: Ensure PAM Enforces Password Requirements - Maximum Consecutive Repeating
Characters from Same Character Class - Informative message based on the authselect
integrity check result
ansible.builtin.assert:
that:
- result_authselect_check_cmd.rc == 0
fail_msg:
- authselect integrity check failed. Remediation aborted!
- This remediation could not be applied because an authselect profile was
not selected or the selected profile is not intact.
- It is not recommended to manually edit the PAM files when authselect tool
is available.
- In cases where the default authselect profile does not cover a specific
demand, a custom authselect profile is recommended.
success_msg:
- authselect integrity check passed
- name: Ensure PAM Enforces Password Requirements - Maximum Consecutive Repeating
Characters from Same Character Class - Get authselect current profile
ansible.builtin.shell:
cmd: authselect current -r | awk '{ print $1 }'
register: result_authselect_profile
changed_when: false
when:
- result_authselect_check_cmd is success
- name: Ensure PAM Enforces Password Requirements - Maximum Consecutive Repeating
Characters from Same Character Class - Define the current authselect profile
as a local fact
ansible.builtin.set_fact:
authselect_current_profile: '{{ result_authselect_profile.stdout }}'
authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
when:
- result_authselect_profile is not skipped
- result_authselect_profile.stdout is match("custom/")
- name: Ensure PAM Enforces Password Requirements - Maximum Consecutive Repeating
Characters from Same Character Class - Define the new authselect custom profile
as a local fact
ansible.builtin.set_fact:
authselect_current_profile: '{{ result_authselect_profile.stdout }}'
authselect_custom_profile: custom/hardening
when:
- result_authselect_profile is not skipped
- result_authselect_profile.stdout is not match("custom/")
- name: Ensure PAM Enforces Password Requirements - Maximum Consecutive Repeating
Characters from Same Character Class - Get authselect current features to
also enable them in the custom profile
ansible.builtin.shell:
cmd: authselect current | tail -n+3 | awk '{ print $2 }'
register: result_authselect_features
changed_when: false
when:
- result_authselect_profile is not skipped
- authselect_current_profile is not match("custom/")
- name: Ensure PAM Enforces Password Requirements - Maximum Consecutive Repeating
Characters from Same Character Class - Check if any custom profile with the
same name was already created
ansible.builtin.stat:
path: /etc/authselect/{{ authselect_custom_profile }}
register: result_authselect_custom_profile_present
changed_when: false
when:
- authselect_current_profile is not match("custom/")
- name: Ensure PAM Enforces Password Requirements - Maximum Consecutive Repeating
Characters from Same Character Class - Create an authselect custom profile
based on the current profile
ansible.builtin.command:
cmd: authselect create-profile hardening -b {{ authselect_current_profile
}}
when:
- result_authselect_check_cmd is success
- authselect_current_profile is not match("custom/")
- not result_authselect_custom_profile_present.stat.exists
- name: Ensure PAM Enforces Password Requirements - Maximum Consecutive Repeating
Characters from Same Character Class - Ensure authselect changes are applied
ansible.builtin.command:
cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
when:
- result_authselect_check_cmd is success
- result_authselect_profile is not skipped
- authselect_current_profile is not match("custom/")
- authselect_custom_profile is not match(authselect_current_profile)
- name: Ensure PAM Enforces Password Requirements - Maximum Consecutive Repeating
Characters from Same Character Class - Ensure the authselect custom profile
is selected
ansible.builtin.command:
cmd: authselect select {{ authselect_custom_profile }}
register: result_pam_authselect_select_profile
when:
- result_authselect_check_cmd is success
- result_authselect_profile is not skipped
- authselect_current_profile is not match("custom/")
- authselect_custom_profile is not match(authselect_current_profile)
- name: Ensure PAM Enforces Password Requirements - Maximum Consecutive Repeating
Characters from Same Character Class - Restore the authselect features in
the custom profile
ansible.builtin.command:
cmd: authselect enable-feature {{ item }}
loop: '{{ result_authselect_features.stdout_lines }}'
register: result_pam_authselect_restore_features
when:
- result_authselect_profile is not skipped
- result_authselect_features is not skipped
- result_pam_authselect_select_profile is not skipped
- name: Ensure PAM Enforces Password Requirements - Maximum Consecutive Repeating
Characters from Same Character Class - Ensure authselect changes are applied
ansible.builtin.command:
cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
when:
- result_authselect_check_cmd is success
- result_authselect_profile is not skipped
- result_pam_authselect_restore_features is not skipped
- name: Ensure PAM Enforces Password Requirements - Maximum Consecutive Repeating
Characters from Same Character Class - Change the PAM file to be edited according
to the custom authselect profile
ansible.builtin.set_fact:
pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
| basename }}
when:
- result_authselect_present.stat.exists
- name: Ensure PAM Enforces Password Requirements - Maximum Consecutive Repeating
Characters from Same Character Class - Ensure the "maxclassrepeat" option from
"pam_pwquality.so" is not present in {{ pam_file_path }}
ansible.builtin.replace:
dest: '{{ pam_file_path }}'
regexp: (.*password.*pam_pwquality.so.*)\bmaxclassrepeat\b=?[0-9a-zA-Z]*(.*)
replace: \1\2
register: result_pam_option_removal
- name: Ensure PAM Enforces Password Requirements - Maximum Consecutive Repeating
Characters from Same Character Class - Ensure authselect changes are applied
ansible.builtin.command:
cmd: authselect apply-changes -b
when:
- result_authselect_present.stat.exists
- result_pam_option_removal is changed
when:
- '"pam" in ansible_facts.packages'
- result_pam_file_present.stat.exists
tags:
- DISA-STIG-OL08-00-020140
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_maxclassrepeat
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Ensure PAM Enforces Password Requirements - Maximum Consecutive Repeating
Characters from Same Character Class - Ensure PAM variable maxclassrepeat is set
accordingly
ansible.builtin.lineinfile:
create: true
dest: /etc/security/pwquality.conf
regexp: ^#?\s*maxclassrepeat
line: maxclassrepeat = {{ var_password_pam_maxclassrepeat }}
when: '"pam" in ansible_facts.packages'
tags:
- DISA-STIG-OL08-00-020140
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_maxclassrepeat
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy