The pam_pwquality module's maxrepeat parameter controls requirements for
consecutive repeating characters. When set to a positive number, it will reject passwords
which contain more than that number of consecutive characters. Modify the maxrepeat 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 the password, the greater the number of possible combinations that need to be tested before the
password is compromised.
Passwords with excessive repeating characters may be more vulnerable to password-guessing attacks.
ISA-62443-2-1-2009, Security for Industrial Automation and Control Systems Part 2-1: Establishing an Industrial Automation and Control Systems Security Program
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-OL08-00-020150
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_maxrepeat
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: XCCDF Value var_password_pam_maxrepeat # promote to variable
set_fact:
var_password_pam_maxrepeat: !!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_maxrepeat" use="legacy"/>
tags:
- always
- name: Set Password Maximum Consecutive Repeating Characters - 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-020150
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_maxrepeat
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Set Password Maximum Consecutive Repeating Characters - Ensure maxrepeat is
not set in pwquality.conf.d
ansible.builtin.lineinfile:
path: '{{ item.path }}'
regexp: ^\s*\bmaxrepeat\b.*
state: absent
with_items: '{{ pwquality_conf_d_files.files }}'
when: '"pam" in ansible_facts.packages'
tags:
- DISA-STIG-OL08-00-020150
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_maxrepeat
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Set Password Maximum Consecutive Repeating Characters - 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-020150
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_maxrepeat
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Set Password Maximum Consecutive Repeating Characters - Check the proper remediation
for the system
block:
- name: Set Password Maximum Consecutive Repeating Characters - Define the PAM file
to be edited as a local fact
ansible.builtin.set_fact:
pam_file_path: /etc/pam.d/system-auth
- name: Set Password Maximum Consecutive Repeating Characters - Check if system
relies on authselect tool
ansible.builtin.stat:
path: /usr/bin/authselect
register: result_authselect_present
- name: Set Password Maximum Consecutive Repeating Characters - Ensure authselect
custom profile is used if authselect is present
block:
- name: Set Password Maximum Consecutive Repeating Characters - Check integrity
of authselect current profile
ansible.builtin.command:
cmd: authselect check
register: result_authselect_check_cmd
changed_when: false
failed_when: false
- name: Set Password Maximum Consecutive Repeating Characters - 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: Set Password Maximum Consecutive Repeating Characters - 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: Set Password Maximum Consecutive Repeating Characters - 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: Set Password Maximum Consecutive Repeating Characters - 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: Set Password Maximum Consecutive Repeating Characters - 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: Set Password Maximum Consecutive Repeating Characters - 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: Set Password Maximum Consecutive Repeating Characters - 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: Set Password Maximum Consecutive Repeating Characters - 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: Set Password Maximum Consecutive Repeating Characters - 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: Set Password Maximum Consecutive Repeating Characters - 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: Set Password Maximum Consecutive Repeating Characters - 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: Set Password Maximum Consecutive Repeating Characters - 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: Set Password Maximum Consecutive Repeating Characters - Ensure the "maxrepeat"
option from "pam_pwquality.so" is not present in {{ pam_file_path }}
ansible.builtin.replace:
dest: '{{ pam_file_path }}'
regexp: (.*password.*pam_pwquality.so.*)\bmaxrepeat\b=?[0-9a-zA-Z]*(.*)
replace: \1\2
register: result_pam_option_removal
- name: Set Password Maximum Consecutive Repeating Characters - 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-020150
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_maxrepeat
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Set Password Maximum Consecutive Repeating Characters - Ensure PAM variable
maxrepeat is set accordingly
ansible.builtin.lineinfile:
create: true
dest: /etc/security/pwquality.conf
regexp: ^#?\s*maxrepeat
line: maxrepeat = {{ var_password_pam_maxrepeat }}
when: '"pam" in ansible_facts.packages'
tags:
- DISA-STIG-OL08-00-020150
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_maxrepeat
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation - Shell Script
# Remediation is applicable only in certain platforms
if rpm --quiet -q pam; then
var_password_pam_maxrepeat='<xccdf-1.2:sub xmlns:xccdf-1.2="http://checklists.nist.gov/xccdf/1.2" idref="xccdf_org.ssgproject.content_value_var_password_pam_maxrepeat" use="legacy"/>'
if grep -sq maxrepeat /etc/security/pwquality.conf.d/*.conf ; then
sed -i "/maxrepeat/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.*\bmaxrepeat\b' "$PAM_FILE_PATH"; then
sed -i -E --follow-symlinks 's/(.*password.*pam_pwquality.so.*)\bmaxrepeat\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' <<< "^maxrepeat")
# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_password_pam_maxrepeat"
# 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 "^maxrepeat\\>" "/etc/security/pwquality.conf"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
LC_ALL=C sed -i --follow-symlinks "s/^maxrepeat\\>.*/$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