Skip to content

Commit f9578f3

Browse files
authored
Merge pull request #14188 from jan-cerny/unlock_time_zero
Add new rule accounts_passwords_pam_faillock_unlock_time_with_zero
2 parents d3d2766 + 62147a3 commit f9578f3

File tree

14 files changed

+360
-6
lines changed

14 files changed

+360
-6
lines changed

components/pam.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ rules:
6868
- accounts_passwords_pam_faillock_silent
6969
- accounts_passwords_pam_faillock_root_unlock_time
7070
- accounts_passwords_pam_faillock_unlock_time
71+
- accounts_passwords_pam_faillock_unlock_time_with_zero
7172
- accounts_passwords_pam_faillock_enabled
7273
- accounts_passwords_pam_tally2
7374
- accounts_passwords_pam_tally2_deny_root

controls/cis_rhel10.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1956,7 +1956,7 @@ controls:
19561956
by an administrator. However, it also mentions that using value 0 can facilitate a DoS
19571957
attack to legitimate users.
19581958
rules:
1959-
- accounts_passwords_pam_faillock_unlock_time
1959+
- accounts_passwords_pam_faillock_unlock_time_with_zero
19601960
- var_accounts_passwords_pam_faillock_unlock_time=900
19611961

19621962
- id: 5.3.2.1.3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# platform = multi_platform_all
2+
# reboot = false
3+
# strategy = restrict
4+
# complexity = low
5+
# disruption = low
6+
{{{ ansible_pam_faillock_enable(rule_title=rule_title) }}}
7+
{{{ ansible_pam_faillock_parameter_value('unlock_time', 'var_accounts_passwords_pam_faillock_unlock_time', rule_title=rule_title) }}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# platform = multi_platform_all
2+
3+
{{{ bash_instantiate_variables('var_accounts_passwords_pam_faillock_unlock_time') }}}
4+
5+
{{{ bash_pam_faillock_enable() }}}
6+
{{{ bash_pam_faillock_parameter_value('unlock_time', '$var_accounts_passwords_pam_faillock_unlock_time') }}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
<def-group>
2+
<definition class="compliance" id="{{{ rule_id }}}" version="6">
3+
{{{ oval_metadata(DESCRIPTION, rule_title=rule_title) }}}
4+
5+
<criteria operator="AND" comment="Check the proper configuration of pam_faillock.so">
6+
<criteria operator="AND" comment="Check if pam_faillock.so is properly enabled">
7+
<!-- pam_unix.so is a control module present in all realistic scenarios and also used
8+
as reference for the correct position of pam_faillock.so in auth section. If the
9+
system is properly configured, it must appear only once in auth section. -->
10+
<criteria operator="AND"
11+
comment="Count occurrences of pam_unix.so in system-auth and password-auth">
12+
<criterion test_ref="test_{{{ rule_id }}}_system_pam_unix_auth"
13+
comment="pam_unix.so appears only once in auth section of system-auth"/>
14+
<criterion test_ref="test_{{{ rule_id }}}_password_pam_unix_auth"
15+
comment="pam_unix.so appears only once in auth section of password-auth"/>
16+
</criteria>
17+
18+
<!-- pam_faillock.so parameters can be defined directly in pam files or, in newer
19+
versions, in {{{ pam_faillock_conf_path }}}. The last is the recommended option when
20+
available. Also, is the option used by authselect tool. However, regardless the
21+
approach, a minimal declaration is common in pam files. -->
22+
<criteria operator="AND" comment="Check common definition of pam_faillock.so">
23+
<criterion
24+
test_ref="test_{{{ rule_id }}}_system_pam_faillock_auth"
25+
comment="pam_faillock.so is properly defined in auth section of system-auth"/>
26+
<criterion
27+
test_ref="test_{{{ rule_id }}}_system_pam_faillock_account"
28+
comment="pam_faillock.so is properly defined in account section of system-auth"/>
29+
<criterion
30+
test_ref="test_{{{ rule_id }}}_password_pam_faillock_auth"
31+
comment="pam_faillock.so is properly defined in auth section of password-auth"/>
32+
<criterion
33+
test_ref="test_{{{ rule_id }}}_password_pam_faillock_account"
34+
comment="pam_faillock.so is properly defined in account section of password-auth"/>
35+
</criteria>
36+
</criteria>
37+
38+
<!-- pam_faillock.so parameters should be defined in {{{ pam_faillock_conf_path }}} whenever
39+
possible. But due to backwards compatibility, they are also allowed in pam files
40+
directly. In case they are defined in both places, pam files have precedence and this
41+
may confuse the assessment. The following tests ensure only one option is used. Note
42+
that if {{{ pam_faillock_conf_path }}} is available, authselect tool only manage parameters on it -->
43+
<criteria operator="OR" comment="Check expected value for pam_faillock.so unlock_time parameter">
44+
<criteria operator="AND"
45+
comment="Check expected pam_faillock.so unlock_time parameter in pam files">
46+
<criterion
47+
test_ref="test_{{{ rule_id }}}_parameter_pamd_system"
48+
comment="Check the unlock_time parameter in auth section of system-auth file"/>
49+
<criterion
50+
test_ref="test_{{{ rule_id }}}_parameter_pamd_password"
51+
comment="Check the unlock_time parameter in auth section of password-auth file"/>
52+
<criterion
53+
test_ref="test_{{{ rule_id }}}_parameter_no_faillock_conf"
54+
comment="Ensure the unlock_time parameter is not present in {{{ pam_faillock_conf_path }}}"/>
55+
</criteria>
56+
<criteria operator="AND"
57+
comment="Check expected pam_faillock.so unlock_time parameter in {{{ pam_faillock_conf_path }}}">
58+
<criterion
59+
test_ref="test_{{{ rule_id }}}_parameter_no_pamd_system"
60+
comment="Check the unlock_time parameter is not present system-auth file"/>
61+
<criterion
62+
test_ref="test_{{{ rule_id }}}_parameter_no_pamd_password"
63+
comment="Check the unlock_time parameter is not present password-auth file"/>
64+
<criterion
65+
test_ref="test_{{{ rule_id }}}_parameter_faillock_conf"
66+
comment="Ensure the unlock_time parameter is present in {{{ pam_faillock_conf_path }}}"/>
67+
</criteria>
68+
</criteria>
69+
</criteria>
70+
71+
</definition>
72+
73+
<!-- The following tests demand complex regex which are necessary more than once.
74+
These variables make simpler the usage of regex patterns. -->
75+
<constant_variable id="var_{{{ rule_id }}}_pam_unix_regex"
76+
datatype="string" version="2"
77+
comment="regex to identify pam_unix.so in auth section of pam files">
78+
<value>^\s*auth\N+pam_unix\.so</value>
79+
</constant_variable>
80+
81+
<constant_variable
82+
id="var_{{{ rule_id }}}_pam_faillock_auth_regex"
83+
datatype="string" version="2"
84+
comment="regex to identify pam_faillock.so entries in auth section of pam files">
85+
<value>^[\s]*auth[\s]+(required|\[(?=.*?\bsuccess=ok\b)(?=.*?\bnew_authtok_reqd=ok\b)(?=.*?\bignore=ignore\b)(?=.*?\bdefault=bad\b).*\])[\s]+pam_faillock\.so[\s\w\d=]+preauth[\s\S]*^[\s]*auth[\s]+(sufficient|\[(?=.*\bsuccess=done\b)(?=.*?\bnew_authtok_reqd=done\b)(?=.*?\bdefault=ignore\b).*\])[\s]+pam_unix\.so[\s\S]*^[\s]*auth[\s]+(required|\[(?=.*?\bsuccess=ok\b)(?=.*?\bnew_authtok_reqd=ok\b)(?=.*?\bignore=ignore\b)(?=.*?\bdefault=bad\b).*\])[\s]+pam_faillock\.so[\s\w\d=]+authfail</value>
86+
</constant_variable>
87+
88+
<constant_variable
89+
id="var_{{{ rule_id }}}_pam_faillock_account_regex"
90+
datatype="string" version="2"
91+
comment="regex to identify pam_faillock.so entry in account section of pam files">
92+
<value>^[\s]*account[\s]+(required|\[(?=.*?\bsuccess=ok\b)(?=.*?\bnew_authtok_reqd=ok\b)(?=.*?\bignore=ignore\b)(?=.*?\bdefault=bad\b).*\])[\s]+pam_faillock\.so[\s\S]*^[\s]*account[\s]+(required|\[(?=.*?\bsuccess=ok\b)(?=.*?\bnew_authtok_reqd=ok\b)(?=.*?\bignore=ignore\b)(?=.*?\bdefault=bad\b).*\])[\s]+pam_unix\.so</value>
93+
</constant_variable>
94+
95+
<constant_variable
96+
id="var_{{{ rule_id }}}_pam_faillock_unlock_time_parameter_regex"
97+
datatype="string" version="1"
98+
comment="regex to identify pam_faillock.so unlock_time entry in auth section of pam files">
99+
<value>^[\s]*auth[\s]+.+[\s]+pam_faillock.so[\s]+[^\n]*unlock_time=([0-9]+)</value>
100+
</constant_variable>
101+
102+
<constant_variable
103+
id="var_{{{ rule_id }}}_faillock_conf_unlock_time_parameter_regex"
104+
datatype="string" version="1"
105+
comment="regex to identify unlock_time entry in {{{ pam_faillock_conf_path }}}">
106+
<value>^[\s]*unlock_time[\s]*=[\s]*([0-9]+)</value>
107+
</constant_variable>
108+
109+
{{% macro generate_test_faillock_enabled(file_stem) %}}
110+
<!-- Check occurences of pam_unix.so in auth section of {{{ file_stem }}}-auth file -->
111+
<ind:textfilecontent54_test
112+
check="all" check_existence="none_exist" version="2"
113+
id="test_{{{ rule_id }}}_{{{ file_stem }}}_pam_unix_auth"
114+
comment="no more that one pam_unix.so is expected in auth section of {{{ file_stem }}}-auth">
115+
<ind:object object_ref="object_{{{ rule_id }}}_{{{ file_stem }}}_pam_unix_auth"/>
116+
</ind:textfilecontent54_test>
117+
118+
<ind:textfilecontent54_object
119+
version="2"
120+
id="object_{{{ rule_id }}}_{{{ file_stem }}}_pam_unix_auth"
121+
comment="Get the second and subsequent occurrences of pam_unix.so in auth section of {{{ file_stem}}}-auth">
122+
<ind:filepath>/etc/pam.d/{{{file_stem}}}-auth</ind:filepath>
123+
<ind:pattern operation="pattern match" var_ref="var_{{{ rule_id }}}_pam_unix_regex"/>
124+
<ind:instance datatype="int" operation="greater than">1</ind:instance>
125+
</ind:textfilecontent54_object>
126+
127+
<!-- Check common definition of pam_faillock.so in {{{ file_stem }}}-auth file -->
128+
<ind:textfilecontent54_test
129+
check="all" check_existence="only_one_exists" version="2"
130+
id="test_{{{ rule_id }}}_{{{ file_stem }}}_pam_faillock_auth"
131+
comment="One and only one occurrence is expected in auth section of {{{ file_stem }}}-auth">
132+
<ind:object
133+
object_ref="object_{{{ rule_id }}}_{{{ file_stem }}}_pam_faillock_auth"/>
134+
</ind:textfilecontent54_test>
135+
136+
<ind:textfilecontent54_object
137+
version="2"
138+
id="object_{{{ rule_id }}}_{{{ file_stem }}}_pam_faillock_auth"
139+
comment="Check common definition of pam_faillock.so in auth section of common-auth">
140+
<ind:filepath>/etc/pam.d/{{{ file_stem }}}-auth</ind:filepath>
141+
<ind:pattern operation="pattern match"
142+
var_ref="var_{{{ rule_id }}}_pam_faillock_auth_regex"/>
143+
<ind:instance datatype="int" operation="equals">1</ind:instance>
144+
</ind:textfilecontent54_object>
145+
{{% endmacro %}}
146+
147+
{{{ generate_test_faillock_enabled (file_stem="system") }}}
148+
{{{ generate_test_faillock_enabled (file_stem="password") }}}
149+
{{{ generate_test_faillock_enabled (file_stem="common") }}}
150+
151+
{{% macro generate_test_faillock_account(file_stem, file) %}}
152+
<!-- Check common definition of pam_faillock.so in {{{ file_stem }}}-account -->
153+
<ind:textfilecontent54_test
154+
check="all" check_existence="only_one_exists" version="2"
155+
id="test_{{{ rule_id }}}_{{{ file_stem }}}_pam_faillock_account"
156+
comment="One and only one occurrence is expected in {{{ file }}}">
157+
<ind:object
158+
object_ref="object_{{{ rule_id }}}_{{{ file_stem }}}_pam_faillock_account"/>
159+
</ind:textfilecontent54_test>
160+
161+
<ind:textfilecontent54_object
162+
version="2"
163+
id="object_{{{ rule_id }}}_{{{ file_stem }}}_pam_faillock_account"
164+
comment="Check common definition of pam_faillock.so in account section of {{{ file }}}">
165+
<ind:filepath>/etc/pam.d/{{{ file }}}</ind:filepath>
166+
<ind:pattern operation="pattern match"
167+
var_ref="var_{{{ rule_id }}}_pam_faillock_account_regex"/>
168+
<ind:instance datatype="int" operation="equals">1</ind:instance>
169+
</ind:textfilecontent54_object>
170+
{{% endmacro %}}
171+
172+
{{{ generate_test_faillock_account (file_stem="system", file="system-auth") }}}
173+
{{{ generate_test_faillock_account (file_stem="password", file="password-auth") }}}
174+
{{{ generate_test_faillock_account (file_stem="common", file="common-account") }}}
175+
176+
{{% macro generate_check_parameter_in_pam_file(file_stem) %}}
177+
<!-- Check absence of unlock_time parameter in {{{ file_stem }}}-auth -->
178+
<ind:textfilecontent54_test
179+
check="all" check_existence="none_exist" version="2"
180+
id="test_{{{ rule_id }}}_parameter_no_pamd_{{{ file_stem }}}"
181+
comment="Check the absence of unlock_time parameter in {{{ file_stem }}}-auth">
182+
<ind:object object_ref="object_{{{ rule_id }}}_parameter_pamd_{{{ file_stem }}}"/>
183+
</ind:textfilecontent54_test>
184+
185+
<!-- Check expected value of unlock_time parameter in {{{ file_stem }}}-auth -->
186+
<ind:textfilecontent54_test
187+
check="all" check_existence="all_exist" version="2"
188+
id="test_{{{ rule_id }}}_parameter_pamd_{{{ file_stem }}}"
189+
comment="Check the expected unlock_time value in {{{ file_stem }}}-auth" state_operator="OR">
190+
<ind:object object_ref="object_{{{ rule_id }}}_parameter_pamd_{{{ file_stem }}}"/>
191+
<ind:state state_ref="state_{{{ rule_id }}}_parameter_lower_bound"/>
192+
<ind:state state_ref="state_{{{ rule_id }}}_parameter_special_allowed_value"/>
193+
</ind:textfilecontent54_test>
194+
195+
<ind:textfilecontent54_object
196+
version="2"
197+
id="object_{{{ rule_id }}}_parameter_pamd_{{{ file_stem }}}"
198+
comment="Get the pam_faillock.so unlock_time parameter from {{{ file_stem }}}-auth file">
199+
<ind:filepath>/etc/pam.d/{{{ file_stem }}}-auth</ind:filepath>
200+
<ind:pattern operation="pattern match"
201+
var_ref="var_{{{ rule_id }}}_pam_faillock_unlock_time_parameter_regex"/>
202+
<ind:instance datatype="int" operation="equals">1</ind:instance>
203+
</ind:textfilecontent54_object>
204+
{{% endmacro %}}
205+
206+
<!-- boundaries to test the parameter value -->
207+
<!-- Specify the required external variable & create corresponding state from it -->
208+
<external_variable id="var_accounts_passwords_pam_faillock_unlock_time" datatype="int"
209+
comment="external variable to use" version="1"/>
210+
211+
<ind:textfilecontent54_state
212+
version="1"
213+
id="state_{{{ rule_id }}}_parameter_lower_bound">
214+
<ind:subexpression datatype="int" operation="greater than or equal"
215+
var_ref="var_accounts_passwords_pam_faillock_unlock_time"/>
216+
</ind:textfilecontent54_state>
217+
218+
<ind:textfilecontent54_state
219+
version="1"
220+
id="state_{{{ rule_id }}}_parameter_special_allowed_value">
221+
<ind:subexpression datatype="int" operation="equals">0</ind:subexpression>
222+
</ind:textfilecontent54_state>
223+
224+
{{{ generate_check_parameter_in_pam_file (file_stem="system") }}}
225+
{{{ generate_check_parameter_in_pam_file (file_stem="password") }}}
226+
{{{ generate_check_parameter_in_pam_file (file_stem="common") }}}
227+
228+
<!-- Check expected value of unlock_time parameter in {{{ pam_faillock_conf_path }}} -->
229+
<ind:textfilecontent54_test check="all" check_existence="all_exist" version="1"
230+
id="test_{{{ rule_id }}}_parameter_faillock_conf"
231+
comment="Check the expected unlock_time value in {{{ pam_faillock_conf_path }}}" state_operator="OR">
232+
<ind:object object_ref="object_{{{ rule_id }}}_parameter_faillock_conf"/>
233+
<ind:state state_ref="state_{{{ rule_id }}}_parameter_lower_bound"/>
234+
<ind:state state_ref="state_{{{ rule_id }}}_parameter_special_allowed_value"/>
235+
</ind:textfilecontent54_test>
236+
237+
<!-- Check absence of unlock_time parameter in {{{ pam_faillock_conf_path }}} -->
238+
<ind:textfilecontent54_test
239+
check="all" check_existence="none_exist" version="1"
240+
id="test_{{{ rule_id }}}_parameter_no_faillock_conf"
241+
comment="Check the absence of unlock_time parameter in {{{ pam_faillock_conf_path }}}">
242+
<ind:object object_ref="object_{{{ rule_id }}}_parameter_faillock_conf"/>
243+
</ind:textfilecontent54_test>
244+
245+
<ind:textfilecontent54_object
246+
version="1"
247+
id="object_{{{ rule_id }}}_parameter_faillock_conf"
248+
comment="Check the expected pam_faillock.so unlock_time parameter in {{{ pam_faillock_conf_path }}}">
249+
<ind:filepath>{{{ pam_faillock_conf_path }}}</ind:filepath>
250+
<ind:pattern
251+
operation="pattern match"
252+
var_ref="var_{{{ rule_id }}}_faillock_conf_unlock_time_parameter_regex"/>
253+
<ind:instance datatype="int" operation="equals">1</ind:instance>
254+
</ind:textfilecontent54_object>
255+
256+
</def-group>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
documentation_complete: true
2+
3+
title: 'Set Lockout Time for Failed Password Attempts'
4+
5+
description: |-
6+
This rule configures the system to lock out accounts during a specified time period after a
7+
number of incorrect login attempts using <tt>pam_faillock.so</tt>.
8+
9+
Ensure that the file <tt>/etc/security/faillock.conf</tt> contains the following entry:
10+
<tt>unlock_time=&lt;interval-in-seconds&gt;</tt> where
11+
<tt>interval-in-seconds</tt> is set to <tt>0</tt> or is set to
12+
<tt>{{{xccdf_value("var_accounts_passwords_pam_faillock_unlock_time") }}}</tt> or greater.
13+
14+
pam_faillock.so module requires multiple entries in pam files. These entries must be carefully
15+
defined to work as expected. In order to avoid any errors when manually editing these files,
16+
it is recommended to use the appropriate tools, such as <tt>authselect</tt> or <tt>authconfig</tt>,
17+
depending on the OS version.
18+
19+
If <tt>unlock_time</tt> is set to <tt>0</tt>, manual intervention by an administrator is required
20+
to unlock a user. This should be done using the <tt>faillock</tt> tool.
21+
22+
rationale: |-
23+
By limiting the number of failed logon attempts the risk of unauthorized system
24+
access via user password guessing, otherwise known as brute-forcing, is reduced.
25+
Limits are imposed by locking the account.
26+
27+
severity: medium
28+
29+
identifiers:
30+
cce@rhel10: CCE-87367-9
31+
32+
platform: package[pam]
33+
34+
ocil_clause: |-
35+
the "unlock_time" option is not set to "0" or is not set to "{{{ xccdf_value("var_accounts_passwords_pam_faillock_unlock_time") }}}",
36+
the line is missing, or commented out
37+
38+
ocil: |-
39+
Verify {{{ full_name }}} is configured to lock an account until released by an administrator
40+
after {{{ xccdf_value("var_accounts_passwords_pam_faillock_deny") }}} unsuccessful logon
41+
attempts with the command:
42+
43+
<pre>$ grep 'unlock_time =' /etc/security/faillock.conf</pre>
44+
<tt>unlock_time = {{{ xccdf_value("var_accounts_passwords_pam_faillock_unlock_time") }}}</tt>
45+
46+
fixtext: |-
47+
Configure {{{ full_name }}} to lock an account until released by an administrator after
48+
{{{ xccdf_value("var_accounts_passwords_pam_faillock_deny") }}} unsuccessful logon attempts with the command:
49+
50+
$ sudo authselect enable-feature with-faillock
51+
52+
Then edit the <tt>/etc/security/faillock.conf</tt> file as follows:
53+
<pre>unlock_time = {{{ xccdf_value("var_accounts_passwords_pam_faillock_unlock_time") }}}</pre>
54+
55+
warnings:
56+
- general: |-
57+
If the system supports the new <tt>/etc/security/faillock.conf</tt> file but the
58+
pam_faillock.so parameters are defined directly in <tt>/etc/pam.d/system-auth</tt> and
59+
<tt>/etc/pam.d/password-auth</tt>, the remediation will migrate the <tt>unlock_time</tt> parameter
60+
to <tt>/etc/security/faillock.conf</tt> to ensure compatibility with <tt>authselect</tt> tool.
61+
The parameters <tt>deny</tt> and <tt>fail_interval</tt>, if used, also have to be migrated
62+
by their respective remediation.
63+
64+
- general: |-
65+
If the system relies on <tt>authselect</tt> tool to manage PAM settings, the remediation
66+
will also use <tt>authselect</tt> tool. However, if any manual modification was made in
67+
PAM files, the <tt>authselect</tt> integrity check will fail and the remediation will be
68+
aborted in order to preserve intentional changes. In this case, an informative message will
69+
be shown in the remediation report.
70+
If the system supports the <tt>/etc/security/faillock.conf</tt> file, the pam_faillock
71+
parameters should be defined in <tt>faillock.conf</tt> file.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/bash
2+
# variables = var_accounts_passwords_pam_faillock_unlock_time=900
3+
echo "unlock_time = 900" > "{{{ pam_faillock_conf_path }}}"
4+
authselect enable-feature with-faillock
5+
authselect apply-changes
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/bash
2+
# variables = var_accounts_passwords_pam_faillock_unlock_time=900
3+
echo "unlock_time = 1000" > "{{{ pam_faillock_conf_path }}}"
4+
authselect enable-feature with-faillock
5+
authselect apply-changes
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/bash
2+
echo "unlock_time = 0" > "{{{ pam_faillock_conf_path }}}"
3+
authselect enable-feature with-faillock
4+
authselect apply-changes

shared/references/cce-redhat-avail.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,6 @@ CCE-87362-0
385385
CCE-87364-6
386386
CCE-87365-3
387387
CCE-87366-1
388-
CCE-87367-9
389388
CCE-87368-7
390389
CCE-87371-1
391390
CCE-87372-9

0 commit comments

Comments
 (0)