Group
Guide to the Secure Configuration of SUSE Linux Enterprise 15
Group contains 44 groups and 110 rules |
Group
System Settings
Group contains 35 groups and 90 rules |
[ref]
Contains rules that check correct system settings. |
Group
Installing and Maintaining Software
Group contains 4 groups and 7 rules |
[ref]
The following sections contain information on
security-relevant choices during the initial operating system
installation process and the setup of software
updates. |
Group
System and Software Integrity
Group contains 1 group and 1 rule |
[ref]
System and software integrity can be gained by installing antivirus, increasing
system encryption strength with FIPS, verifying installed software, enabling SELinux,
installing an Intrusion Prevention System, etc. However, installing or enabling integrity
checking tools cannot prevent intrusions, but they can detect that an intrusion
may have occurred. Requirements for integrity checking may be highly dependent on
the environment in which the system will be used. Snapshot-based approaches such
as AIDE may induce considerable overhead in the presence of frequent software updates. |
Group
Operating System Vendor Support and Certification
Group contains 1 rule |
[ref]
The assurance of a vendor to provide operating system support and maintenance
for their product is an important criterion to ensure product stability and
security over the life of the product. A certified product that follows the
necessary standards and government certification requirements guarantees that
known software vulnerabilities will be remediated, and proper guidance for
protecting and securing the operating system will be given. |
Rule
The Installed Operating System Is Vendor Supported
[ref] | The installed operating system must be maintained by a vendor.
SUSE Linux Enterprise is supported by SUSE. As the SUSE Linux Enterprise
vendor, SUSE is responsible for providing security patches. Warning:
There is no remediation besides switching to a different operating system. | Rationale: | An operating system is considered "supported" if the vendor continues to
provide security patches for the product. With an unsupported release, it
will not be possible to resolve any security issue discovered in the system
software. | Severity: | high | Rule ID: | xccdf_org.ssgproject.content_rule_installed_OS_is_vendor_supported | Identifiers and References | Identifiers:
CCE-83260-0 References:
18, 20, 4, APO12.01, APO12.02, APO12.03, APO12.04, BAI03.10, DSS05.01, DSS05.02, CCI-000366, 4.2.3, 4.2.3.12, 4.2.3.7, 4.2.3.9, A.12.6.1, A.14.2.3, A.16.1.3, A.18.2.2, A.18.2.3, CM-6(a), MA-6, SA-13(a), ID.RA-1, PR.IP-12, SRG-OS-000480-GPOS-00227, SLES-15-010000, SV-234800r622137_rule | |
|
Group
Disk Partitioning
Group contains 5 rules |
[ref]
To ensure separation and protection of data, there
are top-level system directories which should be placed on their
own physical partition or logical volume. The installer's default
partitioning scheme creates separate logical volumes for
/ , /boot , and swap .
- If starting with any of the default layouts, check the box to
\"Review and modify partitioning.\" This allows for the easy creation
of additional logical volumes inside the volume group already
created, though it may require making
/ 's logical volume smaller to
create space. In general, using logical volumes is preferable to
using partitions because they can be more easily adjusted
later. - If creating a custom layout, create the partitions mentioned in
the previous paragraph (which the installer will require anyway),
as well as separate ones described in the following sections.
If a system has already been installed, and the default
partitioning
scheme was used, it is possible but nontrivial to
modify it to create separate logical volumes for the directories
listed above. The Logical Volume Manager (LVM) makes this possible.
See the LVM HOWTO at
http://tldp.org/HOWTO/LVM-HOWTO/
for more detailed information on LVM. |
Rule
Ensure /home Located On Separate Partition
[ref] | If user home directories will be stored locally, create a separate partition
for /home at installation time (or migrate it later using LVM). If
/home will be mounted from another system such as an NFS server, then
creating a separate partition is not necessary at installation time, and the
mountpoint can instead be configured later. | Rationale: | Ensuring that /home is mounted on its own partition enables the
setting of more restrictive mount options, and also helps ensure that
users cannot trivially fill partitions used for log or audit data storage. | Severity: | low | Rule ID: | xccdf_org.ssgproject.content_rule_partition_for_home | Identifiers and References | Identifiers:
CCE-85639-3 References:
BP28(R12), 12, 15, 8, APO13.01, DSS05.02, CCI-000366, CCI-001208, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.6, A.13.1.1, A.13.2.1, A.14.1.3, CM-6(a), SC-5(2), PR.PT-4, SRG-OS-000480-GPOS-00227, SLES-15-040200, 1.1.17, SV-235004r622137_rule | |
|
Rule
Ensure /tmp Located On Separate Partition
[ref] | The /tmp directory is a world-writable directory used
for temporary file storage. Ensure it has its own partition or
logical volume at installation time, or migrate it using LVM. | Rationale: | The /tmp partition is used as temporary storage by many programs.
Placing /tmp in its own partition enables the setting of more
restrictive mount options, which can help protect programs which use it. | Severity: | low | Rule ID: | xccdf_org.ssgproject.content_rule_partition_for_tmp | Identifiers and References | Identifiers:
CCE-91179-2 References:
BP28(R12), 12, 15, 8, APO13.01, DSS05.02, CCI-000366, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.6, A.13.1.1, A.13.2.1, A.14.1.3, CM-6(a), SC-5(2), PR.PT-4, SRG-OS-000480-GPOS-00227, 1.1.2 | |
|
Rule
Ensure /var Located On Separate Partition
[ref] | The /var directory is used by daemons and other system
services to store frequently-changing data. Ensure that /var has its own partition
or logical volume at installation time, or migrate it using LVM. | Rationale: | Ensuring that /var is mounted on its own partition enables the
setting of more restrictive mount options. This helps protect
system services such as daemons or other programs which use it.
It is not uncommon for the /var directory to contain
world-writable directories installed by other software packages. | Severity: | low | Rule ID: | xccdf_org.ssgproject.content_rule_partition_for_var | Identifiers and References | Identifiers:
CCE-85640-1 References:
BP28(R12), 12, 15, 8, APO13.01, DSS05.02, CCI-000366, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.6, A.13.1.1, A.13.2.1, A.14.1.3, CM-6(a), SC-5(2), PR.PT-4, SRG-OS-000480-GPOS-00227, SLES-15-040210, 1.1.10, SV-235005r622137_rule | |
|
Rule
Ensure /var/log Located On Separate Partition
[ref] | System logs are stored in the /var/log directory.
Ensure that /var/log has its own partition or logical
volume at installation time, or migrate it using LVM. | Rationale: | Placing /var/log in its own partition
enables better separation between log files
and other files in /var/ . | Severity: | low | Rule ID: | xccdf_org.ssgproject.content_rule_partition_for_var_log | Identifiers and References | Identifiers:
CCE-91181-8 References:
BP28(R12), BP28(R47), 1, 12, 14, 15, 16, 3, 5, 6, 8, APO11.04, APO13.01, BAI03.05, DSS05.02, DSS05.04, DSS05.07, MEA02.01, CCI-000366, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, CIP-007-3 R6.5, CM-6(a), AU-4, SC-5(2), PR.PT-1, PR.PT-4, SRG-OS-000480-GPOS-00227, 1.1.15 | |
|
Rule
Ensure /var/log/audit Located On Separate Partition
[ref] | Audit logs are stored in the /var/log/audit directory.
Ensure that /var/log/audit has its own partition or logical
volume at installation time, or migrate it using LVM.
Make absolutely certain that it is large enough to store all
audit logs that will be created by the auditing daemon. | Rationale: | Placing /var/log/audit in its own partition
enables better separation between audit files
and other files, and helps ensure that
auditing cannot be halted due to the partition running out
of space. | Severity: | low | Rule ID: | xccdf_org.ssgproject.content_rule_partition_for_var_log_audit | Identifiers and References | Identifiers:
CCE-85618-7 References:
BP28(R43), 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 8, APO11.04, APO13.01, BAI03.05, BAI04.04, DSS05.02, DSS05.04, DSS05.07, MEA02.01, CCI-000366, CCI-001849, 164.312(a)(2)(ii), 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.2, SR 7.6, A.12.1.3, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.17.2.1, CIP-007-3 R6.5, CM-6(a), AU-4, SC-5(2), PR.DS-4, PR.PT-1, PR.PT-4, FMT_SMF_EXT.1, SRG-OS-000341-GPOS-00132, SRG-OS-000480-GPOS-00227, SRG-APP-000357-CTR-000800, SLES-15-030810, 1.1.16, SV-234980r622137_rule | |
|
Group
Updating Software
Group contains 1 rule |
[ref]
The zypper command line tool is used to install and
update software packages. The system also provides a graphical
software update tool in the System menu, in the Administration submenu,
called Software Update.
SUSE Linux Enterprise 15 systems contain an installed software catalog called
the RPM database, which records metadata of installed packages. Consistently using
zypper or the graphical Software Update for all software installation
allows for insight into the current inventory of installed software on the system.
|
Rule
Ensure Software Patches Installed
[ref] |
If the system is configured for online updates, invoking the following command will list available
security updates:
$ sudo zypper refresh && sudo zypper list-patches -g security
NOTE: U.S. Defense systems are required to be patched within 30 days or sooner as local policy
dictates. | Rationale: | Installing software updates is a fundamental mitigation against
the exploitation of publicly-known vulnerabilities. If the most
recent security patches and updates are not installed, unauthorized
users may take advantage of weaknesses in the unpatched software. The
lack of prompt attention to patching could result in a system compromise. | Severity: | medium | Rule ID: | xccdf_org.ssgproject.content_rule_security_patches_up_to_date | Identifiers and References | Identifiers:
CCE-83261-8 References:
BP28(R08), 18, 20, 4, 5.10.4.1, APO12.01, APO12.02, APO12.03, APO12.04, BAI03.10, DSS05.01, DSS05.02, CCI-000366, CCI-001227, 4.2.3, 4.2.3.12, 4.2.3.7, 4.2.3.9, A.12.6.1, A.14.2.3, A.16.1.3, A.18.2.2, A.18.2.3, SI-2(5), SI-2(c), CM-6(a), ID.RA-1, PR.IP-12, FMT_MOF_EXT.1, Req-6.2, 6.3.3, SRG-OS-000480-GPOS-00227, SLES-15-010010, 1.9, SV-234802r622137_rule | |
|
Group
Account and Access Control
Group contains 6 groups and 7 rules |
[ref]
In traditional Unix security, if an attacker gains
shell access to a certain login account, they can perform any action
or access any file to which that account has access. Therefore,
making it more difficult for unauthorized people to gain shell
access to accounts, particularly to privileged accounts, is a
necessary part of securing a system. This section introduces
mechanisms for restricting access to accounts under
SUSE Linux Enterprise 15. |
Group
Protect Accounts by Configuring PAM
Group contains 2 groups and 2 rules |
[ref]
PAM, or Pluggable Authentication Modules, is a system
which implements modular authentication for Linux programs. PAM provides
a flexible and configurable architecture for authentication, and it should be configured
to minimize exposure to unnecessary risk. This section contains
guidance on how to accomplish that.
PAM is implemented as a set of shared objects which are
loaded and invoked whenever an application wishes to authenticate a
user. Typically, the application must be running as root in order
to take advantage of PAM, because PAM's modules often need to be able
to access sensitive stores of account information, such as /etc/shadow.
Traditional privileged network listeners
(e.g. sshd) or SUID programs (e.g. sudo) already meet this
requirement. An SUID root application, userhelper, is provided so
that programs which are not SUID or privileged themselves can still
take advantage of PAM.
PAM looks in the directory /etc/pam.d for
application-specific configuration information. For instance, if
the program login attempts to authenticate a user, then PAM's
libraries follow the instructions in the file /etc/pam.d/login
to determine what actions should be taken.
One very important file in /etc/pam.d is
/etc/pam.d/system-auth . This file, which is included by
many other PAM configuration files, defines 'default' system authentication
measures. Modifying this file is a good way to make far-reaching
authentication changes, for instance when implementing a
centralized authentication service. Warning:
Be careful when making changes to PAM's configuration files.
The syntax for these files is complex, and modifications can
have unexpected consequences. The default configurations shipped
with applications should be sufficient for most users. |
Group
Set Password Quality Requirements
Group contains 1 group and 1 rule |
[ref]
The default pam_pwquality PAM module provides strength
checking for passwords. It performs a number of checks, such as
making sure passwords are not similar to dictionary words, are of
at least a certain length, are not the previous password reversed,
and are not simply a change of case from the previous password. It
can also require passwords to be in certain character classes. The
pam_pwquality module is the preferred way of configuring
password requirements.
The man pages pam_pwquality(8)
provide information on the capabilities and configuration of
each. |
Group
Set Password Quality Requirements with pam_pwquality
Group contains 1 rule |
[ref]
The pam_pwquality PAM module can be configured to meet
requirements for a variety of policies.
For example, to configure pam_pwquality to require at least one uppercase
character, lowercase character, digit, and other (special)
character, make sure that pam_pwquality exists in /etc/pam.d/system-auth :
password requisite pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type=
If no such line exists, add one as the first line of the password section in /etc/pam.d/system-auth .
Next, modify the settings in /etc/security/pwquality.conf to match the following:
difok = 4
minlen = 14
dcredit = -1
ucredit = -1
lcredit = -1
ocredit = -1
maxrepeat = 3
The arguments can be modified to ensure compliance with
your organization's security policy. Discussion of each parameter follows. |
Rule
Ensure PAM Enforces Password Requirements - Authentication Retry Prompts Permitted Per-Session
[ref] | To configure the number of retry prompts that are permitted per-session:
Edit the pam_pwquality.so statement in
/etc/pam.d/system-auth to show
retry=3 , or a lower value if site
policy is more restrictive. The DoD requirement is a maximum of 3 prompts
per session. | Rationale: | Setting the password retry prompts that are permitted on a per-session basis to a low value
requires some software, such as SSH, to re-connect. This can slow down and
draw additional attention to some types of password-guessing attacks. Note that this
is different from account lockout, which is provided by the pam_faillock module. | Severity: | medium | Rule ID: | xccdf_org.ssgproject.content_rule_accounts_password_pam_retry | Identifiers and References | References:
1, 11, 12, 15, 16, 3, 5, 9, 5.5.3, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, CCI-000192, CCI-000366, 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3, SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 7.6, A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3, CM-6(a), AC-7(a), IA-5(4), PR.AC-1, PR.AC-6, PR.AC-7, PR.IP-1, FMT_MOF_EXT.1, SRG-OS-000069-GPOS-00037, SRG-OS-000480-GPOS-00227 | |
|
Rule
Ensure PAM Displays Last Logon/Access Notification
[ref] | To configure the system to notify users of last logon/access using pam_lastlog ,
add or correct the pam_lastlog settings in /etc/pam.d/login
to include showfailed option, such as:
session optional pam_lastlog.so showfailed
And make sure that the silent option is not set for this specific line. Warning:
If the system relies on authselect tool to manage PAM settings, the remediation
will also use authselect tool. However, if any manual modification was made in
PAM files, the authselect integrity check will fail and the remediation will be
aborted in order to preserve intentional changes. In this case, an informative message will
be shown in the remediation report. Warning:
authselect contains an authselect feature to easily and properly enable Last Logon
notifications with pam_lastlog.so module. If a custom profile was created and used
in the system before this authselect feature was available, the new feature can't be used
with this custom profile and the remediation will fail. In this case, the custom profile
should be recreated or manually updated. | Rationale: | Users need to be aware of activity that occurs regarding their account. Providing users with
information regarding the number of unsuccessful attempts that were made to login to their
account allows the user to determine if any unauthorized activity has occurred and gives them
an opportunity to notify administrators. | Severity: | low | Rule ID: | xccdf_org.ssgproject.content_rule_display_login_attempts | Identifiers and References | Identifiers:
CCE-85560-1 References:
1, 12, 15, 16, 5.5.2, DSS05.04, DSS05.10, DSS06.10, CCI-000052, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9, 0582, 0584, 05885, 0586, 0846, 0957, A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3, AC-9, AC-9(1), PR.AC-7, Req-10.2.4, 10.2.1.4, SRG-OS-000480-GPOS-00227, SLES-15-020080, SV-234873r858542_rule | |
|
Group
Protect Accounts by Restricting Password-Based Login
Group contains 1 group and 1 rule |
[ref]
Conventionally, Unix shell accounts are accessed by
providing a username and password to a login program, which tests
these values for correctness using the /etc/passwd and
/etc/shadow files. Password-based login is vulnerable to
guessing of weak passwords, and to sniffing and man-in-the-middle
attacks against passwords entered over a network or at an insecure
console. Therefore, mechanisms for accessing accounts by entering
usernames and passwords should be restricted to those which are
operationally necessary. |
Group
Set Password Expiration Parameters
Group contains 1 rule |
[ref]
The file /etc/login.defs controls several
password-related settings. Programs such as passwd ,
su , and
login consult /etc/login.defs to determine
behavior with regard to password aging, expiration warnings,
and length. See the man page login.defs(5) for more information.
Users should be forced to change their passwords, in order to
decrease the utility of compromised passwords. However, the need to
change passwords often should be balanced against the risk that
users will reuse or write down passwords if forced to change them
too often. Forcing password changes every 90-360 days, depending on
the environment, is recommended. Set the appropriate value as
PASS_MAX_DAYS and apply it to existing accounts with the
-M flag.
The PASS_MIN_DAYS ( -m ) setting prevents password
changes for 7 days after the first change, to discourage password
cycling. If you use this setting, train users to contact an administrator
for an emergency password change in case a new password becomes
compromised. The PASS_WARN_AGE ( -W ) setting gives
users 7 days of warnings at login time that their passwords are about to expire.
For example, for each existing human user USER, expiration parameters
could be adjusted to a 180 day maximum password age, 7 day minimum password
age, and 7 day warning period with the following command:
$ sudo chage -M 180 -m 7 -W 7 USER |
Rule
Set Password Minimum Length in login.defs
[ref] | To specify password length requirements for new accounts, edit the file
/etc/login.defs and add or correct the following line:
PASS_MIN_LEN 15
The DoD requirement is 15 .
The FISMA requirement is 12 .
The profile requirement is
15 .
If a program consults /etc/login.defs and also another PAM module
(such as pam_pwquality ) during a password change operation, then
the most restrictive must be satisfied. See PAM section for more
information about enforcing password quality requirements. | Rationale: | Requiring a minimum password length makes password
cracking attacks more difficult by ensuring a larger
search space. However, any security benefit from an onerous requirement
must be carefully weighed against usability problems, support costs, or counterproductive
behavior that may result. | Severity: | medium | Rule ID: | xccdf_org.ssgproject.content_rule_accounts_password_minlen_login_defs | Identifiers and References | Identifiers:
CCE-91168-5 References:
BP28(R18), 1, 12, 15, 16, 5, 5.6.2.1, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, 3.5.7, CCI-000205, 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4, SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, 0421, 0422, 0431, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561, A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3, IA-5(f), IA-5(1)(a), CM-6(a), PR.AC-1, PR.AC-6, PR.AC-7, SRG-OS-000078-GPOS-00046 | |
|
Group
Secure Session Configuration Files for Login Accounts
Group contains 4 rules |
[ref]
When a user logs into a Unix account, the system
configures the user's session by reading a number of files. Many of
these files are located in the user's home directory, and may have
weak permissions as a result of user error or misconfiguration. If
an attacker can modify or even read certain types of account
configuration information, they can often gain full access to the
affected user's account. Therefore, it is important to test and
correct configuration file permissions for interactive accounts,
particularly those of privileged users such as root or system
administrators. |
Rule
Ensure Home Directories are Created for New Users
[ref] | All local interactive user accounts, upon creation, should be assigned a home directory.
Configure the operating system to assign home directories to all new local interactive users by setting the CREATE_HOME
parameter in /etc/login.defs to yes as follows:
CREATE_HOME yes | Rationale: | If local interactive users are not assigned a valid home directory, there is no place
for the storage and control of files they should own. | Severity: | medium | Rule ID: | xccdf_org.ssgproject.content_rule_accounts_have_homedir_login_defs | Identifiers and References | Identifiers:
CCE-85562-7 References:
CCI-000366, SRG-OS-000480-GPOS-00227, SLES-15-020110, SV-234880r622137_rule | |
|
Rule
Ensure the Logon Failure Delay is Set Correctly in login.defs
[ref] | To ensure the logon failure delay controlled by /etc/login.defs is set properly,
add or correct the FAIL_DELAY setting in /etc/login.defs to read as follows:
FAIL_DELAY 4 | Rationale: | Increasing the time between a failed authentication attempt and re-prompting to
enter credentials helps to slow a single-threaded brute force attack. | Severity: | medium | Rule ID: | xccdf_org.ssgproject.content_rule_accounts_logon_fail_delay | Identifiers and References | References:
11, 3, 9, BAI10.01, BAI10.02, BAI10.03, BAI10.05, CCI-000366, 4.3.4.3.2, 4.3.4.3.3, SR 7.6, A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, AC-7(b), CM-6(a), PR.IP-1, SRG-OS-000480-GPOS-00226 | |
|
Rule
Limit the Number of Concurrent Login Sessions Allowed Per User
[ref] | Limiting the number of allowed users and sessions per user can limit risks related to Denial of
Service attacks. This addresses concurrent sessions for a single account and does not address
concurrent sessions by a single user via multiple accounts. To set the number of concurrent
sessions per user add the following line in /etc/security/limits.conf or
a file under /etc/security/limits.d/ :
* hard maxlogins 1 | Rationale: | Limiting simultaneous user logins can insulate the system from denial of service
problems caused by excessive logins. Automated login processes operating improperly or
maliciously may result in an exceptional number of simultaneous login sessions. | Severity: | low | Rule ID: | xccdf_org.ssgproject.content_rule_accounts_max_concurrent_login_sessions | Identifiers and References | Identifiers:
CCE-85555-1 References:
14, 15, 18, 9, 5.5.2.2, DSS01.05, DSS05.02, CCI-000054, 4.3.3.4, SR 3.1, SR 3.8, A.13.1.1, A.13.1.3, A.13.2.1, A.14.1.2, A.14.1.3, CIP-007-3 R5.1, CIP-007-3 R5.1.2, AC-10, CM-6(a), PR.AC-5, SRG-OS-000027-GPOS-00008, SLES-15-020020, SV-234868r877399_rule | |
|
Rule
Set Interactive Session Timeout
[ref] | Setting the TMOUT option in /etc/profile ensures that
all user sessions will terminate based on inactivity.
The value of TMOUT should be exported and read only.
The TMOUT
setting in /etc/profile.d/autologout.sh should read as follows:
TMOUT=600
readonly TMOUT
export TMOUT | Rationale: | Terminating an idle session within a short time period reduces
the window of opportunity for unauthorized personnel to take control of a
management session enabled on the console or console port that has been
left unattended. | Severity: | medium | Rule ID: | xccdf_org.ssgproject.content_rule_accounts_tmout | Identifiers and References | Identifiers:
CCE-83269-1 References:
BP28(R29), 1, 12, 15, 16, DSS05.04, DSS05.10, DSS06.10, 3.1.11, CCI-000057, CCI-001133, CCI-002361, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9, A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3, CIP-004-6 R2.2.3, CIP-007-3 R5.1, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3, AC-12, SC-10, AC-2(5), CM-6(a), PR.AC-7, FMT_MOF_EXT.1, 8.6.1, SRG-OS-000163-GPOS-00072, SRG-OS-000029-GPOS-00010, SLES-15-010130, 5.4.4, SV-234813r622137_rule | |
|
Group
System Accounting with auditd
Group contains 7 groups and 34 rules |
[ref]
The audit service provides substantial capabilities
for recording system activities. By default, the service audits about
SELinux AVC denials and certain types of security-relevant events
such as system logins, account modifications, and authentication
events performed by programs such as sudo.
Under its default configuration, auditd has modest disk space
requirements, and should not noticeably impact system performance.
NOTE: The Linux Audit daemon auditd can be configured to use
the augenrules program to read audit rules files ( *.rules )
located in /etc/audit/rules.d location and compile them to create
the resulting form of the /etc/audit/audit.rules configuration file
during the daemon startup (default configuration). Alternatively, the auditd
daemon can use the auditctl utility to read audit rules from the
/etc/audit/audit.rules configuration file during daemon startup,
and load them into the kernel. The expected behavior is configured via the
appropriate ExecStartPost directive setting in the
/usr/lib/systemd/system/auditd.service configuration file.
To instruct the auditd daemon to use the augenrules program
to read audit rules (default configuration), use the following setting:
ExecStartPost=-/sbin/augenrules --load
in the /usr/lib/systemd/system/auditd.service configuration file.
In order to instruct the auditd daemon to use the auditctl
utility to read audit rules, use the following setting:
ExecStartPost=-/sbin/auditctl -R /etc/audit/audit.rules
in the /usr/lib/systemd/system/auditd.service configuration file.
Refer to [Service] section of the /usr/lib/systemd/system/auditd.service
configuration file for further details.
Government networks often have substantial auditing
requirements and auditd can be configured to meet these
requirements.
Examining some example audit records demonstrates how the Linux audit system
satisfies common requirements.
The following example from Red Hat Enterprise Linux 7 Documentation available at
https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html-single/selinux_users_and_administrators_guide/index#sect-Security-Enhanced_Linux-Fixing_Problems-Raw_Audit_Messages
shows the substantial amount of information captured in a
two typical "raw" audit messages, followed by a breakdown of the most important
fields. In this example the message is SELinux-related and reports an AVC
denial (and the associated system call) that occurred when the Apache HTTP
Server attempted to access the /var/www/html/file1 file (labeled with
the samba_share_t type):
type=AVC msg=audit(1226874073.147:96): avc: denied { getattr } for pid=2465 comm="httpd"
path="/var/www/html/file1" dev=dm-0 ino=284133 scontext=unconfined_u:system_r:httpd_t:s0
tcontext=unconfined_u:object_r:samba_share_t:s0 tclass=file
type=SYSCALL msg=audit(1226874073.147:96): arch=40000003 syscall=196 success=no exit=-13
a0=b98df198 a1=bfec85dc a2=54dff4 a3=2008171 items=0 ppid=2463 pid=2465 auid=502 uid=48
gid=48 euid=48 suid=48 fsuid=48 egid=48 sgid=48 fsgid=48 tty=(none) ses=6 comm="httpd"
exe="/usr/sbin/httpd" subj=unconfined_u:system_r:httpd_t:s0 key=(null)
msg=audit(1226874073.147:96) - The number in parentheses is the unformatted time stamp (Epoch time)
for the event, which can be converted to standard time by using the
date command.
{ getattr } - The item in braces indicates the permission that was denied.
getattr
indicates the source process was trying to read the target file's status information.
This occurs before reading files. This action is denied due to the file being
accessed having the wrong label. Commonly seen permissions include getattr ,
read , and write .
comm="httpd" - The executable that launched the process. The full path of the executable is
found in the
exe= section of the system call (SYSCALL ) message,
which in this case, is exe="/usr/sbin/httpd" .
path="/var/www/html/file1" - The path to the object (target) the process attempted to access.
scontext="unconfined_u:system_r:httpd_t:s0" - The SELinux context of the process that attempted the denied action. In
this case, it is the SELinux context of the Apache HTTP Server, which is running
in the
httpd_t domain.
tcontext="unconfined_u:object_r:samba_share_t:s0" - The SELinux context of the object (target) the process attempted to access.
In this case, it is the SELinux context of
file1 . Note: the samba_share_t
type is not accessible to processes running in the httpd_t domain.
- From the system call (
SYSCALL ) message, two items are of interest:
success=no : indicates whether the denial (AVC) was enforced or not.
success=no indicates the system call was not successful (SELinux denied
access). success=yes indicates the system call was successful - this can
be seen for permissive domains or unconfined domains, such as initrc_t
and kernel_t .
exe="/usr/sbin/httpd" : the full path to the executable that launched
the process, which in this case, is exe="/usr/sbin/httpd" .
|
Group
Configure auditd Rules for Comprehensive Auditing
Group contains 6 groups and 32 rules |
[ref]
The auditd program can perform comprehensive
monitoring of system activity. This section describes recommended
configuration settings for comprehensive auditing, but a full
description of the auditing system's capabilities is beyond the
scope of this guide. The mailing list linux-audit@redhat.com exists
to facilitate community discussion of the auditing system.
The audit subsystem supports extensive collection of events, including:
- Tracing of arbitrary system calls (identified by name or number)
on entry or exit.
- Filtering by PID, UID, call success, system call argument (with
some limitations), etc.
- Monitoring of specific files for modifications to the file's
contents or metadata.
Auditing rules at startup are controlled by the file /etc/audit/audit.rules .
Add rules to it to meet the auditing requirements for your organization.
Each line in /etc/audit/audit.rules represents a series of arguments
that can be passed to auditctl and can be individually tested
during runtime. See documentation in /usr/share/doc/audit-VERSION and
in the related man pages for more details.
If copying any example audit rulesets from /usr/share/doc/audit-VERSION ,
be sure to comment out the
lines containing arch= which are not appropriate for your system's
architecture. Then review and understand the following rules,
ensuring rules are activated as needed for the appropriate
architecture.
After reviewing all the rules, reading the following sections, and
editing as needed, the new rules can be activated as follows:
$ sudo service auditd restart |
Group
Record Events that Modify the System's Discretionary Access Controls
Group contains 13 rules |
[ref]
At a minimum, the audit system should collect file permission
changes for all users and root. Note that the "-F arch=b32" lines should be
present even on a 64 bit system. These commands identify system calls for
auditing. Even if the system is 64 bit it can still execute 32 bit system
calls. Additionally, these rules can be configured in a number of ways while
still achieving the desired effect. An example of this is that the "-S" calls
could be split up and placed on separate lines, however, this is less efficient.
Add the following to /etc/audit/audit.rules :
-a always,exit -F arch=b32 -S chmod,fchmod,fchmodat -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b32 -S chown,fchown,fchownat,lchown -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b32 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>=1000 -F auid!=unset -F key=perm_mod
If your system is 64 bit then these lines should be duplicated and the
arch=b32 replaced with arch=b64 as follows:
-a always,exit -F arch=b64 -S chmod,fchmod,fchmodat -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b64 -S chown,fchown,fchownat,lchown -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b64 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>=1000 -F auid!=unset -F key=perm_mod |
Rule
Record Events that Modify the System's Discretionary Access Controls - chmod
[ref] | At a minimum, the audit system should collect file permission
changes for all users and root. If the auditd daemon is configured to
use the augenrules program to read audit rules during daemon startup
(the default), add the following line to a file with suffix .rules in
the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S chmod -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S chmod -F auid>=1000 -F auid!=unset -F key=perm_mod
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S chmod -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S chmod -F auid>=1000 -F auid!=unset -F key=perm_mod Warning:
Note that these rules can be configured in a
number of ways while still achieving the desired effect. Here the system calls
have been placed independent of other system calls. Grouping these system
calls with others as identifying earlier in this guide is more efficient. | Rationale: | The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. | Severity: | medium | Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_dac_modification_chmod | Identifiers and References | Identifiers:
CCE-85693-0 References:
BP28(R73), 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000126, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-3, AU-3.1, AU-12(c), AU-12.1(iv), AU-12(a), AU-12.1(ii), MA-4(1)(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1.1.c, Req-10.5.5, 10.3.4, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000466-GPOS-00210, SRG-OS-000458-GPOS-00203, SRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SLES-15-030290, 4.1.9, SV-234928r854238_rule | Remediation Ansible snippet ⇲Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CCE-85693-0
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030290
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_chmod
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit chmod tasks
set_fact:
audit_arch: b64
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- not ( ansible_architecture == "aarch64" )
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- CCE-85693-0
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030290
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_chmod
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for chmod for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- chmod
syscall_grouping:
- chmod
- fchmod
- fchmodat
- name: Check existence of chmod in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- chmod
syscall_grouping:
- chmod
- fchmod
- fchmodat
- name: Check existence of chmod in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- not ( ansible_architecture == "aarch64" )
tags:
- CCE-85693-0
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030290
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_chmod
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for chmod for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- chmod
syscall_grouping:
- chmod
- fchmod
- fchmodat
- name: Check existence of chmod in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- chmod
syscall_grouping:
- chmod
- fchmod
- fchmodat
- name: Check existence of chmod in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- not ( ansible_architecture == "aarch64" )
- audit_arch == "b64"
tags:
- CCE-85693-0
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030290
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_chmod
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script ⇲# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit && { ! ( grep -q aarch64 /proc/sys/kernel/osrelease ); }; then
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="chmod"
KEY="perm_mod"
SYSCALL_GROUPING="chmod fchmod fchmodat"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0640 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod o-rwx ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod o-rwx ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
|
Rule
Record Events that Modify the System's Discretionary Access Controls - chown
[ref] | At a minimum, the audit system should collect file permission
changes for all users and root. If the auditd daemon is configured to
use the augenrules program to read audit rules during daemon startup
(the default), add the following line to a file with suffix .rules in
the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S chown -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S chown -F auid>=1000 -F auid!=unset -F key=perm_mod
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S chown -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S chown -F auid>=1000 -F auid!=unset -F key=perm_mod Warning:
Note that these rules can be configured in a
number of ways while still achieving the desired effect. Here the system calls
have been placed independent of other system calls. Grouping these system
calls with others as identifying earlier in this guide is more efficient. | Rationale: | The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. | Severity: | medium | Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_dac_modification_chown | Identifiers and References | Identifiers:
CCE-85690-6 References:
BP28(R73), 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000126, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-3, AU-3.1, AU-12(c), AU-12.1(iv), AU-12(a), AU-12.1(ii), MA-4(1)(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1.1.c, Req-10.5.5, 10.3.4, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000466-GPOS-00210, SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219, SRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SLES-15-030250, 4.1.9, SV-234924r854236_rule | Remediation Ansible snippet ⇲Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CCE-85690-6
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030250
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_chown
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit chown tasks
set_fact:
audit_arch: b64
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- not ( ansible_architecture == "aarch64" )
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- CCE-85690-6
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030250
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_chown
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for chown for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- chown
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of chown in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- chown
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of chown in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- not ( ansible_architecture == "aarch64" )
tags:
- CCE-85690-6
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030250
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_chown
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for chown for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- chown
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of chown in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- chown
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of chown in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- not ( ansible_architecture == "aarch64" )
- audit_arch == "b64"
tags:
- CCE-85690-6
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030250
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_chown
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script ⇲# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit && { ! ( grep -q aarch64 /proc/sys/kernel/osrelease ); }; then
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="chown"
KEY="perm_mod"
SYSCALL_GROUPING="chown fchown fchownat lchown"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0640 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod o-rwx ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod o-rwx ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
|
Rule
Record Events that Modify the System's Discretionary Access Controls - fchmod
[ref] | At a minimum, the audit system should collect file permission
changes for all users and root. If the auditd daemon is configured to
use the augenrules program to read audit rules during daemon startup
(the default), add the following line to a file with suffix .rules in
the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S fchmod -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fchmod -F auid>=1000 -F auid!=unset -F key=perm_mod
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S fchmod -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fchmod -F auid>=1000 -F auid!=unset -F key=perm_mod Warning:
Note that these rules can be configured in a
number of ways while still achieving the desired effect. Here the system calls
have been placed independent of other system calls. Grouping these system
calls with others as identifying earlier in this guide is more efficient. | Rationale: | The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. | Severity: | medium | Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_dac_modification_fchmod | Identifiers and References | Identifiers:
CCE-85694-8 References:
BP28(R73), 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000126, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-3, AU-3.1, AU-12(c), AU-12.1(iv), AU-12(a), AU-12.1(ii), MA-4(1)(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1.1.c, Req-10.5.5, 10.3.4, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000466-GPOS-00210, SRG-OS-000458-GPOS-00203, SRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SLES-15-030290, 4.1.9, SV-234928r854238_rule | Remediation Ansible snippet ⇲Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CCE-85694-8
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030290
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchmod
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit fchmod tasks
set_fact:
audit_arch: b64
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- CCE-85694-8
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030290
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchmod
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for fchmod for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- fchmod
syscall_grouping:
- chmod
- fchmod
- fchmodat
- name: Check existence of fchmod in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fchmod
syscall_grouping:
- chmod
- fchmod
- fchmodat
- name: Check existence of fchmod in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CCE-85694-8
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030290
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchmod
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for fchmod for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- fchmod
syscall_grouping:
- chmod
- fchmod
- fchmodat
- name: Check existence of fchmod in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fchmod
syscall_grouping:
- chmod
- fchmod
- fchmodat
- name: Check existence of fchmod in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- CCE-85694-8
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030290
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchmod
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script ⇲# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="fchmod"
KEY="perm_mod"
SYSCALL_GROUPING="chmod fchmod fchmodat"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0640 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod o-rwx ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod o-rwx ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
|
Rule
Record Events that Modify the System's Discretionary Access Controls - fchmodat
[ref] | At a minimum, the audit system should collect file permission
changes for all users and root. If the auditd daemon is configured to
use the augenrules program to read audit rules during daemon startup
(the default), add the following line to a file with suffix .rules in
the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S fchmodat -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fchmodat -F auid>=1000 -F auid!=unset -F key=perm_mod
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S fchmodat -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fchmodat -F auid>=1000 -F auid!=unset -F key=perm_mod Warning:
Note that these rules can be configured in a
number of ways while still achieving the desired effect. Here the system calls
have been placed independent of other system calls. Grouping these system
calls with others as identifying earlier in this guide is more efficient. | Rationale: | The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. | Severity: | medium | Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_dac_modification_fchmodat | Identifiers and References | Identifiers:
CCE-85695-5 References:
BP28(R73), 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000126, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-3, AU-3.1, AU-12(c), AU-12.1(iv), AU-12(a), AU-12.1(ii), MA-4(1)(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1.1.c, Req-10.5.5, 10.3.4, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000466-GPOS-00210, SRG-OS-000458-GPOS-00203, SRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SLES-15-030290, 4.1.9, SV-234928r854238_rule | Remediation Ansible snippet ⇲Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CCE-85695-5
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030290
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchmodat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit fchmodat tasks
set_fact:
audit_arch: b64
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- CCE-85695-5
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030290
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchmodat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for fchmodat for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- fchmodat
syscall_grouping:
- chmod
- fchmod
- fchmodat
- name: Check existence of fchmodat in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fchmodat
syscall_grouping:
- chmod
- fchmod
- fchmodat
- name: Check existence of fchmodat in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CCE-85695-5
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030290
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchmodat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for fchmodat for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- fchmodat
syscall_grouping:
- chmod
- fchmod
- fchmodat
- name: Check existence of fchmodat in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fchmodat
syscall_grouping:
- chmod
- fchmod
- fchmodat
- name: Check existence of fchmodat in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- CCE-85695-5
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030290
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchmodat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script ⇲# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="fchmodat"
KEY="perm_mod"
SYSCALL_GROUPING="chmod fchmod fchmodat"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0640 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod o-rwx ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod o-rwx ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
|
Rule
Record Events that Modify the System's Discretionary Access Controls - fchown
[ref] | At a minimum, the audit system should collect file permission
changes for all users and root. If the auditd daemon is configured
to use the augenrules program to read audit rules during daemon
startup (the default), add the following line to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S fchown -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fchown -F auid>=1000 -F auid!=unset -F key=perm_mod
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S fchown -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fchown -F auid>=1000 -F auid!=unset -F key=perm_mod Warning:
Note that these rules can be configured in a
number of ways while still achieving the desired effect. Here the system calls
have been placed independent of other system calls. Grouping these system
calls with others as identifying earlier in this guide is more efficient. | Rationale: | The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. | Severity: | medium | Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_dac_modification_fchown | Identifiers and References | Identifiers:
CCE-85721-9 References:
BP28(R73), 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000126, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-3, AU-3.1, AU-12(c), AU-12.1(iv), AU-12(a), AU-12.1(ii), MA-4(1)(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1.1.c, Req-10.5.5, 10.3.4, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000466-GPOS-00210, SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219, SRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SLES-15-030250, 4.1.9, SV-234924r854236_rule | Remediation Ansible snippet ⇲Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CCE-85721-9
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030250
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchown
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit fchown tasks
set_fact:
audit_arch: b64
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- CCE-85721-9
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030250
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchown
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for fchown for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- fchown
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of fchown in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fchown
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of fchown in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CCE-85721-9
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030250
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchown
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for fchown for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- fchown
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of fchown in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fchown
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of fchown in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- CCE-85721-9
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030250
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchown
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script ⇲# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="fchown"
KEY="perm_mod"
SYSCALL_GROUPING="chown fchown fchownat lchown"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0640 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod o-rwx ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod o-rwx ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
|
Rule
Record Events that Modify the System's Discretionary Access Controls - fchownat
[ref] | At a minimum, the audit system should collect file permission
changes for all users and root. If the auditd daemon is configured
to use the augenrules program to read audit rules during daemon
startup (the default), add the following line to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S fchownat -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fchownat -F auid>=1000 -F auid!=unset -F key=perm_mod
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S fchownat -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fchownat -F auid>=1000 -F auid!=unset -F key=perm_mod Warning:
Note that these rules can be configured in a
number of ways while still achieving the desired effect. Here the system calls
have been placed independent of other system calls. Grouping these system
calls with others as identifying earlier in this guide is more efficient. | Rationale: | The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. | Severity: | medium | Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_dac_modification_fchownat | Identifiers and References | Identifiers:
CCE-85692-2 References:
BP28(R73), 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000126, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-3, AU-3.1, AU-12(c), AU-12.1(iv), AU-12(a), AU-12.1(ii), MA-4(1)(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1.1.c, Req-10.5.5, 10.3.4, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000466-GPOS-00210, SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219, SRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SLES-15-030250, 4.1.9, SV-234924r854236_rule | Remediation Ansible snippet ⇲Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CCE-85692-2
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030250
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchownat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit fchownat tasks
set_fact:
audit_arch: b64
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- CCE-85692-2
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030250
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchownat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for fchownat for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- fchownat
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of fchownat in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fchownat
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of fchownat in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CCE-85692-2
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030250
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchownat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for fchownat for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- fchownat
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of fchownat in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fchownat
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of fchownat in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- CCE-85692-2
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030250
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchownat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script ⇲# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="fchownat"
KEY="perm_mod"
SYSCALL_GROUPING="chown fchown fchownat lchown"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0640 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod o-rwx ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod o-rwx ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
|
Rule
Record Events that Modify the System's Discretionary Access Controls - fremovexattr
[ref] | At a minimum, the audit system should collect file permission
changes for all users and root.
If the auditd daemon is configured
to use the augenrules program to read audit rules during daemon
startup (the default), add the following line to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S fremovexattr -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fremovexattr -F auid>=1000 -F auid!=unset -F key=perm_mod
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S fremovexattr -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fremovexattr -F auid>=1000 -F auid!=unset -F key=perm_mod Warning:
Note that these rules can be configured in a
number of ways while still achieving the desired effect. Here the system calls
have been placed independent of other system calls. Grouping these system
calls with others as identifying earlier in this guide is more efficient. | Rationale: | The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. | Severity: | medium | Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_dac_modification_fremovexattr | Identifiers and References | Identifiers:
CCE-85686-4 References:
BP28(R73), 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-12(a), AU-12.1(ii), AU-12(c), AU-12.1(iv), AU-3, AU-3.1, MA-4(1)(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1.1.c, Req-10.5.5, 10.3.4, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000462-GPOS-00206, SRG-OS-000463-GPOS-00207, SRG-OS-000471-GPOS-00215, SRG-OS-000474-GPOS-00219, SRG-OS-000466-GPOS-00210, SRG-OS-000468-GPOS-00212, SRG-OS-000064-GPOS-00033, SRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000496-CTR-001240, SRG-APP-000497-CTR-001245, SRG-APP-000498-CTR-001250, SRG-APP-000499-CTR-001255, SLES-15-030190, 4.1.9, SV-234918r854234_rule | Remediation Ansible snippet ⇲Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CCE-85686-4
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030190
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fremovexattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit fremovexattr tasks
set_fact:
audit_arch: b64
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- CCE-85686-4
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030190
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fremovexattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for fremovexattr for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- fremovexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of fremovexattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fremovexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of fremovexattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CCE-85686-4
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030190
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fremovexattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for fremovexattr for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- fremovexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of fremovexattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fremovexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of fremovexattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- CCE-85686-4
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030190
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fremovexattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script ⇲# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="fremovexattr"
KEY="perm_mod"
SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0640 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod o-rwx ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod o-rwx ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
|
Rule
Record Events that Modify the System's Discretionary Access Controls - fsetxattr
[ref] | At a minimum, the audit system should collect file permission
changes for all users and root. If the auditd daemon is configured
to use the augenrules program to read audit rules during daemon
startup (the default), add the following line to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S fsetxattr -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fsetxattr -F auid>=1000 -F auid!=unset -F key=perm_mod
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S fsetxattr -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fsetxattr -F auid>=1000 -F auid!=unset -F key=perm_mod Warning:
Note that these rules can be configured in a
number of ways while still achieving the desired effect. Here the system calls
have been placed independent of other system calls. Grouping these system
calls with others as identifying earlier in this guide is more efficient. | Rationale: | The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. | Severity: | medium | Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_dac_modification_fsetxattr | Identifiers and References | Identifiers:
CCE-85688-0 References:
BP28(R73), 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000126, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-12(a), AU-12.1(ii), AU-12(c), AU-12.1(iv), AU-3, AU-3.1, MA-4(1)(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1.1.c, Req-10.5.5, 10.3.4, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000462-GPOS-00206, SRG-OS-000463-GPOS-00207, SRG-OS-000466-GPOS-00210, SRG-OS-000468-GPOS-00212, SRG-OS-000471-GPOS-00215, SRG-OS-000474-GPOS-00219, SRG-OS-000064-GPOS-00033, SRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000496-CTR-001240, SRG-APP-000497-CTR-001245, SRG-APP-000498-CTR-001250, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270, SLES-15-030190, 4.1.9, SV-234918r854234_rule | Remediation Ansible snippet ⇲Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CCE-85688-0
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030190
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fsetxattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit fsetxattr tasks
set_fact:
audit_arch: b64
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- CCE-85688-0
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030190
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fsetxattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for fsetxattr for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- fsetxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of fsetxattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fsetxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of fsetxattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CCE-85688-0
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030190
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fsetxattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for fsetxattr for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- fsetxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of fsetxattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fsetxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of fsetxattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- CCE-85688-0
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030190
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fsetxattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script ⇲# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="fsetxattr"
KEY="perm_mod"
SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0640 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod o-rwx ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod o-rwx ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
|
Rule
Record Events that Modify the System's Discretionary Access Controls - lchown
[ref] | At a minimum, the audit system should collect file permission
changes for all users and root. If the auditd daemon is configured
to use the augenrules program to read audit rules during daemon
startup (the default), add the following line to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S lchown -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S lchown -F auid>=1000 -F auid!=unset -F key=perm_mod
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S lchown -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S lchown -F auid>=1000 -F auid!=unset -F key=perm_mod Warning:
Note that these rules can be configured in a
number of ways while still achieving the desired effect. Here the system calls
have been placed independent of other system calls. Grouping these system
calls with others as identifying earlier in this guide is more efficient. | Rationale: | The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. | Severity: | medium | Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_dac_modification_lchown | Identifiers and References | Identifiers:
CCE-85691-4 References:
BP28(R73), 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000126, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-3, AU-3.1, AU-12(c), AU-12.1(iv), AU-12(a), AU-12.1(ii), MA-4(1)(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1.1.c, Req-10.5.5, 10.3.4, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000466-GPOS-00210, SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219, SRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SLES-15-030250, 4.1.9, SV-234924r854236_rule | Remediation Ansible snippet ⇲Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CCE-85691-4
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030250
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_lchown
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit lchown tasks
set_fact:
audit_arch: b64
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- not ( ansible_architecture == "aarch64" )
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- CCE-85691-4
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030250
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_lchown
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for lchown for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- lchown
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of lchown in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- lchown
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of lchown in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- not ( ansible_architecture == "aarch64" )
tags:
- CCE-85691-4
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030250
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_lchown
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for lchown for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- lchown
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of lchown in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- lchown
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of lchown in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- not ( ansible_architecture == "aarch64" )
- audit_arch == "b64"
tags:
- CCE-85691-4
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030250
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_lchown
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script ⇲# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit && { ! ( grep -q aarch64 /proc/sys/kernel/osrelease ); }; then
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="lchown"
KEY="perm_mod"
SYSCALL_GROUPING="chown fchown fchownat lchown"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0640 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod o-rwx ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod o-rwx ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
|
Rule
Record Events that Modify the System's Discretionary Access Controls - lremovexattr
[ref] | At a minimum, the audit system should collect file permission
changes for all users and root.
If the auditd daemon is configured
to use the augenrules program to read audit rules during daemon
startup (the default), add the following line to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S lremovexattr -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S lremovexattr -F auid>=1000 -F auid!=unset -F key=perm_mod
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S lremovexattr -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S lremovexattr -F auid>=1000 -F auid!=unset -F key=perm_mod Warning:
Note that these rules can be configured in a
number of ways while still achieving the desired effect. Here the system calls
have been placed independent of other system calls. Grouping these system
calls with others as identifying earlier in this guide is more efficient. | Rationale: | The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. | Severity: | medium | Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_dac_modification_lremovexattr | Identifiers and References | Identifiers:
CCE-85685-6 References:
BP28(R73), 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-3, AU-3.1, AU-12(c), AU-12.1(iv), AU-12(a), AU-12.1(ii), MA-4(1)(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1.1.c, Req-10.5.5, 10.3.4, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000462-GPOS-00206, SRG-OS-000463-GPOS-00207, SRG-OS-000468-GPOS-00212, SRG-OS-000471-GPOS-00215, SRG-OS-000474-GPOS-00219, SRG-OS-000466-GPOS-00210, SRG-OS-000064-GPOS-00033, SRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000496-CTR-001240, SRG-APP-000497-CTR-001245, SRG-APP-000498-CTR-001250, SRG-APP-000499-CTR-001255, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270, SLES-15-030190, 4.1.9, SV-234918r854234_rule | Remediation Ansible snippet ⇲Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CCE-85685-6
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030190
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_lremovexattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit lremovexattr tasks
set_fact:
audit_arch: b64
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- CCE-85685-6
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030190
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_lremovexattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for lremovexattr for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- lremovexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of lremovexattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- lremovexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of lremovexattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CCE-85685-6
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030190
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_lremovexattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for lremovexattr for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- lremovexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of lremovexattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- lremovexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of lremovexattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- CCE-85685-6
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030190
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_lremovexattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script ⇲# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="lremovexattr"
KEY="perm_mod"
SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0640 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod o-rwx ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod o-rwx ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
|
Rule
Record Events that Modify the System's Discretionary Access Controls - lsetxattr
[ref] | At a minimum, the audit system should collect file permission
changes for all users and root. If the auditd daemon is configured
to use the augenrules program to read audit rules during daemon
startup (the default), add the following line to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S lsetxattr -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S lsetxattr -F auid>=1000 -F auid!=unset -F key=perm_mod
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S lsetxattr -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S lsetxattr -F auid>=1000 -F auid!=unset -F key=perm_mod Warning:
Note that these rules can be configured in a
number of ways while still achieving the desired effect. Here the system calls
have been placed independent of other system calls. Grouping these system
calls with others as identifying earlier in this guide is more efficient. | Rationale: | The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. | Severity: | medium | Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_dac_modification_lsetxattr | Identifiers and References | Identifiers:
CCE-85689-8 References:
BP28(R73), 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000126, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-12(a), AU-12.1(ii), AU-12(c), AU-12.1(iv), AU-3, AU-3.1, MA-4(1)(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1.1.c, Req-10.5.5, 10.3.4, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000462-GPOS-00206, SRG-OS-000463-GPOS-00207, SRG-OS-000466-GPOS-00210, SRG-OS-000468-GPOS-00212, SRG-OS-000471-GPOS-00215, SRG-OS-000474-GPOS-00219, SRG-OS-000064-GPOS-00033, SRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000496-CTR-001240, SRG-APP-000497-CTR-001245, SRG-APP-000498-CTR-001250, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270, SLES-15-030190, 4.1.9, SV-234918r854234_rule | Remediation Ansible snippet ⇲Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CCE-85689-8
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030190
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_lsetxattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit lsetxattr tasks
set_fact:
audit_arch: b64
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- CCE-85689-8
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030190
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_lsetxattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for lsetxattr for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- lsetxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of lsetxattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- lsetxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of lsetxattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CCE-85689-8
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030190
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_lsetxattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for lsetxattr for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- lsetxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of lsetxattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- lsetxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of lsetxattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- CCE-85689-8
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030190
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_lsetxattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script ⇲# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="lsetxattr"
KEY="perm_mod"
SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0640 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod o-rwx ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod o-rwx ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
|
Rule
Record Events that Modify the System's Discretionary Access Controls - removexattr
[ref] | At a minimum, the audit system should collect file permission
changes for all users and root.
If the auditd daemon is configured to use the augenrules
program to read audit rules during daemon startup (the default), add the
following line to a file with suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S removexattr -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S removexattr -F auid>=1000 -F auid!=unset -F key=perm_mod
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S removexattr -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S removexattr -F auid>=1000 -F auid!=unset -F key=perm_mod Warning:
Note that these rules can be configured in a
number of ways while still achieving the desired effect. Here the system calls
have been placed independent of other system calls. Grouping these system
calls with others as identifying earlier in this guide is more efficient. | Rationale: | The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. | Severity: | medium | Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_dac_modification_removexattr | Identifiers and References | Identifiers:
CCE-85684-9 References:
BP28(R73), 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-12(a), AU-12.1(ii), AU-12(c), AU-12.1(iv), AU-3, AU-3.1, MA-4(1)(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1.1.c, Req-10.5.5, 10.3.4, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000462-GPOS-00206, SRG-OS-000463-GPOS-00207, SRG-OS-000468-GPOS-00212, SRG-OS-000471-GPOS-00215, SRG-OS-000474-GPOS-00219, SRG-OS-000466-GPOS-00210, SRG-OS-000064-GPOS-00033, SRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000496-CTR-001240, SRG-APP-000497-CTR-001245, SRG-APP-000498-CTR-001250, SRG-APP-000499-CTR-001255, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270, SLES-15-030190, 4.1.9, SV-234918r854234_rule | Remediation Ansible snippet ⇲Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CCE-85684-9
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030190
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_removexattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit removexattr tasks
set_fact:
audit_arch: b64
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- CCE-85684-9
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030190
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_removexattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for removexattr for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- removexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of removexattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- removexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of removexattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CCE-85684-9
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030190
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_removexattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for removexattr for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- removexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of removexattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- removexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of removexattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- CCE-85684-9
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030190
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_removexattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script ⇲# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="removexattr"
KEY="perm_mod"
SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0640 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod o-rwx ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod o-rwx ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
|
Rule
Record Events that Modify the System's Discretionary Access Controls - setxattr
[ref] | At a minimum, the audit system should collect file permission
changes for all users and root. If the auditd daemon is configured
to use the augenrules program to read audit rules during daemon
startup (the default), add the following line to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S setxattr -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S setxattr -F auid>=1000 -F auid!=unset -F key=perm_mod
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S setxattr -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S setxattr -F auid>=1000 -F auid!=unset -F key=perm_mod Warning:
Note that these rules can be configured in a
number of ways while still achieving the desired effect. Here the system calls
have been placed independent of other system calls. Grouping these system
calls with others as identifying earlier in this guide is more efficient. | Rationale: | The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. | Severity: | medium | Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_dac_modification_setxattr | Identifiers and References | Identifiers:
CCE-85687-2 References:
BP28(R73), 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000126, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-3, AU-3.1, AU-12(c), AU-12.1(iv), AU-12(a), AU-12.1(ii), MA-4(1)(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1.1.c, Req-10.5.5, 10.3.4, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000466-GPOS-00210, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000458-GPOS-00203, SRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SLES-15-030190, 4.1.9, SV-234918r854234_rule | Remediation Ansible snippet ⇲Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CCE-85687-2
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030190
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_setxattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit setxattr tasks
set_fact:
audit_arch: b64
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- CCE-85687-2
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030190
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_setxattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for setxattr for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- setxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of setxattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- setxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of setxattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CCE-85687-2
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030190
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_setxattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for setxattr for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- setxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of setxattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- setxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of setxattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- CCE-85687-2
- CJIS-5.4.1.1
- DISA-STIG-SLES-15-030190
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_setxattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script ⇲# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit; then
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="setxattr"
KEY="perm_mod"
SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0640 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod o-rwx ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod o-rwx ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
|
Group
Record File Deletion Events by User
Group contains 4 rules |
[ref]
At a minimum, the audit system should collect file deletion events
for all users and root. If the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following line to a file with suffix .rules in the
directory /etc/audit/rules.d , setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S rmdir,unlink,unlinkat,rename,renameat -F auid>=1000 -F auid!=unset -F key=delete
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file, setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S rmdir,unlink,unlinkat,rename,renameat -F auid>=1000 -F auid!=unset -F key=delete |
Rule
Ensure auditd Collects File Deletion Events by User
[ref] | At a minimum the audit system should collect file deletion events
for all users and root. If the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following line to a file with suffix .rules in the
directory /etc/audit/rules.d , setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S rmdir,unlink,unlinkat,rename,renameat -F auid>=1000 -F auid!=unset -F key=delete
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file, setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S rmdir,unlink,unlinkat,rename -S renameat -F auid>=1000 -F auid!=unset -F key=delete Warning:
This rule checks for multiple syscalls related to file deletion;
it was written with DISA STIG in mind. Other policies should use a
separate rule for each syscall that needs to be checked. For example:
audit_rules_file_deletion_events_rmdir audit_rules_file_deletion_events_unlink audit_rules_file_deletion_events_unlinkat
| Rationale: | Auditing file deletions will create an audit trail for files that are removed
from the system. The audit trail could aid in system troubleshooting, as well as, detecting
malicious processes that attempt to delete log files to conceal their presence. | Severity: | medium | Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_file_deletion_events | Identifiers and References | Identifiers:
CCE-85767-2 References:
1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000366, CCI-000172, CCI-002884, 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1.1.c, Req-10.2.7, 10.2.1.7 | |
|
Rule
Ensure auditd Collects File Deletion Events by User - rmdir
[ref] | At a minimum, the audit system should collect file deletion events
for all users and root. If the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following line to a file with suffix .rules in the
directory /etc/audit/rules.d , setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S rmdir -F auid>=1000 -F auid!=unset -F key=delete
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file, setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S rmdir -F auid>=1000 -F auid!=unset -F key=delete | Rationale: | Auditing file deletions will create an audit trail for files that are removed
from the system. The audit trail could aid in system troubleshooting, as well as, detecting
malicious processes that attempt to delete log files to conceal their presence. | Severity: | medium | Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_file_deletion_events_rmdir | Identifiers and References | Identifiers:
CCE-85770-6 References:
BP28(R73), 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-000366, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.4, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.1.1, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.MA-2, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1.1.c, Req-10.2.7, 10.2.1.7, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000466-GPOS-00210, SRG-OS-000467-GPOS-00211, SRG-OS-000468-GPOS-00212, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270 | Remediation Ansible snippet ⇲Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CCE-85770-6
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- PCI-DSSv4-10.2.1.7
- audit_rules_file_deletion_events_rmdir
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit rmdir tasks
set_fact:
audit_arch: b64
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- not ( ansible_architecture == "aarch64" )
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- CCE-85770-6
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- PCI-DSSv4-10.2.1.7
- audit_rules_file_deletion_events_rmdir
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for rmdir for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- rmdir
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of rmdir in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/delete.rules
set_fact: audit_file="/etc/audit/rules.d/delete.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=delete
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- rmdir
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of rmdir in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=delete
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- not ( ansible_architecture == "aarch64" )
tags:
- CCE-85770-6
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- PCI-DSSv4-10.2.1.7
- audit_rules_file_deletion_events_rmdir
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for rmdir for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- rmdir
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of rmdir in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/delete.rules
set_fact: audit_file="/etc/audit/rules.d/delete.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=delete
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- rmdir
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of rmdir in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=delete
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"audit" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- not ( ansible_architecture == "aarch64" )
- audit_arch == "b64"
tags:
- CCE-85770-6
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- PCI-DSSv4-10.2.1.7
- audit_rules_file_deletion_events_rmdir
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script ⇲# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit && { ! ( grep -q aarch64 /proc/sys/kernel/osrelease ); }; then
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="rmdir"
KEY="delete"
SYSCALL_GROUPING="unlink unlinkat rename renameat rmdir"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0640 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod o-rwx ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod o-rwx ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
|
Rule
Ensure auditd Collects File Deletion Events by User - unlink
[ref] | At a minimum, the audit system should collect file deletion events
for all users and root. If the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following line to a file with suffix .rules in the
directory /etc/audit/rules.d , setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S unlink -F auid>=1000 -F auid!=unset -F key=delete
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file, setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S unlink -F auid>=1000 -F auid!=unset -F key=delete | Rationale: | Auditing file deletions will create an audit trail for files that are removed
from the system. The audit trail could aid in system troubleshooting, as well as, detecting
malicious processes that attempt to delete log files to conceal their presence. | Severity: | medium | Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_file_deletion_events_unlink | Identifiers and References | Identifiers:
CCE-85771-4 References:
BP28(R73), 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-000366, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.4, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.1.1, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.MA-2, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1.1.c, Req-10.2.7, 10.2.1.7, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000466-GPOS-00210, SRG-OS-000467-GPOS-00211, SRG-OS-000468-GPOS-00212, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270, 4.1.13 | |
|