Set Deny For Failed Password Attempts
Description
The Ubuntu 20.04 operating system must lock an account after - at most - 5
consecutive invalid access attempts.
Rationale
By limiting the number of failed logon attempts, the risk of unauthorized
system access via user password guessing, otherwise known as brute-force
attacks, is reduced. Limits are imposed by locking the account.
To configure the operating system to lock an account after three
unsuccessful consecutive access attempts using pam_tally2.so
,
modify the content of both /etc/pam.d/common-auth
and
/etc/pam.d/common-account
as follows:
- add or modify the
pam_tally2.so
module line in
/etc/pam.d/common-auth
to ensure both onerr=fail
and
deny=5
are present. For example:
auth required pam_tally2.so onerr=fail silent audit deny=5
- add or modify the following line in
/etc/pam.d/common-account
:
account required pam_tally2.so
Shell script
The following script can be run on the host to remediate the issue.
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q installed; then
var\_password\_pam\_tally2='5'
# Use a non-number regexp to force update of the value of the deny option
if ! grep -qP '^\s\*auth\s+'"required"'\s+pam\_tally2.so\s\*.\*' "/etc/pam.d/common-auth"; then
# Line matching group + control + module was not found. Check group + module.
if [ "$(grep -cP '^\s\*auth\s+.\*\s+pam\_tally2.so\s\*' "/etc/pam.d/common-auth")" -eq 1 ]; then
# The control is updated only if one single line matches.
sed -i -E --follow-symlinks 's/^(\s\*auth\s+).\*(\bpam\_tally2.so.\*)/\1'"required"' \2/' "/etc/pam.d/common-auth"
else
echo 'auth '"required"' pam\_tally2.so' >> "/etc/pam.d/common-auth"
fi
fi
# Check the option
if ! grep -qP '^\s\*auth\s+'"required"'\s+pam\_tally2.so\s\*.\*\sdeny\b' "/etc/pam.d/common-auth"; then
sed -i -E --follow-symlinks '/\s\*auth\s+'"required"'\s+pam\_tally2.so.\*/ s/$/ deny='"${var\_password\_pam\_tally2}"'/' "/etc/pam.d/common-auth"
else
sed -i -E --follow-symlinks 's/(\s\*auth\s+'"required"'\s+pam\_tally2.so\s+.\*)('"deny"'=)[[:alnum:]]+\s\*(.\*)/\1\2'"${var\_password\_pam\_tally2}"' \3/' "/etc/pam.d/common-auth"
fi
if ! grep -qP '^\s\*auth\s+'"required"'\s+pam\_tally2.so\s\*.\*' "/etc/pam.d/common-auth"; then
# Line matching group + control + module was not found. Check group + module.
if [ "$(grep -cP '^\s\*auth\s+.\*\s+pam\_tally2.so\s\*' "/etc/pam.d/common-auth")" -eq 1 ]; then
# The control is updated only if one single line matches.
sed -i -E --follow-symlinks 's/^(\s\*auth\s+).\*(\bpam\_tally2.so.\*)/\1'"required"' \2/' "/etc/pam.d/common-auth"
else
LAST\_MATCH\_LINE=$(grep -nP "(fail)" "/etc/pam.d/common-auth" | tail -n 1 | cut -d: -f 1)
if [ ! -z $LAST\_MATCH\_LINE ]; then
sed -i --follow-symlinks $LAST\_MATCH\_LINE' a auth '"required"' pam\_tally2.so' "/etc/pam.d/common-auth"
else
echo 'auth '"required"' pam\_tally2.so' >> "/etc/pam.d/common-auth"
fi
fi
fi
# Check the option
if ! grep -qP '^\s\*auth\s+'"required"'\s+pam\_tally2.so\s\*.\*\sonerr\b' "/etc/pam.d/common-auth"; then
sed -i -E --follow-symlinks '/\s\*auth\s+'"required"'\s+pam\_tally2.so.\*/ s/$/ onerr='"fail"'/' "/etc/pam.d/common-auth"
else
sed -i -E --follow-symlinks 's/(\s\*auth\s+'"required"'\s+pam\_tally2.so\s+.\*)('"onerr"'=)[[:alnum:]]+\s\*(.\*)/\1\2'"fail"' \3/' "/etc/pam.d/common-auth"
fi
if ! grep -qP '^\s\*account\s+'"required"'\s+pam\_tally2.so\s\*.\*' "/etc/pam.d/common-account"; then
# Line matching group + control + module was not found. Check group + module.
if [ "$(grep -cP '^\s\*account\s+.\*\s+pam\_tally2.so\s\*' "/etc/pam.d/common-account")" -eq 1 ]; then
# The control is updated only if one single line matches.
sed -i -E --follow-symlinks 's/^(\s\*account\s+).\*(\bpam\_tally2.so.\*)/\1'"required"' \2/' "/etc/pam.d/common-account"
else
echo 'account '"required"' pam\_tally2.so' >> "/etc/pam.d/common-account"
fi
fi
# Check the option
if ! grep -qP '^\s\*account\s+'"required"'\s+pam\_tally2.so\s\*.\*\s\b' "/etc/pam.d/common-account"; then
sed -i -E --follow-symlinks '/\s\*account\s+'"required"'\s+pam\_tally2.so.\*/ s/$/ /' "/etc/pam.d/common-account"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Ansible playbook
The following playbook can be run with Ansible to remediate the issue.
- name: Gather the package facts
package\_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010072
- PCI-DSS-Req-8.1.6
- PCI-DSSv4-8.3.4
- accounts\_passwords\_pam\_tally2
- configure\_strategy
- low\_complexity
- low\_disruption
- medium\_severity
- no\_reboot\_needed
- name: XCCDF Value var\_password\_pam\_tally2 # promote to variable
set\_fact:
var\_password\_pam\_tally2: !!str 5
tags:
- always
- name: Set Deny For Failed Password Attempts - Check if expected PAM module line
is present in /etc/pam.d/common-auth
ansible.builtin.lineinfile:
path: /etc/pam.d/common-auth
regexp: ^\s\*auth\s+required\s+pam\_tally2.so\s\*.\*
state: absent
check\_mode: true
changed\_when: false
register: result\_pam\_line\_present
when: '"libpam-runtime" in ansible\_facts.packages'
tags:
- DISA-STIG-UBTU-20-010072
- PCI-DSS-Req-8.1.6
- PCI-DSSv4-8.3.4
- accounts\_passwords\_pam\_tally2
- configure\_strategy
- low\_complexity
- low\_disruption
- medium\_severity
- no\_reboot\_needed
- name: Set Deny For Failed Password Attempts - Include or update the PAM module line
in /etc/pam.d/common-auth
block:
- name: Set Deny For Failed Password Attempts - Check if required PAM module line
is present in /etc/pam.d/common-auth with different control
ansible.builtin.lineinfile:
path: /etc/pam.d/common-auth
regexp: ^\s\*auth\s+.\*\s+pam\_tally2.so\s\*
state: absent
check\_mode: true
changed\_when: false
register: result\_pam\_line\_other\_control\_present
- name: Set Deny For Failed Password Attempts - Ensure the correct control for the
required PAM module line in /etc/pam.d/common-auth
ansible.builtin.replace:
dest: /etc/pam.d/common-auth
regexp: ^(\s\*auth\s+).\*(\bpam\_tally2.so.\*)
replace: \1required \2
register: result\_pam\_module\_edit
when:
- result\_pam\_line\_other\_control\_present.found == 1
- name: Set Deny For Failed Password Attempts - Ensure the required PAM module line
is included in /etc/pam.d/common-auth
ansible.builtin.lineinfile:
dest: /etc/pam.d/common-auth
line: auth required pam\_tally2.so
register: result\_pam\_module\_add
when:
- result\_pam\_line\_other\_control\_present.found == 0 or result\_pam\_line\_other\_control\_present.found
> 1
- name: Set Deny For Failed Password Attempts - Ensure authselect changes are applied
ansible.builtin.command:
cmd: authselect apply-changes -b
when:
- result\_authselect\_present is defined
- result\_authselect\_present.stat.exists
- |-
(result\_pam\_module\_add is defined and result\_pam\_module\_add.changed)
or (result\_pam\_module\_edit is defined and result\_pam\_module\_edit.changed)
when:
- '"libpam-runtime" in ansible\_facts.packages'
- result\_pam\_line\_present.found is defined
- result\_pam\_line\_present.found == 0
tags:
- DISA-STIG-UBTU-20-010072
- PCI-DSS-Req-8.1.6
- PCI-DSSv4-8.3.4
- accounts\_passwords\_pam\_tally2
- configure\_strategy
- low\_complexity
- low\_disruption
- medium\_severity
- no\_reboot\_needed
- name: Set Deny For Failed Password Attempts - Check if the required PAM module option
is present in /etc/pam.d/common-auth
ansible.builtin.lineinfile:
path: /etc/pam.d/common-auth
regexp: ^\s\*auth\s+required\s+pam\_tally2.so\s\*.\*\sdeny\b
state: absent
check\_mode: true
changed\_when: false
register: result\_pam\_module\_deny\_option\_present
when: '"libpam-runtime" in ansible\_facts.packages'
tags:
- DISA-STIG-UBTU-20-010072
- PCI-DSS-Req-8.1.6
- PCI-DSSv4-8.3.4
- accounts\_passwords\_pam\_tally2
- configure\_strategy
- low\_complexity
- low\_disruption
- medium\_severity
- no\_reboot\_needed
- name: Set Deny For Failed Password Attempts - Ensure the "deny" PAM option for "pam\_tally2.so"
is included in /etc/pam.d/common-auth
ansible.builtin.lineinfile:
path: /etc/pam.d/common-auth
backrefs: true
regexp: ^(\s\*auth\s+required\s+pam\_tally2.so.\*)
line: \1 deny={{ var\_password\_pam\_tally2 }}
state: present
register: result\_pam\_deny\_add
when:
- '"libpam-runtime" in ansible\_facts.packages'
- result\_pam\_module\_deny\_option\_present.found == 0
tags:
- DISA-STIG-UBTU-20-010072
- PCI-DSS-Req-8.1.6
- PCI-DSSv4-8.3.4
- accounts\_passwords\_pam\_tally2
- configure\_strategy
- low\_complexity
- low\_disruption
- medium\_severity
- no\_reboot\_needed
- name: Set Deny For Failed Password Attempts - Ensure the required value for "deny"
PAM option from "pam\_tally2.so" in /etc/pam.d/common-auth
ansible.builtin.lineinfile:
path: /etc/pam.d/common-auth
backrefs: true
regexp: ^(\s\*auth\s+required\s+pam\_tally2.so\s+.\*)(deny)=[0-9a-zA-Z]+\s\*(.\*)
line: \1\2={{ var\_password\_pam\_tally2 }} \3
register: result\_pam\_deny\_edit
when:
- '"libpam-runtime" in ansible\_facts.packages'
- result\_pam\_module\_deny\_option\_present.found > 0
tags:
- DISA-STIG-UBTU-20-010072
- PCI-DSS-Req-8.1.6
- PCI-DSSv4-8.3.4
- accounts\_passwords\_pam\_tally2
- configure\_strategy
- low\_complexity
- low\_disruption
- medium\_severity
- no\_reboot\_needed
- name: Set Deny For Failed Password Attempts - Check if expected PAM module line
is present in /etc/pam.d/common-auth
ansible.builtin.lineinfile:
path: /etc/pam.d/common-auth
regexp: ^\s\*auth\s+required\s+pam\_tally2.so\s\*.\*
state: absent
check\_mode: true
changed\_when: false
register: result\_pam\_line\_present
when: '"libpam-runtime" in ansible\_facts.packages'
tags:
- DISA-STIG-UBTU-20-010072
- PCI-DSS-Req-8.1.6
- PCI-DSSv4-8.3.4
- accounts\_passwords\_pam\_tally2
- configure\_strategy
- low\_complexity
- low\_disruption
- medium\_severity
- no\_reboot\_needed
- name: Set Deny For Failed Password Attempts - Include or update the PAM module line
in /etc/pam.d/common-auth
block:
- name: Set Deny For Failed Password Attempts - Check if required PAM module line
is present in /etc/pam.d/common-auth with different control
ansible.builtin.lineinfile:
path: /etc/pam.d/common-auth
regexp: ^\s\*auth\s+.\*\s+pam\_tally2.so\s\*
state: absent
check\_mode: true
changed\_when: false
register: result\_pam\_line\_other\_control\_present
- name: Set Deny For Failed Password Attempts - Ensure the correct control for the
required PAM module line in /etc/pam.d/common-auth
ansible.builtin.replace:
dest: /etc/pam.d/common-auth
regexp: ^(\s\*auth\s+).\*(\bpam\_tally2.so.\*)
replace: \1required \2
register: result\_pam\_module\_edit
when:
- result\_pam\_line\_other\_control\_present.found == 1
- name: Set Deny For Failed Password Attempts - Ensure the required PAM module line
is included in /etc/pam.d/common-auth
ansible.builtin.lineinfile:
dest: /etc/pam.d/common-auth
insertafter: (fail)
line: auth required pam\_tally2.so
register: result\_pam\_module\_add
when:
- result\_pam\_line\_other\_control\_present.found == 0 or result\_pam\_line\_other\_control\_present.found
> 1
- name: Set Deny For Failed Password Attempts - Ensure authselect changes are applied
ansible.builtin.command:
cmd: authselect apply-changes -b
when:
- result\_authselect\_present is defined
- result\_authselect\_present.stat.exists
- |-
(result\_pam\_module\_add is defined and result\_pam\_module\_add.changed)
or (result\_pam\_module\_edit is defined and result\_pam\_module\_edit.changed)
when:
- '"libpam-runtime" in ansible\_facts.packages'
- result\_pam\_line\_present.found is defined
- result\_pam\_line\_present.found == 0
tags:
- DISA-STIG-UBTU-20-010072
- PCI-DSS-Req-8.1.6
- PCI-DSSv4-8.3.4
- accounts\_passwords\_pam\_tally2
- configure\_strategy
- low\_complexity
- low\_disruption
- medium\_severity
- no\_reboot\_needed
- name: Set Deny For Failed Password Attempts - Check if the required PAM module option
is present in /etc/pam.d/common-auth
ansible.builtin.lineinfile:
path: /etc/pam.d/common-auth
regexp: ^\s\*auth\s+required\s+pam\_tally2.so\s\*.\*\sonerr\b
state: absent
check\_mode: true
changed\_when: false
register: result\_pam\_module\_onerr\_option\_present
when: '"libpam-runtime" in ansible\_facts.packages'
tags:
- DISA-STIG-UBTU-20-010072
- PCI-DSS-Req-8.1.6
- PCI-DSSv4-8.3.4
- accounts\_passwords\_pam\_tally2
- configure\_strategy
- low\_complexity
- low\_disruption
- medium\_severity
- no\_reboot\_needed
- name: Set Deny For Failed Password Attempts - Ensure the "onerr" PAM option for
"pam\_tally2.so" is included in /etc/pam.d/common-auth
ansible.builtin.lineinfile:
path: /etc/pam.d/common-auth
backrefs: true
regexp: ^(\s\*auth\s+required\s+pam\_tally2.so.\*)
line: \1 onerr=fail
state: present
register: result\_pam\_onerr\_add
when:
- '"libpam-runtime" in ansible\_facts.packages'
- result\_pam\_module\_onerr\_option\_present.found == 0
tags:
- DISA-STIG-UBTU-20-010072
- PCI-DSS-Req-8.1.6
- PCI-DSSv4-8.3.4
- accounts\_passwords\_pam\_tally2
- configure\_strategy
- low\_complexity
- low\_disruption
- medium\_severity
- no\_reboot\_needed
- name: Set Deny For Failed Password Attempts - Ensure the required value for "onerr"
PAM option from "pam\_tally2.so" in /etc/pam.d/common-auth
ansible.builtin.lineinfile:
path: /etc/pam.d/common-auth
backrefs: true
regexp: ^(\s\*auth\s+required\s+pam\_tally2.so\s+.\*)(onerr)=[0-9a-zA-Z]+\s\*(.\*)
line: \1\2=fail \3
register: result\_pam\_onerr\_edit
when:
- '"libpam-runtime" in ansible\_facts.packages'
- result\_pam\_module\_onerr\_option\_present.found > 0
tags:
- DISA-STIG-UBTU-20-010072
- PCI-DSS-Req-8.1.6
- PCI-DSSv4-8.3.4
- accounts\_passwords\_pam\_tally2
- configure\_strategy
- low\_complexity
- low\_disruption
- medium\_severity
- no\_reboot\_needed
- name: Set Deny For Failed Password Attempts - Check if expected PAM module line
is present in /etc/pam.d/common-account
ansible.builtin.lineinfile:
path: /etc/pam.d/common-account
regexp: ^\s\*account\s+required\s+pam\_tally2.so\s\*.\*
state: absent
check\_mode: true
changed\_when: false
register: result\_pam\_line\_present
when: '"libpam-runtime" in ansible\_facts.packages'
tags:
- DISA-STIG-UBTU-20-010072
- PCI-DSS-Req-8.1.6
- PCI-DSSv4-8.3.4
- accounts\_passwords\_pam\_tally2
- configure\_strategy
- low\_complexity
- low\_disruption
- medium\_severity
- no\_reboot\_needed
- name: Set Deny For Failed Password Attempts - Include or update the PAM module line
in /etc/pam.d/common-account
block:
- name: Set Deny For Failed Password Attempts - Check if required PAM module line
is present in /etc/pam.d/common-account with different control
ansible.builtin.lineinfile:
path: /etc/pam.d/common-account
regexp: ^\s\*account\s+.\*\s+pam\_tally2.so\s\*
state: absent
check\_mode: true
changed\_when: false
register: result\_pam\_line\_other\_control\_present
- name: Set Deny For Failed Password Attempts - Ensure the correct control for the
required PAM module line in /etc/pam.d/common-account
ansible.builtin.replace:
dest: /etc/pam.d/common-account
regexp: ^(\s\*account\s+).\*(\bpam\_tally2.so.\*)
replace: \1required \2
register: result\_pam\_module\_edit
when:
- result\_pam\_line\_other\_control\_present.found == 1
- name: Set Deny For Failed Password Attempts - Ensure the required PAM module line
is included in /etc/pam.d/common-account
ansible.builtin.lineinfile:
dest: /etc/pam.d/common-account
line: account required pam\_tally2.so
register: result\_pam\_module\_add
when:
- result\_pam\_line\_other\_control\_present.found == 0 or result\_pam\_line\_other\_control\_present.found
> 1
- name: Set Deny For Failed Password Attempts - Ensure authselect changes are applied
ansible.builtin.command:
cmd: authselect apply-changes -b
when:
- result\_authselect\_present is defined
- result\_authselect\_present.stat.exists
- |-
(result\_pam\_module\_add is defined and result\_pam\_module\_add.changed)
or (result\_pam\_module\_edit is defined and result\_pam\_module\_edit.changed)
when:
- '"libpam-runtime" in ansible\_facts.packages'
- result\_pam\_line\_present.found is defined
- result\_pam\_line\_present.found == 0
tags:
- DISA-STIG-UBTU-20-010072
- PCI-DSS-Req-8.1.6
- PCI-DSSv4-8.3.4
- accounts\_passwords\_pam\_tally2
- configure\_strategy
- low\_complexity
- low\_disruption
- medium\_severity
- no\_reboot\_needed
- name: Set Deny For Failed Password Attempts - Check if the required PAM module option
is present in /etc/pam.d/common-account
ansible.builtin.lineinfile:
path: /etc/pam.d/common-account
regexp: ^\s\*account\s+required\s+pam\_tally2.so\s\*.\*\s\b
state: absent
check\_mode: true
changed\_when: false
register: result\_pam\_module\_\_option\_present
when: '"libpam-runtime" in ansible\_facts.packages'
tags:
- DISA-STIG-UBTU-20-010072
- PCI-DSS-Req-8.1.6
- PCI-DSSv4-8.3.4
- accounts\_passwords\_pam\_tally2
- configure\_strategy
- low\_complexity
- low\_disruption
- medium\_severity
- no\_reboot\_needed
- name: Set Deny For Failed Password Attempts - Ensure the "" PAM option for "pam\_tally2.so"
is included in /etc/pam.d/common-account
ansible.builtin.lineinfile:
path: /etc/pam.d/common-account
backrefs: true
regexp: ^(\s\*account\s+required\s+pam\_tally2.so.\*)
line: \1
state: present
register: result\_pam\_\_add
when:
- '"libpam-runtime" in ansible\_facts.packages'
- result\_pam\_module\_\_option\_present.found == 0
tags:
- DISA-STIG-UBTU-20-010072
- PCI-DSS-Req-8.1.6
- PCI-DSSv4-8.3.4
- accounts\_passwords\_pam\_tally2
- configure\_strategy
- low\_complexity
- low\_disruption
- medium\_severity
- no\_reboot\_needed