contains 191 rules |
System Settings
[ref]groupContains rules that check correct system settings. |
contains 170 rules |
Installing and Maintaining Software
[ref]groupThe following sections contain information on
security-relevant choices during the initial operating system
installation process and the setup of software
updates. |
contains 10 rules |
System and Software Integrity
[ref]groupSystem 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. |
contains 5 rules |
Software Integrity Checking
[ref]groupBoth the AIDE (Advanced Intrusion Detection Environment)
software and the RPM package management system provide
mechanisms for verifying the integrity of installed software.
AIDE uses snapshots of file metadata (such as hashes) and compares these
to current system files in order to detect changes.
The RPM package management system can conduct integrity
checks by comparing information in its metadata database with
files installed on the system. |
contains 3 rules |
Verify Integrity with AIDE
[ref]groupAIDE conducts integrity checks by comparing information about
files with previously-gathered information. Ideally, the AIDE database is
created immediately after initial system configuration, and then again after any
software update. AIDE is highly configurable, with further configuration
information located in /usr/share/doc/aide-VERSION . |
contains 3 rules |
Install AIDE
[ref]ruleThe aide package can be installed with the following command:
$ apt-get install aide Rationale:The AIDE package must be installed if it is to be available for integrity checking. References:
BP28(R51), 1.4.1, 1, 11, 12, 13, 14, 15, 16, 2, 3, 5, 7, 8, 9, 5.10.1.3, APO01.06, BAI01.06, BAI02.01, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS03.05, DSS04.07, DSS05.02, DSS05.03, DSS05.05, DSS05.07, DSS06.02, DSS06.06, CCI-002696, CCI-002699, CCI-001744, 4.3.4.3.2, 4.3.4.3.3, 4.3.4.4.4, SR 3.1, SR 3.3, SR 3.4, SR 3.8, SR 4.1, SR 6.2, SR 7.6, 1034, 1288, 1341, 1417, A.11.2.4, A.12.1.2, A.12.2.1, A.12.4.1, A.12.5.1, A.12.6.2, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.14.2.7, A.15.2.1, A.8.2.3, CM-6(a), DE.CM-1, DE.CM-7, PR.DS-1, PR.DS-6, PR.DS-8, PR.IP-1, PR.IP-3, Req-11.5, SRG-OS-000445-GPOS-00199, UBTU-20-010450 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Ensure aide is installed
package:
name: aide
state: present
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.10.1.3
- DISA-STIG-UBTU-20-010450
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-11.5
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- package_aide_installed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
DEBIAN_FRONTEND=noninteractive apt-get install -y "aide"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include install_aide
class install_aide {
package { 'aide':
ensure => 'installed',
}
}
Remediation script: (show)
[[packages]]
name = "aide"
version = "*"
|
Configure AIDE to Verify the Audit Tools
[ref]ruleThe operating system file integrity tool must be configured to protect the integrity of the audit tools. Rationale:Protecting the integrity of the tools used for auditing purposes is a
critical step toward ensuring the integrity of audit information. Audit
information includes all information (e.g., audit records, audit settings,
and audit reports) needed to successfully audit information system
activity.
Audit tools include but are not limited to vendor-provided and open-source
audit tools needed to successfully view and manipulate audit information
system activity and records. Audit tools include custom queries and report
generators.
It is not uncommon for attackers to replace the audit tools or inject code
into the existing tools to provide the capability to hide or erase system
activity from the audit logs.
To address this risk, audit tools must be cryptographically signed to
provide the capability to identify when the audit tools have been modified,
manipulated, or replaced. An example is a checksum hash of the file or
files. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
DEBIAN_FRONTEND=noninteractive apt-get install -y "aide"
if grep -i '^.*/usr/sbin/auditctl.*$' /etc/aide/aide.conf; then
sed -i "s#.*/usr/sbin/auditctl.*#/usr/sbin/auditctl p+i+n+u+g+s+b+acl+xattrs+sha512
#" /etc/aide/aide.conf
else
echo "/usr/sbin/auditctl p+i+n+u+g+s+b+acl+xattrs+sha512
" >> /etc/aide/aide.conf
fi
if grep -i '^.*/usr/sbin/auditd.*$' /etc/aide/aide.conf; then
sed -i "s#.*/usr/sbin/auditd.*#/usr/sbin/auditd p+i+n+u+g+s+b+acl+xattrs+sha512
#" /etc/aide/aide.conf
else
echo "/usr/sbin/auditd p+i+n+u+g+s+b+acl+xattrs+sha512
" >> /etc/aide/aide.conf
fi
if grep -i '^.*/usr/sbin/ausearch.*$' /etc/aide/aide.conf; then
sed -i "s#.*/usr/sbin/ausearch.*#/usr/sbin/ausearch p+i+n+u+g+s+b+acl+xattrs+sha512
#" /etc/aide/aide.conf
else
echo "/usr/sbin/ausearch p+i+n+u+g+s+b+acl+xattrs+sha512
" >> /etc/aide/aide.conf
fi
if grep -i '^.*/usr/sbin/aureport.*$' /etc/aide/aide.conf; then
sed -i "s#.*/usr/sbin/aureport.*#/usr/sbin/aureport p+i+n+u+g+s+b+acl+xattrs+sha512
#" /etc/aide/aide.conf
else
echo "/usr/sbin/aureport p+i+n+u+g+s+b+acl+xattrs+sha512
" >> /etc/aide/aide.conf
fi
if grep -i '^.*/usr/sbin/autrace.*$' /etc/aide/aide.conf; then
sed -i "s#.*/usr/sbin/autrace.*#/usr/sbin/autrace p+i+n+u+g+s+b+acl+xattrs+sha512
#" /etc/aide/aide.conf
else
echo "/usr/sbin/autrace p+i+n+u+g+s+b+acl+xattrs+sha512
" >> /etc/aide/aide.conf
fi
if grep -i '^.*/usr/sbin/augenrules.*$' /etc/aide/aide.conf; then
sed -i "s#.*/usr/sbin/augenrules.*#/usr/sbin/augenrules p+i+n+u+g+s+b+acl+xattrs+sha512
#" /etc/aide/aide.conf
else
echo "/usr/sbin/augenrules p+i+n+u+g+s+b+acl+xattrs+sha512
" >> /etc/aide/aide.conf
fi
if grep -i '^.*/usr/sbin/audispd.*$' /etc/aide/aide.conf; then
sed -i "s#.*/usr/sbin/audispd.*#/usr/sbin/audispd p+i+n+u+g+s+b+acl+xattrs+sha512
#" /etc/aide/aide.conf
else
echo "/usr/sbin/audispd p+i+n+u+g+s+b+acl+xattrs+sha512
" >> /etc/aide/aide.conf
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Configure Periodic Execution of AIDE
[ref]ruleAt a minimum, AIDE should be configured to run a weekly scan.
To implement a daily execution of AIDE at 4:05am using cron, add the following line to /etc/crontab :
05 4 * * * root /usr/bin/aide.wrapper --config /etc/aide/aide.conf --check
To implement a weekly execution of AIDE at 4:05am using cron, add the following line to /etc/crontab :
05 4 * * 0 root /usr/bin/aide.wrapper --config /etc/aide/aide.conf --check
AIDE can be executed periodically through other means; this is merely one example.
The usage of cron's special time codes, such as @daily and
@weekly is acceptable.Rationale:By default, AIDE does not install itself for periodic execution. Periodically
running AIDE is necessary to reveal unexpected changes in installed files.
Unauthorized changes to the baseline configuration could make the system vulnerable
to various attacks or allow unauthorized access to the operating system. Changes to
operating system configurations can have unintended side effects, some of which may
be relevant to security.
Detecting such changes and providing an automated response can help avoid unintended,
negative consequences that could ultimately affect the security state of the operating
system. The operating system's Information Management Officer (IMO)/Information System
Security Officer (ISSO) and System Administrators (SAs) must be notified via email and/or
monitoring system trap when there is an unauthorized modification of a configuration item. References:
BP28(R51), 1.4.2, 1, 11, 12, 13, 14, 15, 16, 2, 3, 5, 7, 8, 9, 5.10.1.3, APO01.06, BAI01.06, BAI02.01, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS03.05, DSS04.07, DSS05.02, DSS05.03, DSS05.05, DSS05.07, DSS06.02, DSS06.06, CCI-001744, CCI-002699, CCI-002702, 4.3.4.3.2, 4.3.4.3.3, 4.3.4.4.4, SR 3.1, SR 3.3, SR 3.4, SR 3.8, SR 4.1, SR 6.2, SR 7.6, A.11.2.4, A.12.1.2, A.12.2.1, A.12.4.1, A.12.5.1, A.12.6.2, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.14.2.7, A.15.2.1, A.8.2.3, SI-7, SI-7(1), CM-6(a), DE.CM-1, DE.CM-7, PR.DS-1, PR.DS-6, PR.DS-8, PR.IP-1, PR.IP-3, Req-11.5, SRG-OS-000363-GPOS-00150, SRG-OS-000446-GPOS-00200, SRG-OS-000447-GPOS-00201, UBTU-20-010074 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Ensure AIDE is installed
package:
name: '{{ item }}'
state: present
with_items:
- aide
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.10.1.3
- DISA-STIG-UBTU-20-010074
- NIST-800-53-CM-6(a)
- NIST-800-53-SI-7
- NIST-800-53-SI-7(1)
- PCI-DSS-Req-11.5
- aide_periodic_cron_checking
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Set cron package name - RedHat
set_fact:
cron_pkg_name: cronie
when:
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- ansible_os_family == "RedHat" or ansible_os_family == "Suse"
tags:
- CJIS-5.10.1.3
- DISA-STIG-UBTU-20-010074
- NIST-800-53-CM-6(a)
- NIST-800-53-SI-7
- NIST-800-53-SI-7(1)
- PCI-DSS-Req-11.5
- aide_periodic_cron_checking
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Set cron package name - Debian
set_fact:
cron_pkg_name: cron
when:
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- ansible_os_family == "Debian"
tags:
- CJIS-5.10.1.3
- DISA-STIG-UBTU-20-010074
- NIST-800-53-CM-6(a)
- NIST-800-53-SI-7
- NIST-800-53-SI-7(1)
- PCI-DSS-Req-11.5
- aide_periodic_cron_checking
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Install cron
package:
name: '{{ cron_pkg_name }}'
state: present
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.10.1.3
- DISA-STIG-UBTU-20-010074
- NIST-800-53-CM-6(a)
- NIST-800-53-SI-7
- NIST-800-53-SI-7(1)
- PCI-DSS-Req-11.5
- aide_periodic_cron_checking
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Configure Periodic Execution of AIDE
cron:
name: run AIDE check
minute: 5
hour: 4
weekday: 0
user: root
job: /usr/bin/aide.wrapper --check
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.10.1.3
- DISA-STIG-UBTU-20-010074
- NIST-800-53-CM-6(a)
- NIST-800-53-SI-7
- NIST-800-53-SI-7(1)
- PCI-DSS-Req-11.5
- aide_periodic_cron_checking
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
DEBIAN_FRONTEND=noninteractive apt-get install -y "aide"
# AiDE usually adds its own cron jobs to /etc/cron.daily. If script is there, this rule is
# compliant. Otherwise, we copy the script to the /etc/cron.weekly
if ! egrep -q '^(\/usr\/bin\/)?aide(\.wrapper)?\s+' /etc/cron.*/*; then
cp -f /usr/share/aide/config/cron.daily/aide /etc/cron.weekly/
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Federal Information Processing Standard (FIPS)
[ref]groupThe Federal Information Processing Standard (FIPS) is a computer security standard which
is developed by the U.S. Government and industry working groups to validate the quality
of cryptographic modules. The FIPS standard provides four security levels to ensure
adequate coverage of different industries, implementation of cryptographic modules, and
organizational sizes and requirements.
FIPS 140-2 is the current standard for validating that mechanisms used to access cryptographic modules
utilize authentication that meets industry and government requirements. For government systems, this allows
Security Levels 1, 2, 3, or 4 for use on Ubuntu 20.04.
See http://csrc.nist.gov/publications/PubsFIPS.html for more information. |
contains 1 rule |
Verify '/proc/sys/crypto/fips_enabled' exists
[ref]ruleOn a system where FIPS 140-2 mode is enabled, /proc/sys/crypto/fips_enabled must exist.
To verify FIPS mode, run the following command:
cat /proc/sys/crypto/fips_enabled Warning:
To configure the OS to run in FIPS 140-2 mode, the kernel parameter "fips=1" needs to be added during its installation.
Enabling FIPS mode on a preexisting system involves a number of modifications to it. Refer to the vendor installation
guidances. Warning:
System Crypto Modules must be provided by a vendor that undergoes
FIPS-140 certifications.
FIPS-140 is applicable to all Federal agencies that use
cryptographic-based security systems to protect sensitive information
in computer and telecommunication systems (including voice systems) as
defined in Section 5131 of the Information Technology Management Reform
Act of 1996, Public Law 104-106. This standard shall be used in
designing and implementing cryptographic modules that Federal
departments and agencies operate or are operated for them under
contract. See https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.140-2.pdf
To meet this, the system has to have cryptographic software provided by
a vendor that has undergone this certification. This means providing
documentation, test results, design information, and independent third
party review by an accredited lab. While open source software is
capable of meeting this, it does not meet FIPS-140 unless the vendor
submits to this process. Rationale:Use of weak or untested encryption algorithms undermines the purposes of utilizing encryption to
protect data. The operating system must implement cryptographic modules adhering to the higher
standards approved by the federal government since this provides assurance they have been tested
and validated. |
Endpoint Protection Software
[ref]groupEndpoint protection security software that is not provided or supported
by Red Hat can be installed to provide complementary or duplicative
security capabilities to those provided by the base platform. Add-on
software may not be appropriate for some specialized systems. |
contains 1 rule |
McAfee Endpoint Security Software
[ref]groupIn DoD environments, McAfee Host-based Security System (HBSS) and
VirusScan Enterprise for Linux (VSEL) is required to be installed on all systems. |
contains 1 rule |
McAfee Endpoint Security for Linux (ENSL)
[ref]groupMcAfee Endpoint Security for Linux (ENSL) is a suite of software applications
used to monitor, detect, and defend computer networks and systems. |
contains 1 rule |
Install McAfee Endpoint Security for Linux (ENSL)
[ref]ruleInstall McAfee Endpoint Security for Linux antivirus software
which is provided for DoD systems and uses signatures to search for the
presence of viruses on the filesystem.
The mfetp package can be installed with the following command:
$ apt-get install mfetp Warning:
Due to McAfee Endpoint Security for Linux (ENSL) being 3rd party software,
automated remediation is not available for this configuration check. Rationale:Virus scanning software can be used to detect if a system has been compromised by
computer viruses, as well as to limit their spread to other systems. |
Disk Partitioning
[ref]groupTo 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. |
contains 1 rule |
Encrypt Partitions
[ref]ruleUbuntu 20.04 natively supports partition encryption through the
Linux Unified Key Setup-on-disk-format (LUKS) technology. The easiest way to
encrypt a partition is during installation time.
For manual installations, select the Encrypt checkbox during
partition creation to encrypt the partition. When this
option is selected the system will prompt for a passphrase to use in
decrypting the partition. The passphrase will subsequently need to be entered manually
every time the system boots.
For automated/unattended installations, it is possible to use Kickstart by adding
the --encrypted and --passphrase= options to the definition of each partition to be
encrypted. For example, the following line would encrypt the root partition:
part / --fstype=ext4 --size=100 --onpart=hda1 --encrypted --passphrase=PASSPHRASE
Any PASSPHRASE is stored in the Kickstart in plaintext, and the Kickstart
must then be protected accordingly.
Omitting the --passphrase= option from the partition definition will cause the
installer to pause and interactively ask for the passphrase during installation.
By default, the Anaconda installer uses aes-xts-plain64 cipher
with a minimum 512 bit key size which should be compatible with FIPS enabled.
Detailed information on encrypting partitions using LUKS or LUKS ciphers can be found on
the Ubuntu 20.04 Documentation web site:
https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/security_hardening/encrypting-block-devices-using-luks_security-hardening
.Rationale:The risk of a system's physical compromise, particularly mobile systems such as
laptops, places its data at risk of compromise. Encrypting this data mitigates
the risk of its loss if the system is lost. References:
13, 14, APO01.06, BAI02.01, BAI06.01, DSS04.07, DSS05.03, DSS05.04, DSS05.07, DSS06.02, DSS06.06, 3.13.16, CCI-001199, CCI-002475, CCI-002476, 164.308(a)(1)(ii)(D), 164.308(b)(1), 164.310(d), 164.312(a)(1), 164.312(a)(2)(iii), 164.312(a)(2)(iv), 164.312(b), 164.312(c), 164.314(b)(2)(i), 164.312(d), SR 3.4, SR 4.1, SR 5.2, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CIP-003-8 R4.2, CIP-007-3 R5.1, CM-6(a), SC-28, SC-28(1), SC-13, AU-9(3), PR.DS-1, PR.DS-5, SRG-OS-000405-GPOS-00184, SRG-OS-000185-GPOS-00079, SRG-OS-000404-GPOS-00183, UBTU-20-010414, SRG-OS-000404-VMM-001650, SRG-OS-000405-VMM-001660 |
GNOME Desktop Environment
[ref]groupGNOME is a graphical desktop environment bundled with many Linux distributions that
allow users to easily interact with the operating system graphically rather than
textually. The GNOME Graphical Display Manager (GDM) provides login, logout, and user
switching contexts as well as display server management.
GNOME is developed by the GNOME Project and is considered the default
Red Hat Graphical environment.
For more information on GNOME and the GNOME Project, see https://www.gnome.org. |
contains 2 rules |
Configure GNOME Screen Locking
[ref]groupIn the default GNOME3 desktop, the screen can be locked
by selecting the user name in the far right corner of the main panel and
selecting Lock.
The following sections detail commands to enforce idle activation of the screensaver,
screen locking, a blank-screen screensaver, and an idle activation time.
Because users should be trained to lock the screen when they
step away from the computer, the automatic locking feature is only
meant as a backup.
The root account can be screen-locked; however, the root account should
never be used to log into an X Windows environment and should only
be used to for direct login via console in emergency circumstances.
For more information about enforcing preferences in the GNOME3 environment using the DConf
configuration system, see http://wiki.gnome.org/dconf and
the man page dconf(1) . |
contains 1 rule |
Enable GNOME3 Screensaver Lock After Idle Period
[ref]rule
To activate locking of the screensaver in the GNOME3 desktop when it is activated,
add or set lock-enabled to true in
/etc/dconf/db/local.d/00-security-settings . For example:
[org/gnome/desktop/screensaver]
lock-enabled=true
Once the settings have been added, add a lock to
/etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification.
For example:
/org/gnome/desktop/screensaver/lock-enabled
After the settings have been set, run dconf update .Rationale:A session lock is a temporary action taken when a user stops work and moves away from the immediate physical vicinity
of the information system but does not want to logout because of the temporary nature of the absense. References:
1, 12, 15, 16, 5.5.5, DSS05.04, DSS05.10, DSS06.10, 3.1.10, CCI-000056, CCI-000058, CCI-000060, 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, CM-6(a), PR.AC-7, FMT_MOF_EXT.1, Req-8.1.8, SRG-OS-000028-GPOS-00009, SRG-OS-000030-GPOS-00011, UBTU-20-010004 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'gdm3' 2>/dev/null | grep -q installed && { [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; }; then
# Check for setting in any of the DConf db directories
# If files contain ibus or distro, ignore them.
# The assignment assumes that individual filenames don't contain :
readarray -t SETTINGSFILES < <(grep -r "\\[org/gnome/desktop/screensaver\\]" "/etc/dconf/db/" \
| grep -v 'distro\|ibus\|local.d' | cut -d":" -f1)
DCONFFILE="/etc/dconf/db/local.d/00-security-settings"
DBDIR="/etc/dconf/db/local.d"
mkdir -p "${DBDIR}"
# Comment out the configurations in databases different from the target one
if [ "${#SETTINGSFILES[@]}" -ne 0 ]
then
if grep -q "^\\s*lock-enabled\\s*=" "${SETTINGSFILES[@]}"
then
sed -Ei "s/(^\s*)lock-enabled(\s*=)/#\1lock-enabled\2/g" "${SETTINGSFILES[@]}"
fi
fi
[ ! -z "${DCONFFILE}" ] && echo "" >> "${DCONFFILE}"
if ! grep -q "\\[org/gnome/desktop/screensaver\\]" "${DCONFFILE}"
then
printf '%s\n' "[org/gnome/desktop/screensaver]" >> ${DCONFFILE}
fi
escaped_value="$(sed -e 's/\\/\\\\/g' <<< "true")"
if grep -q "^\\s*lock-enabled\\s*=" "${DCONFFILE}"
then
sed -i "s/\\s*lock-enabled\\s*=\\s*.*/lock-enabled=${escaped_value}/g" "${DCONFFILE}"
else
sed -i "\\|\\[org/gnome/desktop/screensaver\\]|a\\lock-enabled=${escaped_value}" "${DCONFFILE}"
fi
dconf update
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/org/gnome/desktop/screensaver/lock-enabled$" "/etc/dconf/db/" \
| grep -v 'distro\|ibus\|local.d' | grep ":" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/local.d/locks"
mkdir -p "${LOCKSFOLDER}"
# Comment out the configurations in databases different from the target one
if [[ ! -z "${LOCKFILES}" ]]
then
sed -i -E "s|^/org/gnome/desktop/screensaver/lock-enabled$|#&|" "${LOCKFILES[@]}"
fi
if ! grep -qr "^/org/gnome/desktop/screensaver/lock-enabled$" /etc/dconf/db/local.d/
then
echo "/org/gnome/desktop/screensaver/lock-enabled" >> "/etc/dconf/db/local.d/locks/00-security-settings-lock"
fi
dconf update
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
GNOME System Settings
[ref]groupGNOME provides configuration and functionality to a graphical desktop environment
that changes grahical configurations or allow a user to perform
actions that users normally would not be able to do in non-graphical mode such as
remote access configuration, power policies, Geo-location, etc.
Configuring such settings in GNOME will prevent accidential graphical configuration
changes by users from taking place. |
contains 1 rule |
Disable Ctrl-Alt-Del Reboot Key Sequence in GNOME3
[ref]ruleBy default, GNOME will reboot the system if the
Ctrl-Alt-Del key sequence is pressed.
To configure the system to ignore the Ctrl-Alt-Del key sequence
from the Graphical User Interface (GUI) instead of rebooting the system,
add or set logout to '' in
/etc/dconf/db/local.d/00-security-settings . For example:
[org/gnome/settings-daemon/plugins/media-keys]
logout=''
Once the settings have been added, add a lock to
/etc/dconf/db/local.d/locks/00-security-settings-lock to prevent
user modification. For example:
/org/gnome/settings-daemon/plugins/media-keys/logout
After the settings have been set, run dconf update .Rationale:A locally logged-in user who presses Ctrl-Alt-Del, when at the console,
can reboot the system. If accidentally pressed, as could happen in
the case of mixed OS environment, this can create the risk of short-term
loss of availability of systems due to unintentional reboot. References:
12, 13, 14, 15, 16, 18, 3, 5, APO01.06, DSS05.04, DSS05.07, DSS06.02, 3.1.2, CCI-000366, 4.3.3.7.3, SR 2.1, SR 5.2, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CM-6(a), AC-6(1), CM-7(b), PR.AC-4, PR.DS-5, SRG-OS-000480-GPOS-00227, UBTU-20-010459 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'gdm3' 2>/dev/null | grep -q installed && { [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; }; then
# Check for setting in any of the DConf db directories
# If files contain ibus or distro, ignore them.
# The assignment assumes that individual filenames don't contain :
readarray -t SETTINGSFILES < <(grep -r "\\[org/gnome/settings-daemon/plugins/media-keys\\]" "/etc/dconf/db/" \
| grep -v 'distro\|ibus\|local.d' | cut -d":" -f1)
DCONFFILE="/etc/dconf/db/local.d/00-security-settings"
DBDIR="/etc/dconf/db/local.d"
mkdir -p "${DBDIR}"
# Comment out the configurations in databases different from the target one
if [ "${#SETTINGSFILES[@]}" -ne 0 ]
then
if grep -q "^\\s*logout\\s*=" "${SETTINGSFILES[@]}"
then
sed -Ei "s/(^\s*)logout(\s*=)/#\1logout\2/g" "${SETTINGSFILES[@]}"
fi
fi
[ ! -z "${DCONFFILE}" ] && echo "" >> "${DCONFFILE}"
if ! grep -q "\\[org/gnome/settings-daemon/plugins/media-keys\\]" "${DCONFFILE}"
then
printf '%s\n' "[org/gnome/settings-daemon/plugins/media-keys]" >> ${DCONFFILE}
fi
escaped_value="$(sed -e 's/\\/\\\\/g' <<< "''")"
if grep -q "^\\s*logout\\s*=" "${DCONFFILE}"
then
sed -i "s/\\s*logout\\s*=\\s*.*/logout=${escaped_value}/g" "${DCONFFILE}"
else
sed -i "\\|\\[org/gnome/settings-daemon/plugins/media-keys\\]|a\\logout=${escaped_value}" "${DCONFFILE}"
fi
dconf update
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/org/gnome/settings-daemon/plugins/media-keys/logout$" "/etc/dconf/db/" \
| grep -v 'distro\|ibus\|local.d' | grep ":" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/local.d/locks"
mkdir -p "${LOCKSFOLDER}"
# Comment out the configurations in databases different from the target one
if [[ ! -z "${LOCKFILES}" ]]
then
sed -i -E "s|^/org/gnome/settings-daemon/plugins/media-keys/logout$|#&|" "${LOCKFILES[@]}"
fi
if ! grep -qr "^/org/gnome/settings-daemon/plugins/media-keys/logout$" /etc/dconf/db/local.d/
then
echo "/org/gnome/settings-daemon/plugins/media-keys/logout" >> "/etc/dconf/db/local.d/locks/00-security-settings-lock"
fi
dconf update
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Sudo , which stands for "su 'do'", provides the ability to delegate authority
to certain users, groups of users, or system administrators. When configured for system
users and/or groups, Sudo can allow a user or group to execute privileged commands
that normally only root is allowed to execute.
For more information on Sudo and addition Sudo configuration options, see
https://www.sudo.ws.
|
contains 1 rule |
Ensure Users Re-Authenticate for Privilege Escalation - sudo
[ref]ruleThe sudo NOPASSWD and !authenticate option, when
specified, allows a user to execute commands using sudo without having to
authenticate. This should be disabled by making sure that
NOPASSWD and/or !authenticate do not exist in
/etc/sudoers configuration file or any sudo configuration snippets
in /etc/sudoers.d/ ." Rationale:Without re-authentication, users may access resources or perform tasks for which they
do not have authorization.
When operating systems provide the capability to escalate a functional capability, it
is critical that the user re-authenticate. References:
1, 12, 15, 16, 5, DSS05.04, DSS05.10, DSS06.03, DSS06.10, CCI-002038, 4.3.3.5.1, 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.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, A.18.1.4, 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-11, CM-6(a), PR.AC-1, PR.AC-7, SRG-OS-000373-GPOS-00156, UBTU-20-010014 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Find /etc/sudoers.d/ files
find:
paths:
- /etc/sudoers.d/
register: sudoers
tags:
- DISA-STIG-UBTU-20-010014
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-11
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sudo_require_authentication
- name: Remove lines containing NOPASSWD from sudoers files
replace:
regexp: (^(?!#).*[\s]+NOPASSWD[\s]*\:.*$)
replace: '# \g<1>'
path: '{{ item.path }}'
validate: /usr/sbin/visudo -cf %s
with_items:
- path: /etc/sudoers
- '{{ sudoers.files }}'
tags:
- DISA-STIG-UBTU-20-010014
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-11
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sudo_require_authentication
- name: Find /etc/sudoers.d/ files
find:
paths:
- /etc/sudoers.d/
register: sudoers
tags:
- DISA-STIG-UBTU-20-010014
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-11
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sudo_require_authentication
- name: Remove lines containing !authenticate from sudoers files
replace:
regexp: (^(?!#).*[\s]+\!authenticate.*$)
replace: '# \g<1>'
path: '{{ item.path }}'
validate: /usr/sbin/visudo -cf %s
with_items:
- path: /etc/sudoers
- '{{ sudoers.files }}'
tags:
- DISA-STIG-UBTU-20-010014
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-11
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sudo_require_authentication
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
for f in /etc/sudoers /etc/sudoers.d/* ; do
if [ ! -e "$f" ] ; then
continue
fi
matching_list=$(grep -P '^(?!#).*[\s]+NOPASSWD[\s]*\:.*$' $f | uniq )
if ! test -z "$matching_list"; then
while IFS= read -r entry; do
# comment out "NOPASSWD" matches to preserve user data
sed -i "s/^${entry}$/# &/g" $f
done <<< "$matching_list"
/usr/sbin/visudo -cf $f &> /dev/null || echo "Fail to validate $f with visudo"
fi
done
for f in /etc/sudoers /etc/sudoers.d/* ; do
if [ ! -e "$f" ] ; then
continue
fi
matching_list=$(grep -P '^(?!#).*[\s]+\!authenticate.*$' $f | uniq )
if ! test -z "$matching_list"; then
while IFS= read -r entry; do
# comment out "!authenticate" matches to preserve user data
sed -i "s/^${entry}$/# &/g" $f
done <<< "$matching_list"
/usr/sbin/visudo -cf $f &> /dev/null || echo "Fail to validate $f with visudo"
fi
done
|
Updating Software
[ref]groupThe apt_get 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.
Ubuntu 20.04 systems contain an installed software catalog called
the RPM database, which records metadata of installed packages. Consistently using
apt_get or the graphical Software Update for all software installation
allows for insight into the current inventory of installed software on the system.
|
contains 1 rule |
Ensure apt_get Removes Previous Package Versions
[ref]ruleapt_get should be configured to remove previous software components after
new versions have been installed. To configure apt_get to remove the
previous software components after updating, set the ::Remove-Unused-Dependencies and
::Remove-Unused-Kernel-Packages
to true in . Rationale:Previous versions of software components that are not removed from the information
system after updates have been installed may be exploited by some adversaries. References:
18, 20, 4, APO12.01, APO12.02, APO12.03, APO12.04, BAI03.10, DSS05.01, DSS05.02, 3.4.8, CCI-002617, 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(6), CM-11(a), CM-11(b), CM-6(a), ID.RA-1, PR.IP-12, SRG-OS-000437-GPOS-00194, UBTU-20-010449, SRG-OS-000437-VMM-001760 Remediation Shell script: (show)
flag1=1
flag2=1
for file in /etc/apt/apt.conf.d/*; do
if [ -e "$file" ]; then
if grep -qi "Unattended-Upgrade::Remove-Unused-Dependencies" $file; then
sed -i --follow-symlinks "s/^.*Unattended-Upgrade::Remove-Unused-Dependencies.*/Unattended-Upgrade::Remove-Unused-Dependencies \"true\";/I" $file
flag1=0
fi
if grep -qi "Unattended-Upgrade::Remove-Unused-Kernel-Packages" $file; then
sed -i --follow-symlinks "s/^.*Unattended-Upgrade::Remove-Unused-Kernel-Packages.*/Unattended-Upgrade::Remove-Unused-Kernel-Packages \"true\";/I" $file
flag2=0
fi
fi
done
if [ $flag1 ] || [ $flag2 ]; then
echo "Unattended-Upgrade::Remove-Unused-Dependencies \"true\";" >> /etc/apt/apt.conf.d/50unattended-upgrades
echo "Unattended-Upgrade::Remove-Unused-Kernel-Packages \"true\";" >> /etc/apt/apt.conf.d/50unattended-upgrades
fi
|
Account and Access Control
[ref]groupIn 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
Ubuntu 20.04. |
contains 39 rules |
Warning Banners for System Accesses
[ref]groupEach system should expose as little information about
itself as possible.
System banners, which are typically displayed just before a
login prompt, give out information about the service or the host's
operating system. This might include the distribution name and the
system kernel version, and the particular version of a network
service. This information can assist intruders in gaining access to
the system as it can reveal whether the system is running
vulnerable software. Most network services can be configured to
limit what information is displayed.
Many organizations implement security policies that require a
system banner provide notice of the system's ownership, provide
warning to unauthorized users, and remind authorized users of their
consent to monitoring. |
contains 3 rules |
Implement a GUI Warning Banner
[ref]groupIn the default graphical environment, users logging
directly into the system are greeted with a login screen provided
by the GNOME Display Manager (GDM). The warning banner should be
displayed in this graphical environment for these users.
The following sections describe how to configure the GDM login
banner. |
contains 2 rules |
Enable GNOME3 Login Warning Banner
[ref]ruleIn the default graphical environment, displaying a login warning banner
in the GNOME Display Manager's login screen can be enabled on the login
screen by setting banner-message-enable to true .
To enable, add or edit banner-message-enable to
/etc/dconf/db/gdm.d/00-security-settings . For example:
[org/gnome/login-screen]
banner-message-enable=true
Once the setting has been added, add a lock to
/etc/dconf/db/gdm.d/locks/00-security-settings-lock to prevent user modification.
For example:
/org/gnome/login-screen/banner-message-enable
After the settings have been set, run dconf update .
The banner text must also be set.Rationale:Display of a standardized and approved use notification before granting access to the operating system
ensures privacy and security notification verbiage used is consistent with applicable federal laws,
Executive Orders, directives, policies, regulations, standards, and guidance.
For U.S. Government systems, system use notifications are required only for access via login interfaces
with human users and are not required when such human interfaces do not exist. References:
1.10, 1, 12, 15, 16, DSS05.04, DSS05.10, DSS06.10, 3.1.9, CCI-000048, CCI-000050, CCI-001384, CCI-001385, CCI-001386, CCI-001387, CCI-001388, 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, AC-8(a), AC-8(b), AC-8(c), PR.AC-7, FMT_MOF_EXT.1, SRG-OS-000023-GPOS-00006, SRG-OS-000228-GPOS-00088, UBTU-20-010002 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'gdm3' 2>/dev/null | grep -q installed; then
# Check for setting in any of the DConf db directories
# If files contain ibus or distro, ignore them.
# The assignment assumes that individual filenames don't contain :
readarray -t SETTINGSFILES < <(grep -r "\\[org/gnome/login-screen\\]" "/etc/dconf/db/" \
| grep -v 'distro\|ibus\|gdm.d' | cut -d":" -f1)
DCONFFILE="/etc/dconf/db/gdm.d/00-security-settings"
DBDIR="/etc/dconf/db/gdm.d"
mkdir -p "${DBDIR}"
# Comment out the configurations in databases different from the target one
if [ "${#SETTINGSFILES[@]}" -ne 0 ]
then
if grep -q "^\\s*banner-message-enable\\s*=" "${SETTINGSFILES[@]}"
then
sed -Ei "s/(^\s*)banner-message-enable(\s*=)/#\1banner-message-enable\2/g" "${SETTINGSFILES[@]}"
fi
fi
[ ! -z "${DCONFFILE}" ] && echo "" >> "${DCONFFILE}"
if ! grep -q "\\[org/gnome/login-screen\\]" "${DCONFFILE}"
then
printf '%s\n' "[org/gnome/login-screen]" >> ${DCONFFILE}
fi
escaped_value="$(sed -e 's/\\/\\\\/g' <<< "true")"
if grep -q "^\\s*banner-message-enable\\s*=" "${DCONFFILE}"
then
sed -i "s/\\s*banner-message-enable\\s*=\\s*.*/banner-message-enable=${escaped_value}/g" "${DCONFFILE}"
else
sed -i "\\|\\[org/gnome/login-screen\\]|a\\banner-message-enable=${escaped_value}" "${DCONFFILE}"
fi
dconf update
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/org/gnome/login-screen/banner-message-enable$" "/etc/dconf/db/" \
| grep -v 'distro\|ibus\|gdm.d' | grep ":" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/gdm.d/locks"
mkdir -p "${LOCKSFOLDER}"
# Comment out the configurations in databases different from the target one
if [[ ! -z "${LOCKFILES}" ]]
then
sed -i -E "s|^/org/gnome/login-screen/banner-message-enable$|#&|" "${LOCKFILES[@]}"
fi
if ! grep -qr "^/org/gnome/login-screen/banner-message-enable$" /etc/dconf/db/gdm.d/
then
echo "/org/gnome/login-screen/banner-message-enable" >> "/etc/dconf/db/gdm.d/locks/00-security-settings-lock"
fi
dconf update
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Set the GNOME3 Login Warning Banner Text
[ref]ruleIn the default graphical environment, configuring the login warning banner text
in the GNOME Display Manager's login screen can be configured on the login
screen by setting banner-message-text to 'APPROVED_BANNER'
where APPROVED_BANNER is the approved banner for your environment.
To enable, add or edit banner-message-text to
/etc/gdm3/greeter.dconf-defaults . For example:
[org/gnome/login-screen]
banner-message-text='APPROVED_BANNER'
After the settings have been set, run dconf update .
When entering a warning banner that spans several lines, remember
to begin and end the string with ' and use \n for new lines.Rationale:An appropriate warning message reinforces policy awareness during the logon
process and facilitates possible legal action against attackers. References:
1.10, 1, 12, 15, 16, DSS05.04, DSS05.10, DSS06.10, 3.1.9, CCI-000048, CCI-001384, CCI-001385, CCI-001386, CCI-001387, CCI-001388, 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, AC-8(a), AC-8(c), PR.AC-7, FMT_MOF_EXT.1, SRG-OS-000023-GPOS-00006, SRG-OS-000228-GPOS-00088, UBTU-20-010003 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'gdm3' 2>/dev/null | grep -q installed; then
login_banner_text='^(You[\s\n]+are[\s\n]+accessing[\s\n]+a[\s\n]+U\.S\.[\s\n]+Government[\s\n]+\(USG\)[\s\n]+Information[\s\n]+System[\s\n]+\(IS\)[\s\n]+that[\s\n]+is[\s\n]+provided[\s\n]+for[\s\n]+USG\-authorized[\s\n]+use[\s\n]+only\.[\s\n]+By[\s\n]+using[\s\n]+this[\s\n]+IS[\s\n]+\(which[\s\n]+includes[\s\n]+any[\s\n]+device[\s\n]+attached[\s\n]+to[\s\n]+this[\s\n]+IS\)\,[\s\n]+you[\s\n]+consent[\s\n]+to[\s\n]+the[\s\n]+following[\s\n]+conditions\:(?:[\n]+|(?:\\n)+)\-The[\s\n]+USG[\s\n]+routinely[\s\n]+intercepts[\s\n]+and[\s\n]+monitors[\s\n]+communications[\s\n]+on[\s\n]+this[\s\n]+IS[\s\n]+for[\s\n]+purposes[\s\n]+including\,[\s\n]+but[\s\n]+not[\s\n]+limited[\s\n]+to\,[\s\n]+penetration[\s\n]+testing\,[\s\n]+COMSEC[\s\n]+monitoring\,[\s\n]+network[\s\n]+operations[\s\n]+and[\s\n]+defense\,[\s\n]+personnel[\s\n]+misconduct[\s\n]+\(PM\)\,[\s\n]+law[\s\n]+enforcement[\s\n]+\(LE\)\,[\s\n]+and[\s\n]+counterintelligence[\s\n]+\(CI\)[\s\n]+investigations\.(?:[\n]+|(?:\\n)+)\-At[\s\n]+any[\s\n]+time\,[\s\n]+the[\s\n]+USG[\s\n]+may[\s\n]+inspect[\s\n]+and[\s\n]+seize[\s\n]+data[\s\n]+stored[\s\n]+on[\s\n]+this[\s\n]+IS\.(?:[\n]+|(?:\\n)+)\-Communications[\s\n]+using\,[\s\n]+or[\s\n]+data[\s\n]+stored[\s\n]+on\,[\s\n]+this[\s\n]+IS[\s\n]+are[\s\n]+not[\s\n]+private\,[\s\n]+are[\s\n]+subject[\s\n]+to[\s\n]+routine[\s\n]+monitoring\,[\s\n]+interception\,[\s\n]+and[\s\n]+search\,[\s\n]+and[\s\n]+may[\s\n]+be[\s\n]+disclosed[\s\n]+or[\s\n]+used[\s\n]+for[\s\n]+any[\s\n]+USG\-authorized[\s\n]+purpose\.(?:[\n]+|(?:\\n)+)\-This[\s\n]+IS[\s\n]+includes[\s\n]+security[\s\n]+measures[\s\n]+\(e\.g\.\,[\s\n]+authentication[\s\n]+and[\s\n]+access[\s\n]+controls\)[\s\n]+to[\s\n]+protect[\s\n]+USG[\s\n]+interests\-\-not[\s\n]+for[\s\n]+your[\s\n]+personal[\s\n]+benefit[\s\n]+or[\s\n]+privacy\.(?:[\n]+|(?:\\n)+)\-Notwithstanding[\s\n]+the[\s\n]+above\,[\s\n]+using[\s\n]+this[\s\n]+IS[\s\n]+does[\s\n]+not[\s\n]+constitute[\s\n]+consent[\s\n]+to[\s\n]+PM\,[\s\n]+LE[\s\n]+or[\s\n]+CI[\s\n]+investigative[\s\n]+searching[\s\n]+or[\s\n]+monitoring[\s\n]+of[\s\n]+the[\s\n]+content[\s\n]+of[\s\n]+privileged[\s\n]+communications\,[\s\n]+or[\s\n]+work[\s\n]+product\,[\s\n]+related[\s\n]+to[\s\n]+personal[\s\n]+representation[\s\n]+or[\s\n]+services[\s\n]+by[\s\n]+attorneys\,[\s\n]+psychotherapists\,[\s\n]+or[\s\n]+clergy\,[\s\n]+and[\s\n]+their[\s\n]+assistants\.[\s\n]+Such[\s\n]+communications[\s\n]+and[\s\n]+work[\s\n]+product[\s\n]+are[\s\n]+private[\s\n]+and[\s\n]+confidential\.[\s\n]+See[\s\n]+User[\s\n]+Agreement[\s\n]+for[\s\n]+details\.|I've[\s\n]+read[\s\n]+\&[\s\n]+consent[\s\n]+to[\s\n]+terms[\s\n]+in[\s\n]+IS[\s\n]+user[\s\n]+agreem't\.)$'
# Multiple regexes transform the banner regex into a usable banner
# 0 - Remove anchors around the banner text
login_banner_text=$(echo "$login_banner_text" | sed 's/^\^\(.*\)\$$/\1/g')
# 1 - Keep only the first banners if there are multiple
# (dod_banners contains the long and short banner)
login_banner_text=$(echo "$login_banner_text" | sed 's/^(\(.*\.\)|.*)$/\1/g')
# 2 - Add spaces ' '. (Transforms regex for "space or newline" into a " ")
login_banner_text=$(echo "$login_banner_text" | sed 's/\[\\s\\n\]+/ /g')
# 3 - Adds newline "tokens". (Transforms "(?:\[\\n\]+|(?:\\n)+)" into "(n)*")
login_banner_text=$(echo "$login_banner_text" | sed 's/(?:\[\\n\]+|(?:\\\\n)+)/(n)*/g')
# 4 - Remove any leftover backslash. (From any parethesis in the banner, for example).
login_banner_text=$(echo "$login_banner_text" | sed 's/\\//g')
# 5 - Removes the newline "token." (Transforms them into newline escape sequences "\n").
# ( Needs to be done after 4, otherwise the escapce sequence will become just "n".
login_banner_text=$(echo "$login_banner_text" | sed 's/(n)\*/\\n/g')
# Will do both approach, since we plan to migrate to checks over dconf db. That way, future updates of the tool
# will pass the check even if we decide to check only for the dconf db path.
if [ -e "/etc/gdm3/greeter.dconf-defaults" ] ; then
LC_ALL=C sed -i "/^\s*banner\-message\-text/Id" "/etc/gdm3/greeter.dconf-defaults"
else
touch "/etc/gdm3/greeter.dconf-defaults"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/gdm3/greeter.dconf-defaults"
cp "/etc/gdm3/greeter.dconf-defaults" "/etc/gdm3/greeter.dconf-defaults.bak"
# Insert after the line matching the regex '\[org/gnome/login-screen\]'
line_number="$(LC_ALL=C grep -n "\[org/gnome/login-screen\]" "/etc/gdm3/greeter.dconf-defaults.bak" | LC_ALL=C sed 's/:.*//g')"
if [ -z "$line_number" ]; then
# There was no match of '\[org/gnome/login-screen\]', insert at
# the end of the file.
printf '%s\n' "banner-message-text='${login_banner_text}'" >> "/etc/gdm3/greeter.dconf-defaults"
else
head -n "$(( line_number ))" "/etc/gdm3/greeter.dconf-defaults.bak" > "/etc/gdm3/greeter.dconf-defaults"
printf '%s\n' "banner-message-text='${login_banner_text}'" >> "/etc/gdm3/greeter.dconf-defaults"
tail -n "+$(( line_number + 1 ))" "/etc/gdm3/greeter.dconf-defaults.bak" >> "/etc/gdm3/greeter.dconf-defaults"
fi
# Clean up after ourselves.
rm "/etc/gdm3/greeter.dconf-defaults.bak"
# Check for setting in any of the DConf db directories
# If files contain ibus or distro, ignore them.
# The assignment assumes that individual filenames don't contain :
readarray -t SETTINGSFILES < <(grep -r "\\[org/gnome/login-screen\\]" "/etc/dconf/db/" \
| grep -v 'distro\|ibus\|gdm.d' | cut -d":" -f1)
DCONFFILE="/etc/dconf/db/gdm.d/00-security-settings"
DBDIR="/etc/dconf/db/gdm.d"
mkdir -p "${DBDIR}"
# Comment out the configurations in databases different from the target one
if [ "${#SETTINGSFILES[@]}" -ne 0 ]
then
if grep -q "^\\s*banner-message-text\\s*=" "${SETTINGSFILES[@]}"
then
sed -Ei "s/(^\s*)banner-message-text(\s*=)/#\1banner-message-text\2/g" "${SETTINGSFILES[@]}"
fi
fi
[ ! -z "${DCONFFILE}" ] && echo "" >> "${DCONFFILE}"
if ! grep -q "\\[org/gnome/login-screen\\]" "${DCONFFILE}"
then
printf '%s\n' "[org/gnome/login-screen]" >> ${DCONFFILE}
fi
escaped_value="$(sed -e 's/\\/\\\\/g' <<< "'${login_banner_text}'")"
if grep -q "^\\s*banner-message-text\\s*=" "${DCONFFILE}"
then
sed -i "s/\\s*banner-message-text\\s*=\\s*.*/banner-message-text=${escaped_value}/g" "${DCONFFILE}"
else
sed -i "\\|\\[org/gnome/login-screen\\]|a\\banner-message-text=${escaped_value}" "${DCONFFILE}"
fi
dconf update
# No need to use dconf update, since bash_dconf_settings does that already
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Modify the System Login Banner for Remote Connections
[ref]ruleTo configure the system login banner edit /etc/issue.net . Replace the
default text with a message compliant with the local site policy or a legal
disclaimer.
The DoD required text is either:
You are accessing a U.S. Government (USG) Information System (IS) that
is provided for USG-authorized use only. By using this IS (which includes
any device attached to this IS), you consent to the following conditions:
-The USG routinely intercepts and monitors communications on this IS
for purposes including, but not limited to, penetration testing, COMSEC
monitoring, network operations and defense, personnel misconduct (PM), law
enforcement (LE), and counterintelligence (CI) investigations.
-At any time, the USG may inspect and seize data stored on this IS.
-Communications using, or data stored on, this IS are not private,
are subject to routine monitoring, interception, and search, and may be
disclosed or used for any USG-authorized purpose.
-This IS includes security measures (e.g., authentication and access
controls) to protect USG interests -- not for your personal benefit or
privacy.
-Notwithstanding the above, using this IS does not constitute consent
to PM, LE or CI investigative searching or monitoring of the content of
privileged communications, or work product, related to personal
representation or services by attorneys, psychotherapists, or clergy, and
their assistants. Such communications and work product are private and
confidential. See User Agreement for details.
OR:
I've read & consent to terms in IS user agreem't. Rationale:Display of a standardized and approved use notification before granting
access to the operating system ensures privacy and security notification
verbiage used is consistent with applicable federal laws, Executive Orders,
directives, policies, regulations, standards, and guidance.
System use notifications are required only for access via login interfaces
with human users and are not required when such human interfaces do not
exist. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | false |
---|
Strategy: | unknown |
---|
- name: XCCDF Value login_banner_text # promote to variable
set_fact:
login_banner_text: !!str ^(You[\s\n]+are[\s\n]+accessing[\s\n]+a[\s\n]+U\.S\.[\s\n]+Government[\s\n]+\(USG\)[\s\n]+Information[\s\n]+System[\s\n]+\(IS\)[\s\n]+that[\s\n]+is[\s\n]+provided[\s\n]+for[\s\n]+USG\-authorized[\s\n]+use[\s\n]+only\.[\s\n]+By[\s\n]+using[\s\n]+this[\s\n]+IS[\s\n]+\(which[\s\n]+includes[\s\n]+any[\s\n]+device[\s\n]+attached[\s\n]+to[\s\n]+this[\s\n]+IS\)\,[\s\n]+you[\s\n]+consent[\s\n]+to[\s\n]+the[\s\n]+following[\s\n]+conditions\:(?:[\n]+|(?:\\n)+)\-The[\s\n]+USG[\s\n]+routinely[\s\n]+intercepts[\s\n]+and[\s\n]+monitors[\s\n]+communications[\s\n]+on[\s\n]+this[\s\n]+IS[\s\n]+for[\s\n]+purposes[\s\n]+including\,[\s\n]+but[\s\n]+not[\s\n]+limited[\s\n]+to\,[\s\n]+penetration[\s\n]+testing\,[\s\n]+COMSEC[\s\n]+monitoring\,[\s\n]+network[\s\n]+operations[\s\n]+and[\s\n]+defense\,[\s\n]+personnel[\s\n]+misconduct[\s\n]+\(PM\)\,[\s\n]+law[\s\n]+enforcement[\s\n]+\(LE\)\,[\s\n]+and[\s\n]+counterintelligence[\s\n]+\(CI\)[\s\n]+investigations\.(?:[\n]+|(?:\\n)+)\-At[\s\n]+any[\s\n]+time\,[\s\n]+the[\s\n]+USG[\s\n]+may[\s\n]+inspect[\s\n]+and[\s\n]+seize[\s\n]+data[\s\n]+stored[\s\n]+on[\s\n]+this[\s\n]+IS\.(?:[\n]+|(?:\\n)+)\-Communications[\s\n]+using\,[\s\n]+or[\s\n]+data[\s\n]+stored[\s\n]+on\,[\s\n]+this[\s\n]+IS[\s\n]+are[\s\n]+not[\s\n]+private\,[\s\n]+are[\s\n]+subject[\s\n]+to[\s\n]+routine[\s\n]+monitoring\,[\s\n]+interception\,[\s\n]+and[\s\n]+search\,[\s\n]+and[\s\n]+may[\s\n]+be[\s\n]+disclosed[\s\n]+or[\s\n]+used[\s\n]+for[\s\n]+any[\s\n]+USG\-authorized[\s\n]+purpose\.(?:[\n]+|(?:\\n)+)\-This[\s\n]+IS[\s\n]+includes[\s\n]+security[\s\n]+measures[\s\n]+\(e\.g\.\,[\s\n]+authentication[\s\n]+and[\s\n]+access[\s\n]+controls\)[\s\n]+to[\s\n]+protect[\s\n]+USG[\s\n]+interests\-\-not[\s\n]+for[\s\n]+your[\s\n]+personal[\s\n]+benefit[\s\n]+or[\s\n]+privacy\.(?:[\n]+|(?:\\n)+)\-Notwithstanding[\s\n]+the[\s\n]+above\,[\s\n]+using[\s\n]+this[\s\n]+IS[\s\n]+does[\s\n]+not[\s\n]+constitute[\s\n]+consent[\s\n]+to[\s\n]+PM\,[\s\n]+LE[\s\n]+or[\s\n]+CI[\s\n]+investigative[\s\n]+searching[\s\n]+or[\s\n]+monitoring[\s\n]+of[\s\n]+the[\s\n]+content[\s\n]+of[\s\n]+privileged[\s\n]+communications\,[\s\n]+or[\s\n]+work[\s\n]+product\,[\s\n]+related[\s\n]+to[\s\n]+personal[\s\n]+representation[\s\n]+or[\s\n]+services[\s\n]+by[\s\n]+attorneys\,[\s\n]+psychotherapists\,[\s\n]+or[\s\n]+clergy\,[\s\n]+and[\s\n]+their[\s\n]+assistants\.[\s\n]+Such[\s\n]+communications[\s\n]+and[\s\n]+work[\s\n]+product[\s\n]+are[\s\n]+private[\s\n]+and[\s\n]+confidential\.[\s\n]+See[\s\n]+User[\s\n]+Agreement[\s\n]+for[\s\n]+details\.|I've[\s\n]+read[\s\n]+\&[\s\n]+consent[\s\n]+to[\s\n]+terms[\s\n]+in[\s\n]+IS[\s\n]+user[\s\n]+agreem't\.)$
tags:
- always
- name: Modify the System Login Banner for Remote Connections - ensure correct banner
copy:
dest: /etc/issue.net
content: '{{ login_banner_text | regex_replace("^\^(.*)\$$", "\1") | regex_replace("^\((.*\.)\|.*\)$",
"\1") | regex_replace("\[\\s\\n\]\+"," ") | regex_replace("\(\?:\[\\n\]\+\|\(\?:\\\\n\)\+\)",
"\n") | regex_replace("\\", "") | wordwrap() }}'
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010038
- banner_etc_issue_net
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- unknown_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
login_banner_text='^(You[\s\n]+are[\s\n]+accessing[\s\n]+a[\s\n]+U\.S\.[\s\n]+Government[\s\n]+\(USG\)[\s\n]+Information[\s\n]+System[\s\n]+\(IS\)[\s\n]+that[\s\n]+is[\s\n]+provided[\s\n]+for[\s\n]+USG\-authorized[\s\n]+use[\s\n]+only\.[\s\n]+By[\s\n]+using[\s\n]+this[\s\n]+IS[\s\n]+\(which[\s\n]+includes[\s\n]+any[\s\n]+device[\s\n]+attached[\s\n]+to[\s\n]+this[\s\n]+IS\)\,[\s\n]+you[\s\n]+consent[\s\n]+to[\s\n]+the[\s\n]+following[\s\n]+conditions\:(?:[\n]+|(?:\\n)+)\-The[\s\n]+USG[\s\n]+routinely[\s\n]+intercepts[\s\n]+and[\s\n]+monitors[\s\n]+communications[\s\n]+on[\s\n]+this[\s\n]+IS[\s\n]+for[\s\n]+purposes[\s\n]+including\,[\s\n]+but[\s\n]+not[\s\n]+limited[\s\n]+to\,[\s\n]+penetration[\s\n]+testing\,[\s\n]+COMSEC[\s\n]+monitoring\,[\s\n]+network[\s\n]+operations[\s\n]+and[\s\n]+defense\,[\s\n]+personnel[\s\n]+misconduct[\s\n]+\(PM\)\,[\s\n]+law[\s\n]+enforcement[\s\n]+\(LE\)\,[\s\n]+and[\s\n]+counterintelligence[\s\n]+\(CI\)[\s\n]+investigations\.(?:[\n]+|(?:\\n)+)\-At[\s\n]+any[\s\n]+time\,[\s\n]+the[\s\n]+USG[\s\n]+may[\s\n]+inspect[\s\n]+and[\s\n]+seize[\s\n]+data[\s\n]+stored[\s\n]+on[\s\n]+this[\s\n]+IS\.(?:[\n]+|(?:\\n)+)\-Communications[\s\n]+using\,[\s\n]+or[\s\n]+data[\s\n]+stored[\s\n]+on\,[\s\n]+this[\s\n]+IS[\s\n]+are[\s\n]+not[\s\n]+private\,[\s\n]+are[\s\n]+subject[\s\n]+to[\s\n]+routine[\s\n]+monitoring\,[\s\n]+interception\,[\s\n]+and[\s\n]+search\,[\s\n]+and[\s\n]+may[\s\n]+be[\s\n]+disclosed[\s\n]+or[\s\n]+used[\s\n]+for[\s\n]+any[\s\n]+USG\-authorized[\s\n]+purpose\.(?:[\n]+|(?:\\n)+)\-This[\s\n]+IS[\s\n]+includes[\s\n]+security[\s\n]+measures[\s\n]+\(e\.g\.\,[\s\n]+authentication[\s\n]+and[\s\n]+access[\s\n]+controls\)[\s\n]+to[\s\n]+protect[\s\n]+USG[\s\n]+interests\-\-not[\s\n]+for[\s\n]+your[\s\n]+personal[\s\n]+benefit[\s\n]+or[\s\n]+privacy\.(?:[\n]+|(?:\\n)+)\-Notwithstanding[\s\n]+the[\s\n]+above\,[\s\n]+using[\s\n]+this[\s\n]+IS[\s\n]+does[\s\n]+not[\s\n]+constitute[\s\n]+consent[\s\n]+to[\s\n]+PM\,[\s\n]+LE[\s\n]+or[\s\n]+CI[\s\n]+investigative[\s\n]+searching[\s\n]+or[\s\n]+monitoring[\s\n]+of[\s\n]+the[\s\n]+content[\s\n]+of[\s\n]+privileged[\s\n]+communications\,[\s\n]+or[\s\n]+work[\s\n]+product\,[\s\n]+related[\s\n]+to[\s\n]+personal[\s\n]+representation[\s\n]+or[\s\n]+services[\s\n]+by[\s\n]+attorneys\,[\s\n]+psychotherapists\,[\s\n]+or[\s\n]+clergy\,[\s\n]+and[\s\n]+their[\s\n]+assistants\.[\s\n]+Such[\s\n]+communications[\s\n]+and[\s\n]+work[\s\n]+product[\s\n]+are[\s\n]+private[\s\n]+and[\s\n]+confidential\.[\s\n]+See[\s\n]+User[\s\n]+Agreement[\s\n]+for[\s\n]+details\.|I've[\s\n]+read[\s\n]+\&[\s\n]+consent[\s\n]+to[\s\n]+terms[\s\n]+in[\s\n]+IS[\s\n]+user[\s\n]+agreem't\.)$'
# Multiple regexes transform the banner regex into a usable banner
# 0 - Remove anchors around the banner text
login_banner_text=$(echo "$login_banner_text" | sed 's/^\^\(.*\)\$$/\1/g')
# 1 - Keep only the first banners if there are multiple
# (dod_banners contains the long and short banner)
login_banner_text=$(echo "$login_banner_text" | sed 's/^(\(.*\.\)|.*)$/\1/g')
# 2 - Add spaces ' '. (Transforms regex for "space or newline" into a " ")
login_banner_text=$(echo "$login_banner_text" | sed 's/\[\\s\\n\]+/ /g')
# 3 - Adds newlines. (Transforms "(?:\[\\n\]+|(?:\\n)+)" into "\n")
login_banner_text=$(echo "$login_banner_text" | sed 's/(?:\[\\n\]+|(?:\\\\n)+)/\n/g')
# 4 - Remove any leftover backslash. (From any parethesis in the banner, for example).
login_banner_text=$(echo "$login_banner_text" | sed 's/\\//g')
formatted=$(echo "$login_banner_text" | fold -sw 80)
cat <<EOF >/etc/issue.net
$formatted
EOF
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Protect Accounts by Configuring PAM
[ref]groupPAM, 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. |
contains 15 rules |
Set Lockouts for Failed Password Attempts
[ref]groupThe pam_faillock PAM module provides the capability to
lock out user accounts after a number of failed login attempts. Its
documentation is available in
/usr/share/doc/pam-VERSION/txts/README.pam_faillock .
Warning:
Locking out user accounts presents the
risk of a denial-of-service attack. The lockout policy
must weigh whether the risk of such a
denial-of-service attack outweighs the benefits of thwarting
password guessing attacks. |
contains 3 rules |
Limit Password Reuse
[ref]ruleDo not allow users to reuse recent passwords. This can be accomplished by using the
remember option for the pam_unix or pam_pwhistory PAM modules. 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:
Newer versions of authselect contain an authselect feature to easily and properly
enable pam_pwhistory.so module. If this feature is not yet available in your
system, an authselect custom profile must be used to avoid integrity issues in PAM files. Rationale:Preventing re-use of previous passwords helps ensure that a compromised password is not
re-used by a user. References:
BP28(R18), 1, 12, 15, 16, 5, 5.6.2.1.1, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, 3.5.8, CCI-000200, 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, 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)(e), PR.AC-1, PR.AC-6, PR.AC-7, Req-8.2.5, SRG-OS-000077-GPOS-00045, UBTU-20-010070, SRG-OS-000077-VMM-000440 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q installed; then
var_password_pam_unix_remember='5'
if [ -e "/etc/pam.d/common-password" ] ; then
valueRegex="$var_password_pam_unix_remember" defaultValue="$var_password_pam_unix_remember"
# non-empty values need to be preceded by an equals sign
[ -n "${valueRegex}" ] && valueRegex="=${valueRegex}"
# add an equals sign to non-empty values
[ -n "${defaultValue}" ] && defaultValue="=${defaultValue}"
# fix 'type' if it's wrong
if grep -q -P "^\\s*(?"'!'"password\\s)[[:alnum:]]+\\s+[[:alnum:]]+\\s+pam_unix.so" < "/etc/pam.d/common-password" ; then
sed --follow-symlinks -i -E -e "s/^(\\s*)[[:alnum:]]+(\\s+[[:alnum:]]+\\s+pam_unix.so)/\\1password\\2/" "/etc/pam.d/common-password"
fi
# fix 'control' if it's wrong
if grep -q -P "^\\s*password\\s+(?"'!'"[success=1 default=ignore])[[:alnum:]]+\\s+pam_unix.so" < "/etc/pam.d/common-password" ; then
sed --follow-symlinks -i -E -e "s/^(\\s*password\\s+)[[:alnum:]]+(\\s+pam_unix.so)/\\1[success=1 default=ignore]\\2/" "/etc/pam.d/common-password"
fi
# fix the value for 'option' if one exists but does not match 'valueRegex'
if grep -q -P "^\\s*password\\s+[success=1 default=ignore]\\s+pam_unix.so(\\s.+)?\\s+obscure sha512 shadow remember(?"'!'"${valueRegex}(\\s|\$))" < "/etc/pam.d/common-password" ; then
sed --follow-symlinks -i -E -e "s/^(\\s*password\\s+[success=1 default=ignore]\\s+pam_unix.so(\\s.+)?\\s)obscure sha512 shadow remember=[^[:space:]]*/\\1obscure sha512 shadow remember${defaultValue}/" "/etc/pam.d/common-password"
# add 'option=default' if option is not set
elif grep -q -E "^\\s*password\\s+[success=1 default=ignore]\\s+pam_unix.so" < "/etc/pam.d/common-password" &&
grep -E "^\\s*password\\s+[success=1 default=ignore]\\s+pam_unix.so" < "/etc/pam.d/common-password" | grep -q -E -v "\\sobscure sha512 shadow remember(=|\\s|\$)" ; then
sed --follow-symlinks -i -E -e "s/^(\\s*password\\s+[success=1 default=ignore]\\s+pam_unix.so[^\\n]*)/\\1 obscure sha512 shadow remember${defaultValue}/" "/etc/pam.d/common-password"
# add a new entry if none exists
elif ! grep -q -P "^\\s*password\\s+[success=1 default=ignore]\\s+pam_unix.so(\\s.+)?\\s+obscure sha512 shadow remember${valueRegex}(\\s|\$)" < "/etc/pam.d/common-password" ; then
echo "password [success=1 default=ignore] pam_unix.so obscure sha512 shadow remember${defaultValue}" >> "/etc/pam.d/common-password"
fi
else
echo "/etc/pam.d/common-password doesn't exist" >&2
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Enforce Delay After Failed Logon Attempts
[ref]ruleTo configure the system to introduce a delay after failed logon attempts,
add or correct the pam_faildelay settings in
/etc/pam.d/common-auth to make sure its delay parameter
is at least 4000000 or greater. For example:
auth required pam_faildelay.so delay=4000000 Rationale:Limiting the number of logon attempts over a certain time interval reduces
the chances that an unauthorized user may gain access to an account. Remediation Shell script: (show)
var_password_pam_delay='4000000'
if [ -e "/etc/pam.d/common-auth" ] ; then
valueRegex="$var_password_pam_delay" defaultValue="$var_password_pam_delay"
# non-empty values need to be preceded by an equals sign
[ -n "${valueRegex}" ] && valueRegex="=${valueRegex}"
# add an equals sign to non-empty values
[ -n "${defaultValue}" ] && defaultValue="=${defaultValue}"
# fix 'type' if it's wrong
if grep -q -P "^\\s*(?"'!'"auth\\s)[[:alnum:]]+\\s+[[:alnum:]]+\\s+pam_faildelay.so" < "/etc/pam.d/common-auth" ; then
sed --follow-symlinks -i -E -e "s/^(\\s*)[[:alnum:]]+(\\s+[[:alnum:]]+\\s+pam_faildelay.so)/\\1auth\\2/" "/etc/pam.d/common-auth"
fi
# fix 'control' if it's wrong
if grep -q -P "^\\s*auth\\s+(?"'!'"required)[[:alnum:]]+\\s+pam_faildelay.so" < "/etc/pam.d/common-auth" ; then
sed --follow-symlinks -i -E -e "s/^(\\s*auth\\s+)[[:alnum:]]+(\\s+pam_faildelay.so)/\\1required\\2/" "/etc/pam.d/common-auth"
fi
# fix the value for 'option' if one exists but does not match 'valueRegex'
if grep -q -P "^\\s*auth\\s+required\\s+pam_faildelay.so(\\s.+)?\\s+delay(?"'!'"${valueRegex}(\\s|\$))" < "/etc/pam.d/common-auth" ; then
sed --follow-symlinks -i -E -e "s/^(\\s*auth\\s+required\\s+pam_faildelay.so(\\s.+)?\\s)delay=[^[:space:]]*/\\1delay${defaultValue}/" "/etc/pam.d/common-auth"
# add 'option=default' if option is not set
elif grep -q -E "^\\s*auth\\s+required\\s+pam_faildelay.so" < "/etc/pam.d/common-auth" &&
grep -E "^\\s*auth\\s+required\\s+pam_faildelay.so" < "/etc/pam.d/common-auth" | grep -q -E -v "\\sdelay(=|\\s|\$)" ; then
sed --follow-symlinks -i -E -e "s/^(\\s*auth\\s+required\\s+pam_faildelay.so[^\\n]*)/\\1 delay${defaultValue}/" "/etc/pam.d/common-auth"
# add a new entry if none exists
elif ! grep -q -P "^\\s*auth\\s+required\\s+pam_faildelay.so(\\s.+)?\\s+delay${valueRegex}(\\s|\$)" < "/etc/pam.d/common-auth" ; then
echo "auth required pam_faildelay.so delay${defaultValue}" >> "/etc/pam.d/common-auth"
fi
else
echo "/etc/pam.d/common-auth doesn't exist" >&2
fi
|
Set Deny For Failed Password Attempts
[ref]ruleThe Ubuntu 20.04 operating system must lock an account after - at most - 3
consecutive invalid access attempts. Rationale:By limiting the number of failed logon attempts, the risk of unauthorized
system access via user password guessing, otherwise known as brute-force
attacks, is reduced. Limits are imposed by locking the account.
To configure the operating system to lock an account after three
unsuccessful consecutive access attempts using pam_tally2.so ,
modify the content of both /etc/pam.d/common-auth and
/etc/pam.d/common-account as follows:
Remediation Shell script: (show)
var_password_pam_tally2='3'
# Use a non-number regexp to force update of the value of the deny option
if [ -e "/etc/pam.d/common-auth" ] ; then
valueRegex="X" defaultValue="${var_password_pam_tally2}"
# non-empty values need to be preceded by an equals sign
[ -n "${valueRegex}" ] && valueRegex="=${valueRegex}"
# add an equals sign to non-empty values
[ -n "${defaultValue}" ] && defaultValue="=${defaultValue}"
# fix 'type' if it's wrong
if grep -q -P "^\\s*(?"'!'"auth\\s)[[:alnum:]]+\\s+[[:alnum:]]+\\s+pam_tally2.so" < "/etc/pam.d/common-auth" ; then
sed --follow-symlinks -i -E -e "s/^(\\s*)[[:alnum:]]+(\\s+[[:alnum:]]+\\s+pam_tally2.so)/\\1auth\\2/" "/etc/pam.d/common-auth"
fi
# fix 'control' if it's wrong
if grep -q -P "^\\s*auth\\s+(?"'!'"required)[[:alnum:]]+\\s+pam_tally2.so" < "/etc/pam.d/common-auth" ; then
sed --follow-symlinks -i -E -e "s/^(\\s*auth\\s+)[[:alnum:]]+(\\s+pam_tally2.so)/\\1required\\2/" "/etc/pam.d/common-auth"
fi
# fix the value for 'option' if one exists but does not match 'valueRegex'
if grep -q -P "^\\s*auth\\s+required\\s+pam_tally2.so(\\s.+)?\\s+deny(?"'!'"${valueRegex}(\\s|\$))" < "/etc/pam.d/common-auth" ; then
sed --follow-symlinks -i -E -e "s/^(\\s*auth\\s+required\\s+pam_tally2.so(\\s.+)?\\s)deny=[^[:space:]]*/\\1deny${defaultValue}/" "/etc/pam.d/common-auth"
# add 'option=default' if option is not set
elif grep -q -E "^\\s*auth\\s+required\\s+pam_tally2.so" < "/etc/pam.d/common-auth" &&
grep -E "^\\s*auth\\s+required\\s+pam_tally2.so" < "/etc/pam.d/common-auth" | grep -q -E -v "\\sdeny(=|\\s|\$)" ; then
sed --follow-symlinks -i -E -e "s/^(\\s*auth\\s+required\\s+pam_tally2.so[^\\n]*)/\\1 deny${defaultValue}/" "/etc/pam.d/common-auth"
# add a new entry if none exists
elif ! grep -q -P "^\\s*auth\\s+required\\s+pam_tally2.so(\\s.+)?\\s+deny${valueRegex}(\\s|\$)" < "/etc/pam.d/common-auth" ; then
echo "auth required pam_tally2.so deny${defaultValue}" >> "/etc/pam.d/common-auth"
fi
else
echo "/etc/pam.d/common-auth doesn't exist" >&2
fi
if [ -e "/etc/pam.d/common-auth" ] ; then
valueRegex="(fail)" defaultValue="fail"
# non-empty values need to be preceded by an equals sign
[ -n "${valueRegex}" ] && valueRegex="=${valueRegex}"
# add an equals sign to non-empty values
[ -n "${defaultValue}" ] && defaultValue="=${defaultValue}"
# fix 'type' if it's wrong
if grep -q -P "^\\s*(?"'!'"auth\\s)[[:alnum:]]+\\s+[[:alnum:]]+\\s+pam_tally2.so" < "/etc/pam.d/common-auth" ; then
sed --follow-symlinks -i -E -e "s/^(\\s*)[[:alnum:]]+(\\s+[[:alnum:]]+\\s+pam_tally2.so)/\\1auth\\2/" "/etc/pam.d/common-auth"
fi
# fix 'control' if it's wrong
if grep -q -P "^\\s*auth\\s+(?"'!'"required)[[:alnum:]]+\\s+pam_tally2.so" < "/etc/pam.d/common-auth" ; then
sed --follow-symlinks -i -E -e "s/^(\\s*auth\\s+)[[:alnum:]]+(\\s+pam_tally2.so)/\\1required\\2/" "/etc/pam.d/common-auth"
fi
# fix the value for 'option' if one exists but does not match 'valueRegex'
if grep -q -P "^\\s*auth\\s+required\\s+pam_tally2.so(\\s.+)?\\s+onerr(?"'!'"${valueRegex}(\\s|\$))" < "/etc/pam.d/common-auth" ; then
sed --follow-symlinks -i -E -e "s/^(\\s*auth\\s+required\\s+pam_tally2.so(\\s.+)?\\s)onerr=[^[:space:]]*/\\1onerr${defaultValue}/" "/etc/pam.d/common-auth"
# add 'option=default' if option is not set
elif grep -q -E "^\\s*auth\\s+required\\s+pam_tally2.so" < "/etc/pam.d/common-auth" &&
grep -E "^\\s*auth\\s+required\\s+pam_tally2.so" < "/etc/pam.d/common-auth" | grep -q -E -v "\\sonerr(=|\\s|\$)" ; then
sed --follow-symlinks -i -E -e "s/^(\\s*auth\\s+required\\s+pam_tally2.so[^\\n]*)/\\1 onerr${defaultValue}/" "/etc/pam.d/common-auth"
# add a new entry if none exists
elif ! grep -q -P "^\\s*auth\\s+required\\s+pam_tally2.so(\\s.+)?\\s+onerr${valueRegex}(\\s|\$)" < "/etc/pam.d/common-auth" ; then
echo "auth required pam_tally2.so onerr${defaultValue}" >> "/etc/pam.d/common-auth"
fi
else
echo "/etc/pam.d/common-auth doesn't exist" >&2
fi
if [ -e "/etc/pam.d/common-account" ] ; then
valueRegex="" defaultValue=""
# non-empty values need to be preceded by an equals sign
[ -n "${valueRegex}" ] && valueRegex="=${valueRegex}"
# add an equals sign to non-empty values
[ -n "${defaultValue}" ] && defaultValue="=${defaultValue}"
# fix 'type' if it's wrong
if grep -q -P "^\\s*(?"'!'"account\\s)[[:alnum:]]+\\s+[[:alnum:]]+\\s+pam_tally2.so" < "/etc/pam.d/common-account" ; then
sed --follow-symlinks -i -E -e "s/^(\\s*)[[:alnum:]]+(\\s+[[:alnum:]]+\\s+pam_tally2.so)/\\1account\\2/" "/etc/pam.d/common-account"
fi
# fix 'control' if it's wrong
if grep -q -P "^\\s*account\\s+(?"'!'"required)[[:alnum:]]+\\s+pam_tally2.so" < "/etc/pam.d/common-account" ; then
sed --follow-symlinks -i -E -e "s/^(\\s*account\\s+)[[:alnum:]]+(\\s+pam_tally2.so)/\\1required\\2/" "/etc/pam.d/common-account"
fi
# fix the value for 'option' if one exists but does not match 'valueRegex'
if grep -q -P "^\\s*account\\s+required\\s+pam_tally2.so(\\s.+)?\\s+(?"'!'"${valueRegex}(\\s|\$))" < "/etc/pam.d/common-account" ; then
sed --follow-symlinks -i -E -e "s/^(\\s*account\\s+required\\s+pam_tally2.so(\\s.+)?\\s)=[^[:space:]]*/\\1${defaultValue}/" "/etc/pam.d/common-account"
# add 'option=default' if option is not set
elif grep -q -E "^\\s*account\\s+required\\s+pam_tally2.so" < "/etc/pam.d/common-account" &&
grep -E "^\\s*account\\s+required\\s+pam_tally2.so" < "/etc/pam.d/common-account" | grep -q -E -v "\\s(=|\\s|\$)" ; then
sed --follow-symlinks -i -E -e "s/^(\\s*account\\s+required\\s+pam_tally2.so[^\\n]*)/\\1 ${defaultValue}/" "/etc/pam.d/common-account"
# add a new entry if none exists
elif ! grep -q -P "^\\s*account\\s+required\\s+pam_tally2.so(\\s.+)?\\s+${valueRegex}(\\s|\$)" < "/etc/pam.d/common-account" ; then
echo "account required pam_tally2.so ${defaultValue}" >> "/etc/pam.d/common-account"
fi
else
echo "/etc/pam.d/common-account doesn't exist" >&2
fi
|
Set Password Quality Requirements
[ref]groupThe 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. |
contains 9 rules |
Set Password Quality Requirements with pam_pwquality
[ref]groupThe 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. |
contains 9 rules |
Ensure PAM Enforces Password Requirements - Minimum Digit Characters
[ref]ruleThe pam_pwquality module's dcredit parameter controls requirements for
usage of digits in a password. When set to a negative number, any password will be required to
contain that many digits. When set to a positive number, pam_pwquality will grant +1 additional
length credit for each digit. Modify the dcredit setting in
/etc/security/pwquality.conf to require the use of a digit in passwords. Rationale:Use of a complex password helps to increase the time and resources required
to compromise the password. Password complexity, or strength, is a measure of
the effectiveness of a password in resisting attempts at guessing and brute-force
attacks.
Password complexity is one factor of several that determines how long it takes
to crack a password. The more complex the password, the greater the number of
possible combinations that need to be tested before the password is compromised.
Requiring digits makes password guessing attacks more difficult by ensuring a larger
search space. References:
BP28(R18), 5.3.1, 1, 12, 15, 16, 5, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, CCI-000194, 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(c), IA-5(1)(a), CM-6(a), IA-5(4), PR.AC-1, PR.AC-6, PR.AC-7, FMT_SMF_EXT.1, Req-8.2.3, SRG-OS-000071-GPOS-00039, UBTU-20-010052, SRG-OS-000071-VMM-000380 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010052
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.3
- accounts_password_pam_dcredit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: XCCDF Value var_password_pam_dcredit # promote to variable
set_fact:
var_password_pam_dcredit: !!str -1
tags:
- always
- name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters - Ensure
PAM variable dcredit is set accordingly
ansible.builtin.lineinfile:
create: true
dest: /etc/security/pwquality.conf
regexp: ^#?\s*dcredit
line: dcredit = {{ var_password_pam_dcredit }}
when: '"libpam-runtime" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-20-010052
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.3
- accounts_password_pam_dcredit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q installed; then
var_password_pam_dcredit='-1'
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "/etc/security/pwquality.conf"; then
sed_command+=('--follow-symlinks')
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^dcredit")
# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_password_pam_dcredit"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^dcredit\\>" "/etc/security/pwquality.conf"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
"${sed_command[@]}" "s/^dcredit\\>.*/$escaped_formatted_output/gi" "/etc/security/pwquality.conf"
else
# \n is precaution for case where file ends without trailing newline
printf '%s\n' "$formatted_output" >> "/etc/security/pwquality.conf"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure PAM Enforces Password Requirements - Prevent the Use of Dictionary Words
[ref]ruleThe pam_pwquality module's dictcheck check if passwords contains dictionary words. When
dictcheck is set to 1 passwords will be checked for dictionary words. Rationale:Use of a complex password helps to increase the time and resources required to compromise the password.
Password complexity, or strength, is a measure of the effectiveness of a password in resisting attempts at
guessing and brute-force attacks.
Password complexity is one factor of several that determines how long it takes to crack a password. The more
complex the password, the greater the number of possible combinations that need to be tested before the
password is compromised.
Passwords with dictionary words may be more vulnerable to password-guessing attacks. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010056
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_dictcheck
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: XCCDF Value var_password_pam_dictcheck # promote to variable
set_fact:
var_password_pam_dictcheck: !!str 1
tags:
- always
- name: Ensure PAM Enforces Password Requirements - Prevent the Use of Dictionary
Words - Ensure PAM variable dictcheck is set accordingly
ansible.builtin.lineinfile:
create: true
dest: /etc/security/pwquality.conf
regexp: ^#?\s*dictcheck
line: dictcheck = {{ var_password_pam_dictcheck }}
when: '"libpam-runtime" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-20-010056
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_dictcheck
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q installed; then
var_password_pam_dictcheck='1'
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "/etc/security/pwquality.conf"; then
sed_command+=('--follow-symlinks')
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^dictcheck")
# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_password_pam_dictcheck"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^dictcheck\\>" "/etc/security/pwquality.conf"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
"${sed_command[@]}" "s/^dictcheck\\>.*/$escaped_formatted_output/gi" "/etc/security/pwquality.conf"
else
# \n is precaution for case where file ends without trailing newline
printf '%s\n' "$formatted_output" >> "/etc/security/pwquality.conf"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure PAM Enforces Password Requirements - Minimum Different Characters
[ref]ruleThe pam_pwquality module's difok parameter sets the number of characters
in a password that must not be present in and old password during a password change.
Modify the difok setting in /etc/security/pwquality.conf
to equal 8 to require differing characters
when changing passwords. Rationale:Use of a complex password helps to increase the time and resources
required to compromise the password. Password complexity, or strength,
is a measure of the effectiveness of a password in resisting attempts
at guessing and brute–force attacks.
Password complexity is one factor of several that determines how long
it takes to crack a password. The more complex the password, the
greater the number of possible combinations that need to be tested
before the password is compromised.
Requiring a minimum number of different characters during password changes ensures that
newly changed passwords should not resemble previously compromised ones.
Note that passwords which are changed on compromised systems will still be compromised, however. References:
1, 12, 15, 16, 5, 5.6.2.1.1, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, CCI-000195, 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, 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(c), IA-5(1)(b), CM-6(a), IA-5(4), PR.AC-1, PR.AC-6, PR.AC-7, SRG-OS-000072-GPOS-00040, UBTU-20-010053, SRG-OS-000072-VMM-000390 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.6.2.1.1
- DISA-STIG-UBTU-20-010053
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(b)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_difok
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: XCCDF Value var_password_pam_difok # promote to variable
set_fact:
var_password_pam_difok: !!str 8
tags:
- always
- name: Ensure PAM Enforces Password Requirements - Minimum Different Characters -
Ensure PAM variable difok is set accordingly
ansible.builtin.lineinfile:
create: true
dest: /etc/security/pwquality.conf
regexp: ^#?\s*difok
line: difok = {{ var_password_pam_difok }}
when: '"libpam-runtime" in ansible_facts.packages'
tags:
- CJIS-5.6.2.1.1
- DISA-STIG-UBTU-20-010053
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(b)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_difok
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q installed; then
var_password_pam_difok='8'
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "/etc/security/pwquality.conf"; then
sed_command+=('--follow-symlinks')
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^difok")
# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_password_pam_difok"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^difok\\>" "/etc/security/pwquality.conf"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
"${sed_command[@]}" "s/^difok\\>.*/$escaped_formatted_output/gi" "/etc/security/pwquality.conf"
else
# \n is precaution for case where file ends without trailing newline
printf '%s\n' "$formatted_output" >> "/etc/security/pwquality.conf"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure PAM Enforces Password Requirements - Enforcing
[ref]ruleVerify that the operating system uses "pwquality" to enforce the
password complexity rules.
Verify the pwquality module is being enforced by operating system by
running the following command:
$ grep -i enforcing /etc/security/pwquality.conf
enforcing = 1
If the value of "enforcing" is not "1" or the line is commented out,
this is a finding.Rationale:Use of a complex password helps to increase the time and resources
required to compromise the password. Password complexity, or strength,
is a measure of the effectiveness of a password in resisting attempts at
guessing and brute-force attacks. Using enforcing=1 ensures "pwquality"
enforces complex password construction configuration and has the ability
to limit brute-force attacks on the system. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010057
- accounts_password_pam_enforcing
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Ensure PAM Enforces Password Requirements - Enforcing
lineinfile:
path: /etc/security/pwquality.conf
create: true
line: enforcing = 1
state: present
when: '"libpam-runtime" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-20-010057
- accounts_password_pam_enforcing
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q installed; then
if [ -e "/etc/security/pwquality.conf" ] ; then
LC_ALL=C sed -i "/^\s*enforcing = 1/Id" "/etc/security/pwquality.conf"
else
touch "/etc/security/pwquality.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/security/pwquality.conf"
cp "/etc/security/pwquality.conf" "/etc/security/pwquality.conf.bak"
# Insert at the end of the file
printf '%s\n' "enforcing = 1" >> "/etc/security/pwquality.conf"
# Clean up after ourselves.
rm "/etc/security/pwquality.conf.bak"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
[ref]ruleThe pam_pwquality module's lcredit parameter controls requirements for
usage of lowercase letters in a password. When set to a negative number, any password will be required to
contain that many lowercase characters. When set to a positive number, pam_pwquality will grant +1 additional
length credit for each lowercase character. Modify the lcredit setting in
/etc/security/pwquality.conf to require the use of a lowercase character in passwords. Rationale:Use of a complex password helps to increase the time and resources required
to compromise the password. Password complexity, or strength, is a measure of
the effectiveness of a password in resisting attempts at guessing and brute-force
attacks.
Password complexity is one factor of several that determines how long it takes
to crack a password. The more complex the password, the greater the number of
possble combinations that need to be tested before the password is compromised.
Requiring a minimum number of lowercase characters makes password guessing attacks
more difficult by ensuring a larger search space. References:
BP28(R18), 5.3.1, 1, 12, 15, 16, 5, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, CCI-000193, 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(c), IA-5(1)(a), CM-6(a), IA-5(4), PR.AC-1, PR.AC-6, PR.AC-7, FMT_SMF_EXT.1, Req-8.2.3, SRG-OS-000070-GPOS-00038, UBTU-20-010051, SRG-OS-000070-VMM-000370 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010051
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.3
- accounts_password_pam_lcredit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: XCCDF Value var_password_pam_lcredit # promote to variable
set_fact:
var_password_pam_lcredit: !!str -1
tags:
- always
- name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters -
Ensure PAM variable lcredit is set accordingly
ansible.builtin.lineinfile:
create: true
dest: /etc/security/pwquality.conf
regexp: ^#?\s*lcredit
line: lcredit = {{ var_password_pam_lcredit }}
when: '"libpam-runtime" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-20-010051
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.3
- accounts_password_pam_lcredit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q installed; then
var_password_pam_lcredit='-1'
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "/etc/security/pwquality.conf"; then
sed_command+=('--follow-symlinks')
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^lcredit")
# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_password_pam_lcredit"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^lcredit\\>" "/etc/security/pwquality.conf"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
"${sed_command[@]}" "s/^lcredit\\>.*/$escaped_formatted_output/gi" "/etc/security/pwquality.conf"
else
# \n is precaution for case where file ends without trailing newline
printf '%s\n' "$formatted_output" >> "/etc/security/pwquality.conf"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure PAM Enforces Password Requirements - Minimum Length
[ref]ruleThe pam_pwquality module's minlen parameter controls requirements for
minimum characters required in a password. Add minlen=15
after pam_pwquality to set minimum password length requirements. Rationale:The shorter the password, the lower the number of possible combinations
that need to be tested before the password is compromised.
Password complexity, or strength, is a measure of the effectiveness of a
password in resisting attempts at guessing and brute-force attacks.
Password length is one factor of several that helps to determine strength
and how long it takes to crack a password. Use of more characters in a password
helps to exponentially increase the time and/or resources required to
compromise the password. References:
BP28(R18), 5.3.1, 1, 12, 15, 16, 5, 5.6.2.1.1, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, 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(c), IA-5(1)(a), CM-6(a), IA-5(4), PR.AC-1, PR.AC-6, PR.AC-7, FMT_SMF_EXT.1, Req-8.2.3, SRG-OS-000078-GPOS-00046, UBTU-20-010054, SRG-OS-000072-VMM-000390, SRG-OS-000078-VMM-000450 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.6.2.1.1
- DISA-STIG-UBTU-20-010054
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.3
- accounts_password_pam_minlen
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: XCCDF Value var_password_pam_minlen # promote to variable
set_fact:
var_password_pam_minlen: !!str 15
tags:
- always
- name: Ensure PAM Enforces Password Requirements - Minimum Length - Ensure PAM variable
minlen is set accordingly
ansible.builtin.lineinfile:
create: true
dest: /etc/security/pwquality.conf
regexp: ^#?\s*minlen
line: minlen = {{ var_password_pam_minlen }}
when: '"libpam-runtime" in ansible_facts.packages'
tags:
- CJIS-5.6.2.1.1
- DISA-STIG-UBTU-20-010054
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.3
- accounts_password_pam_minlen
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q installed; then
var_password_pam_minlen='15'
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "/etc/security/pwquality.conf"; then
sed_command+=('--follow-symlinks')
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^minlen")
# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_password_pam_minlen"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^minlen\\>" "/etc/security/pwquality.conf"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
"${sed_command[@]}" "s/^minlen\\>.*/$escaped_formatted_output/gi" "/etc/security/pwquality.conf"
else
# \n is precaution for case where file ends without trailing newline
printf '%s\n' "$formatted_output" >> "/etc/security/pwquality.conf"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure PAM Enforces Password Requirements - Minimum Special Characters
[ref]ruleThe pam_pwquality module's ocredit= parameter controls requirements for
usage of special (or "other") characters in a password. When set to a negative number,
any password will be required to contain that many special characters.
When set to a positive number, pam_pwquality will grant +1
additional length credit for each special character. Modify the ocredit setting
in /etc/security/pwquality.conf to equal -1
to require use of a special character in passwords. Rationale:Use of a complex password helps to increase the time and resources required
to compromise the password. Password complexity, or strength, is a measure of
the effectiveness of a password in resisting attempts at guessing and brute-force
attacks.
Password complexity is one factor of several that determines how long it takes
to crack a password. The more complex the password, the greater the number of
possible combinations that need to be tested before the password is compromised.
Requiring a minimum number of special characters makes password guessing attacks
more difficult by ensuring a larger search space. References:
BP28(R18), 5.3.1, 1, 12, 15, 16, 5, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, CCI-001619, 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(c), IA-5(1)(a), CM-6(a), IA-5(4), PR.AC-1, PR.AC-6, PR.AC-7, FMT_SMF_EXT.1, SRG-OS-000266-GPOS-00101, UBTU-20-010055, SRG-OS-000266-VMM-000940 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010055
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_ocredit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: XCCDF Value var_password_pam_ocredit # promote to variable
set_fact:
var_password_pam_ocredit: !!str -1
tags:
- always
- name: Ensure PAM Enforces Password Requirements - Minimum Special Characters - Ensure
PAM variable ocredit is set accordingly
ansible.builtin.lineinfile:
create: true
dest: /etc/security/pwquality.conf
regexp: ^#?\s*ocredit
line: ocredit = {{ var_password_pam_ocredit }}
when: '"libpam-runtime" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-20-010055
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_ocredit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q installed; then
var_password_pam_ocredit='-1'
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "/etc/security/pwquality.conf"; then
sed_command+=('--follow-symlinks')
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^ocredit")
# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_password_pam_ocredit"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^ocredit\\>" "/etc/security/pwquality.conf"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
"${sed_command[@]}" "s/^ocredit\\>.*/$escaped_formatted_output/gi" "/etc/security/pwquality.conf"
else
# \n is precaution for case where file ends without trailing newline
printf '%s\n' "$formatted_output" >> "/etc/security/pwquality.conf"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure PAM Enforces Password Requirements - Authentication Retry Prompts Permitted Per-Session
[ref]ruleTo configure the number of retry prompts that are permitted per-session:
Edit the pam_pwquality.so statement in
/etc/pam.d/common-password 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. References:
5.3.1, 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, UBTU-20-010057 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q installed; then
var_password_pam_retry='3'
if [ -e "/etc/pam.d/common-password" ] ; then
valueRegex="$var_password_pam_retry" defaultValue="$var_password_pam_retry"
# non-empty values need to be preceded by an equals sign
[ -n "${valueRegex}" ] && valueRegex="=${valueRegex}"
# add an equals sign to non-empty values
[ -n "${defaultValue}" ] && defaultValue="=${defaultValue}"
# fix 'type' if it's wrong
if grep -q -P "^\\s*(?"'!'"password\\s)[[:alnum:]]+\\s+[[:alnum:]]+\\s+pam_pwquality.so" < "/etc/pam.d/common-password" ; then
sed --follow-symlinks -i -E -e "s/^(\\s*)[[:alnum:]]+(\\s+[[:alnum:]]+\\s+pam_pwquality.so)/\\1password\\2/" "/etc/pam.d/common-password"
fi
# fix 'control' if it's wrong
if grep -q -P "^\\s*password\\s+(?"'!'"requisite)[[:alnum:]]+\\s+pam_pwquality.so" < "/etc/pam.d/common-password" ; then
sed --follow-symlinks -i -E -e "s/^(\\s*password\\s+)[[:alnum:]]+(\\s+pam_pwquality.so)/\\1requisite\\2/" "/etc/pam.d/common-password"
fi
# fix the value for 'option' if one exists but does not match 'valueRegex'
if grep -q -P "^\\s*password\\s+requisite\\s+pam_pwquality.so(\\s.+)?\\s+retry(?"'!'"${valueRegex}(\\s|\$))" < "/etc/pam.d/common-password" ; then
sed --follow-symlinks -i -E -e "s/^(\\s*password\\s+requisite\\s+pam_pwquality.so(\\s.+)?\\s)retry=[^[:space:]]*/\\1retry${defaultValue}/" "/etc/pam.d/common-password"
# add 'option=default' if option is not set
elif grep -q -E "^\\s*password\\s+requisite\\s+pam_pwquality.so" < "/etc/pam.d/common-password" &&
grep -E "^\\s*password\\s+requisite\\s+pam_pwquality.so" < "/etc/pam.d/common-password" | grep -q -E -v "\\sretry(=|\\s|\$)" ; then
sed --follow-symlinks -i -E -e "s/^(\\s*password\\s+requisite\\s+pam_pwquality.so[^\\n]*)/\\1 retry${defaultValue}/" "/etc/pam.d/common-password"
# add a new entry if none exists
elif ! grep -q -P "^\\s*password\\s+requisite\\s+pam_pwquality.so(\\s.+)?\\s+retry${valueRegex}(\\s|\$)" < "/etc/pam.d/common-password" ; then
echo "password requisite pam_pwquality.so retry${defaultValue}" >> "/etc/pam.d/common-password"
fi
else
echo "/etc/pam.d/common-password doesn't exist" >&2
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
[ref]ruleThe pam_pwquality module's ucredit= parameter controls requirements for
usage of uppercase letters in a password. When set to a negative number, any password will be required to
contain that many uppercase characters. When set to a positive number, pam_pwquality will grant +1 additional
length credit for each uppercase character. Modify the ucredit setting in
/etc/security/pwquality.conf to require the use of an uppercase character in passwords. Rationale:Use of a complex password helps to increase the time and resources required to compromise the password.
Password complexity, or strength, is a measure of the effectiveness of a password in resisting attempts
at guessing and brute-force attacks.
Password complexity is one factor of several that determines how long it takes to crack a password. The more
complex the password, the greater the number of possible combinations that need to be tested before
the password is compromised. References:
BP28(R18), 5.3.1, 1, 12, 15, 16, 5, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, CCI-000192, CCI-000193, 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(c), IA-5(1)(a), CM-6(a), IA-5(4), PR.AC-1, PR.AC-6, PR.AC-7, FMT_SMF_EXT.1, Req-8.2.3, SRG-OS-000069-GPOS-00037, SRG-OS-000070-GPOS-00038, UBTU-20-010050, SRG-OS-000069-VMM-000360 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010050
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.3
- accounts_password_pam_ucredit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: XCCDF Value var_password_pam_ucredit # promote to variable
set_fact:
var_password_pam_ucredit: !!str -1
tags:
- always
- name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters -
Ensure PAM variable ucredit is set accordingly
ansible.builtin.lineinfile:
create: true
dest: /etc/security/pwquality.conf
regexp: ^#?\s*ucredit
line: ucredit = {{ var_password_pam_ucredit }}
when: '"libpam-runtime" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-20-010050
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.3
- accounts_password_pam_ucredit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q installed; then
var_password_pam_ucredit='-1'
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "/etc/security/pwquality.conf"; then
sed_command+=('--follow-symlinks')
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^ucredit")
# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_password_pam_ucredit"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^ucredit\\>" "/etc/security/pwquality.conf"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
"${sed_command[@]}" "s/^ucredit\\>.*/$escaped_formatted_output/gi" "/etc/security/pwquality.conf"
else
# \n is precaution for case where file ends without trailing newline
printf '%s\n' "$formatted_output" >> "/etc/security/pwquality.conf"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Set Password Hashing Algorithm
[ref]groupThe system's default algorithm for storing password hashes in
/etc/shadow is SHA-512. This can be configured in several
locations. |
contains 1 rule |
Set Password Hashing Algorithm in /etc/login.defs
[ref]ruleIn /etc/login.defs , add or correct the following line to ensure
the system will use SHA-512 as the hashing algorithm:
ENCRYPT_METHOD SHA512 Rationale:Passwords need to be protected at all times, and encryption is the standard method for protecting passwords.
If passwords are not encrypted, they can be plainly read (i.e., clear text) and easily compromised. Passwords
that are encrypted with a weak algorithm are no more protected than if they are kept in plain text.
Using a stronger hashing algorithm makes password cracking attacks more difficult. References:
BP28(R32), 1, 12, 15, 16, 5, 5.6.2.2, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, 3.13.11, CCI-000196, 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, 0418, 1055, 1402, 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(c), IA-5(1)(c), CM-6(a), PR.AC-1, PR.AC-6, PR.AC-7, Req-8.2.1, SRG-OS-000073-GPOS-00041, UBTU-20-010404 |
Install pam_pwquality Package
[ref]rule
The libpam-pwquality package can be installed with the following command:
$ apt-get install libpam-pwquality Rationale:Use of a complex password helps to increase the time and resources required
to compromise the password. Password complexity, or strength, is a measure
of the effectiveness of a password in resisting attempts at guessing and
brute-force attacks. "pwquality" enforces complex password construction
configuration and has the ability to limit brute-force attacks on the system. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Ensure libpam-pwquality is installed
package:
name: libpam-pwquality
state: present
tags:
- DISA-STIG-UBTU-20-010057
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- package_pam_pwquality_installed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
DEBIAN_FRONTEND=noninteractive apt-get install -y "libpam-pwquality"
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include install_libpam-pwquality
class install_libpam-pwquality {
package { 'libpam-pwquality':
ensure => 'installed',
}
}
Remediation script: (show)
[[packages]]
name = "libpam-pwquality"
version = "*"
|
Ensure PAM Displays Last Logon/Access Notification
[ref]ruleTo 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 read as follows:
session required pam_lastlog.so showfailed
And make sure that the silent option is not set for
pam_lastlog module.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. 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, SRG-OS-000480-GPOS-00227, UBTU-20-010453 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q installed; then
if [ -e "/etc/pam.d/login" ] ; then
PAM_FILE_PATH="/etc/pam.d/login"
if [ -f /usr/bin/authselect ]; then
if ! authselect check; then
echo "
authselect integrity check failed. Remediation aborted!
This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
It is not recommended to manually edit the PAM files when authselect tool is available.
In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
exit 1
fi
CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
# If not already in use, a custom profile is created preserving the enabled features.
if [[ ! $CURRENT_PROFILE == custom/* ]]; then
ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
authselect create-profile hardening -b $CURRENT_PROFILE
CURRENT_PROFILE="custom/hardening"
authselect apply-changes -b --backup=before-hardening-custom-profile
authselect select $CURRENT_PROFILE
for feature in $ENABLED_FEATURES; do
authselect enable-feature $feature;
done
authselect apply-changes -b --backup=after-hardening-custom-profile
fi
PAM_FILE_NAME=$(basename "/etc/pam.d/login")
PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"
authselect apply-changes -b
fi
if ! grep -qP '^\s*session\s+'"required"'\s+pam_lastlog.so\s*.*' "$PAM_FILE_PATH"; then
# Line matching group + control + module was not found. Check group + module.
if [ "$(grep -cP '^\s*session\s+.*\s+pam_lastlog.so\s*' "$PAM_FILE_PATH")" -eq 1 ]; then
# The control is updated only if one single line matches.
sed -i -E --follow-symlinks 's/^(\s*session\s+).*(\bpam_lastlog.so.*)/\1'"required"' \2/' "$PAM_FILE_PATH"
else
sed -i --follow-symlinks '1i session '"required"' pam_lastlog.so' "$PAM_FILE_PATH"
fi
fi
# Check the option
if ! grep -qP '^\s*session\s+'"required"'\s+pam_lastlog.so\s*.*\sshowfailed\b' "$PAM_FILE_PATH"; then
sed -i -E --follow-symlinks '/\s*session\s+'"required"'\s+pam_lastlog.so.*/ s/$/ showfailed/' "$PAM_FILE_PATH"
fi
if [ -f /usr/bin/authselect ]; then
authselect apply-changes -b
fi
else
echo "/etc/pam.d/login was not found" >&2
fi
if [ -e "/etc/pam.d/login" ] ; then
PAM_FILE_PATH="/etc/pam.d/login"
if [ -f /usr/bin/authselect ]; then
if ! authselect check; then
echo "
authselect integrity check failed. Remediation aborted!
This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
It is not recommended to manually edit the PAM files when authselect tool is available.
In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
exit 1
fi
CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
# If not already in use, a custom profile is created preserving the enabled features.
if [[ ! $CURRENT_PROFILE == custom/* ]]; then
ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
authselect create-profile hardening -b $CURRENT_PROFILE
CURRENT_PROFILE="custom/hardening"
authselect apply-changes -b --backup=before-hardening-custom-profile
authselect select $CURRENT_PROFILE
for feature in $ENABLED_FEATURES; do
authselect enable-feature $feature;
done
authselect apply-changes -b --backup=after-hardening-custom-profile
fi
PAM_FILE_NAME=$(basename "/etc/pam.d/login")
PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"
authselect apply-changes -b
fi
if grep -qP '^\s*session\s.*\bpam_lastlog.so\s.*\bsilent\b' "$PAM_FILE_PATH"; then
sed -i -E --follow-symlinks 's/(.*session.*pam_lastlog.so.*)\bsilent\b=?[[:alnum:]]*(.*)/\1\2/g' "$PAM_FILE_PATH"
fi
if [ -f /usr/bin/authselect ]; then
authselect apply-changes -b
fi
else
echo "/etc/pam.d/login was not found" >&2
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Protect Physical Console Access
[ref]groupIt is impossible to fully protect a system from an
attacker with physical access, so securing the space in which the
system is located should be considered a necessary step. However,
there are some steps which, if taken, make it more difficult for an
attacker to quickly or undetectably modify a system from its
console. |
contains 10 rules |
Configure Screen Locking
[ref]groupWhen a user must temporarily leave an account
logged-in, screen locking should be employed to prevent passersby
from abusing the account. User education and training is
particularly important for screen locking to be effective, and policies
can be implemented to reinforce this.
Automatic screen locking is only meant as a safeguard for
those cases where a user forgot to lock the screen. |
contains 8 rules |
Configure Console Screen Locking
[ref]groupA console screen locking mechanism is a temporary action taken when a user
stops work and moves away from the immediate physical vicinity of the
information system but does not logout because of the temporary nature of
the absence. Rather than relying on the user to manually lock their
operation system session prior to vacating the vicinity, operating systems
need to be able to identify when a user's session has idled and take action
to initiate the session lock. |
contains 1 rule |
Check that vlock is installed to allow session locking
[ref]ruleThe Ubuntu 20.04 operating system must have vlock installed to allow for session locking.
The vlock package can be installed with the following command:
$ apt-get install vlock Rationale:A session lock is a temporary action taken when a user stops work and
moves away from the immediate physical vicinity of the information
system but does not want to log out because of the temporary nature of
the absence.
The session lock is implemented at the point where session activity can
be determined.
Regardless of where the session lock is determined and implemented,
once invoked, the session lock must remain in place until the user
reauthenticates. No other activity aside from reauthentication must
unlock the system. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Ensure vlock is installed
package:
name: vlock
state: present
tags:
- DISA-STIG-UBTU-20-010005
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- vlock_installed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
DEBIAN_FRONTEND=noninteractive apt-get install -y "vlock"
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include install_vlock
class install_vlock {
package { 'vlock':
ensure => 'installed',
}
}
Remediation script: (show)
[[packages]]
name = "vlock"
version = "*"
|
Hardware Tokens for Authentication
[ref]groupThe use of hardware tokens such as smart cards for system login
provides stronger, two-factor authentication than using a username and password.
In Red Hat Enterprise Linux servers and workstations, hardware token login
is not enabled by default and must be enabled in the system settings. |
contains 7 rules |
Install the opensc Package For Multifactor Authentication
[ref]rule
The opensc-pkcs11 package can be installed with the following command:
$ apt-get install opensc-pkcs11 Rationale:Using an authentication device, such as a CAC or token that is separate from
the information system, ensures that even if the information system is
compromised, that compromise will not affect credentials stored on the
authentication device.
Multifactor solutions that require devices separate from
information systems gaining access include, for example, hardware tokens
providing time-based or challenge-response authenticators and smart cards such
as the U.S. Government Personal Identity Verification card and the DoD Common
Access Card. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Ensure opensc-pkcs11 is installed
package:
name: opensc-pkcs11
state: present
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010064
- NIST-800-53-CM-6(a)
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- package_opensc_installed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
DEBIAN_FRONTEND=noninteractive apt-get install -y "opensc-pkcs11"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include install_opensc-pkcs11
class install_opensc-pkcs11 {
package { 'opensc-pkcs11':
ensure => 'installed',
}
}
Remediation script: (show)
[[packages]]
name = "opensc-pkcs11"
version = "*"
|
Install Smart Card Packages For Multifactor Authentication
[ref]ruleConfigure the operating system to implement multifactor authentication by
installing the required package with the following command:
The libpam-pkcs11 package can be installed with the following command:
$ apt-get install libpam-pkcs11 Rationale:Using an authentication device, such as a CAC or token that is separate from
the information system, ensures that even if the information system is
compromised, that compromise will not affect credentials stored on the
authentication device.
Multifactor solutions that require devices separate from
information systems gaining access include, for example, hardware tokens
providing time-based or challenge-response authenticators and smart cards such
as the U.S. Government Personal Identity Verification card and the DoD Common
Access Card. References:
CCI-000765, CCI-001948, CCI-001953, CCI-001954, CM-6(a), Req-8.3, SRG-OS-000105-GPOS-00052, SRG-OS-000375-GPOS-00160, SRG-OS-000375-GPOS-00161, SRG-OS-000377-GPOS-00162, UBTU-20-010063 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Ensure libpam-pkcs11 is installed
package:
name: libpam-pkcs11
state: present
when:
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- ansible_architecture != "s390x"
tags:
- DISA-STIG-UBTU-20-010063
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-8.3
- enable_strategy
- install_smartcard_packages
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && { ! grep -q s390x /proc/sys/kernel/osrelease; }; then
DEBIAN_FRONTEND=noninteractive apt-get install -y "libpam-pkcs11"
if [ ! -f /etc/pam_pkcs11/pam_pkcs11.conf ]; then
cp /usr/share/doc/libpam-pkcs11/examples/pam_pkcs11.conf.example /etc/pam_pkcs11/pam_pkcs11.conf
fi
sed -i -e 's/debug = true/debug = false/g' -e 's|module = /usr/lib/opensc-pkcs11|module = /usr/lib/x86_64-linux-gnu/pkcs11/opensc-pkcs11|' /etc/pam_pkcs11/pam_pkcs11.conf
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include install_libpam-pkcs11
class install_libpam-pkcs11 {
package { 'libpam-pkcs11':
ensure => 'installed',
}
}
Remediation script: (show)
[[packages]]
name = "libpam-pkcs11"
version = "*"
|
Configure Smart Card Certificate Authority Validation
[ref]ruleConfigure the operating system to do certificate status checking for PKI
authentication. Modify all of the cert_policy lines in
/etc/pam_pkcs11/pam_pkcs11.conf to include ca like so:
cert_policy = ca, ocsp_on, signature; Rationale:Using an authentication device, such as a CAC or token that is separate from
the information system, ensures that even if the information system is
compromised, that compromise will not affect credentials stored on the
authentication device.
Multifactor solutions that require devices separate from
information systems gaining access include, for example, hardware tokens
providing time-based or challenge-response authenticators and smart cards such
as the U.S. Government Personal Identity Verification card and the DoD Common
Access Card. Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
if [ ! -f /etc/pam_pkcs11/pam_pkcs11.conf ]; then
cp /usr/share/doc/libpam-pkcs11/examples/pam_pkcs11.conf.example /etc/pam_pkcs11/pam_pkcs11.conf
fi
if grep -v "^\s*\#+cert_policy" /etc/pam_pkcs11/pam_pkcs11.conf | grep -qv "ca"; then
sed -i "s/\(^[[:blank:]]*\)\(\(\#*[[:blank:]]*cert_policy[[:blank:]]*=[[:blank:]]*.*;\)[^ $]*\)/\1cert_policy = ca,signature,ocsp_on;/" /etc/pam_pkcs11/pam_pkcs11.conf
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Configure Smart Card Certificate Status Checking
[ref]ruleConfigure the operating system to do certificate status checking for PKI
authentication. Modify all of the cert_policy lines in
/etc/pam_pkcs11/pam_pkcs11.conf to include ocsp_on like so:
cert_policy = ca, ocsp_on, signature; Rationale:Using an authentication device, such as a CAC or token that is separate from
the information system, ensures that even if the information system is
compromised, that compromise will not affect credentials stored on the
authentication device.
Multifactor solutions that require devices separate from
information systems gaining access include, for example, hardware tokens
providing time-based or challenge-response authenticators and smart cards such
as the U.S. Government Personal Identity Verification card and the DoD Common
Access Card. Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && { ! grep -q s390x /proc/sys/kernel/osrelease; }; then
if [ ! -f /etc/pam_pkcs11/pam_pkcs11.conf ]; then
cp /usr/share/doc/libpam-pkcs11/examples/pam_pkcs11.conf.example /etc/pam_pkcs11/pam_pkcs11.conf
fi
if grep -v "^\s*\#+cert_policy" /etc/pam_pkcs11/pam_pkcs11.conf | grep -qv "oscp_on"; then
sed -i "s/\(^[[:blank:]]*\)\(\(\#*[[:blank:]]*cert_policy[[:blank:]]*=[[:blank:]]*.*;\)[^ $]*\)/\1cert_policy = ca,signature,ocsp_on;/" /etc/pam_pkcs11/pam_pkcs11.conf
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Configure Smart Card Local Cache of Revocation Data
[ref]ruleConfigure the operating system for PKI-based authentication to use
local revocation data when unable to access the network to obtain it
remotely. Modify all of the cert_policy lines in
/etc/pam_pkcs11/pam_pkcs11.conf to include crl_auto
or crl_offline like so:
cert_policy = ca,signature,ocsp_on,crl_auto; Rationale:Without configuring a local cache of revocation data, there is the
potential to allow access to users who are no longer authorized
(users with revoked certificates). Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
if [ ! -f /etc/pam_pkcs11/pam_pkcs11.conf ]; then
cp /usr/share/doc/libpam-pkcs11/examples/pam_pkcs11.conf.example /etc/pam_pkcs11/pam_pkcs11.conf
fi
if grep -v "^\s*\#+cert_policy" /etc/pam_pkcs11/pam_pkcs11.conf | grep -Eqv 'crl_auto|crl_offline'; then
sed -i "s/\(^[[:blank:]]*\)\(\(\#*[[:blank:]]*cert_policy[[:blank:]]*=[[:blank:]]*.*;\)[^ $]*\)/\1cert_policy = ca,signature,ocsp_on,crl_auto;/" /etc/pam_pkcs11/pam_pkcs11.conf
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Enable Smart Card Logins in PAM
[ref]ruleThis requirement only applies to components where this is specific to the
function of the device or has the concept of an organizational user (e.g.,
VPN, proxy capability). This does not apply to authentication for the
purpose of configuring the device itself (management).
Check that the pam_pkcs11.so option is configured in the
etc/pam.d/common-auth file with the following command:
# grep pam_pkcs11.so /etc/pam.d/common-auth
auth sufficient pam_pkcs11.so
For general information about enabling smart card authentication, consult
the documentation at:
Rationale:Smart card login provides two-factor authentication stronger than
that provided by a username and password combination. Smart cards leverage PKI
(public key infrastructure) in order to provide and verify credentials.
Using an authentication device, such as a CAC or token that is separate
from the information system, ensures that even if the information system is
compromised, that compromise will not affect credentials stored on the
authentication device.
Multifactor solutions that require devices separate from information
systems gaining access include, for example, hardware tokens providing
time-based or challenge-response authenticators and smart cards such as the
U.S. Government Personal Identity Verification card and the DoD Common
Access Card. References:
CCI-000765, CCI-000766, CCI-000767, CCI-000768, CCI-000187, CCI-001948, CCI-001953, CCI-001954, SRG-OS-000068-GPOS-00036, SRG-OS-000105-GPOS-00052, SRG-OS-000106-GPOS-00053, SRG-OS-000107-GPOS-00054, SRG-OS-000108-GPOS-00055, SRG-OS-000375-GPOS-00160, SRG-OS-000375-GPOS-00161, SRG-OS-000375-GPOS-00162, UBTU-20-010033 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
if [ -e "/etc/pam.d/common-auth" ] ; then
valueRegex="" defaultValue=""
# non-empty values need to be preceded by an equals sign
[ -n "${valueRegex}" ] && valueRegex="=${valueRegex}"
# add an equals sign to non-empty values
[ -n "${defaultValue}" ] && defaultValue="=${defaultValue}"
# fix 'type' if it's wrong
if grep -q -P "^\\s*(?"'!'"auth\\s)[[:alnum:]]+\\s+[[:alnum:]]+\\s+pam_pkcs11.so" < "/etc/pam.d/common-auth" ; then
sed --follow-symlinks -i -E -e "s/^(\\s*)[[:alnum:]]+(\\s+[[:alnum:]]+\\s+pam_pkcs11.so)/\\1auth\\2/" "/etc/pam.d/common-auth"
fi
# fix 'control' if it's wrong
if grep -q -P "^\\s*auth\\s+(?"'!'"[success=2 default=ignore])[[:alnum:]]+\\s+pam_pkcs11.so" < "/etc/pam.d/common-auth" ; then
sed --follow-symlinks -i -E -e "s/^(\\s*auth\\s+)[[:alnum:]]+(\\s+pam_pkcs11.so)/\\1[success=2 default=ignore]\\2/" "/etc/pam.d/common-auth"
fi
# fix the value for 'option' if one exists but does not match 'valueRegex'
if grep -q -P "^\\s*auth\\s+[success=2 default=ignore]\\s+pam_pkcs11.so(\\s.+)?\\s+(?"'!'"${valueRegex}(\\s|\$))" < "/etc/pam.d/common-auth" ; then
sed --follow-symlinks -i -E -e "s/^(\\s*auth\\s+[success=2 default=ignore]\\s+pam_pkcs11.so(\\s.+)?\\s)=[^[:space:]]*/\\1${defaultValue}/" "/etc/pam.d/common-auth"
# add 'option=default' if option is not set
elif grep -q -E "^\\s*auth\\s+[success=2 default=ignore]\\s+pam_pkcs11.so" < "/etc/pam.d/common-auth" &&
grep -E "^\\s*auth\\s+[success=2 default=ignore]\\s+pam_pkcs11.so" < "/etc/pam.d/common-auth" | grep -q -E -v "\\s(=|\\s|\$)" ; then
sed --follow-symlinks -i -E -e "s/^(\\s*auth\\s+[success=2 default=ignore]\\s+pam_pkcs11.so[^\\n]*)/\\1 ${defaultValue}/" "/etc/pam.d/common-auth"
# add a new entry if none exists
elif ! grep -q -P "^\\s*auth\\s+[success=2 default=ignore]\\s+pam_pkcs11.so(\\s.+)?\\s+${valueRegex}(\\s|\$)" < "/etc/pam.d/common-auth" ; then
echo "auth [success=2 default=ignore] pam_pkcs11.so ${defaultValue}" >> "/etc/pam.d/common-auth"
fi
else
echo "/etc/pam.d/common-auth doesn't exist" >&2
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Verify that 'use_mappers' is set to 'pwent' in PAM
[ref]ruleThe operating system must map the authenticated identity to the user or
group account for PKI-based authentication.
Verify that use_mappers is set to pwent in
/etc/pam_pkcs11/pam_pkcs11.conf file with the following command:
$ grep ^use_mappers /etc/pam_pkcs11/pam_pkcs11.conf
use_mappers = pwent Rationale:Without mapping the certificate used to authenticate to the user account,
the ability to determine the identity of the individual user or group will
not be available for forensic analysis. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Verify that 'use_mappers' is set to 'pwent' in PAM
lineinfile:
path: /etc/pam_pkcs11/pam_pkcs11.conf
create: true
line: use_mappers = pwent
state: present
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010006
- low_complexity
- low_disruption
- low_severity
- no_reboot_needed
- restrict_strategy
- verify_use_mappers
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
if [ -e "/etc/pam_pkcs11/pam_pkcs11.conf" ] ; then
LC_ALL=C sed -i "/^\s*use_mappers = pwent/Id" "/etc/pam_pkcs11/pam_pkcs11.conf"
else
touch "/etc/pam_pkcs11/pam_pkcs11.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/pam_pkcs11/pam_pkcs11.conf"
cp "/etc/pam_pkcs11/pam_pkcs11.conf" "/etc/pam_pkcs11/pam_pkcs11.conf.bak"
# Insert at the end of the file
printf '%s\n' "use_mappers = pwent" >> "/etc/pam_pkcs11/pam_pkcs11.conf"
# Clean up after ourselves.
rm "/etc/pam_pkcs11/pam_pkcs11.conf.bak"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Disable Ctrl-Alt-Del Burst Action
[ref]ruleBy default, SystemD will reboot the system if the Ctrl-Alt-Del
key sequence is pressed Ctrl-Alt-Delete more than 7 times in 2 seconds.
To configure the system to ignore the CtrlAltDelBurstAction
setting, add or modify the following to /etc/systemd/system.conf :
CtrlAltDelBurstAction=none Rationale:A locally logged-in user who presses Ctrl-Alt-Del, when at the console,
can reboot the system. If accidentally pressed, as could happen in
the case of mixed OS environment, this can create the risk of short-term
loss of availability of systems due to unintentional reboot. References:
12, 13, 14, 15, 16, 18, 3, 5, APO01.06, DSS05.04, DSS05.07, DSS06.02, 3.4.5, CCI-000366, 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii), 4.3.3.7.3, SR 2.1, SR 5.2, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2, CM-6(a), AC-6(1), CM-6(a), PR.AC-4, PR.DS-5, FAU_GEN.1.2, SRG-OS-000324-GPOS-00125, SRG-OS-000480-GPOS-00227, UBTU-20-010460 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'systemd' 2>/dev/null | grep -q installed; then
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "/etc/systemd/system.conf"; then
sed_command+=('--follow-symlinks')
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^CtrlAltDelBurstAction=")
# shellcheck disable=SC2059
printf -v formatted_output "%s=%s" "$stripped_key" "none"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^CtrlAltDelBurstAction=\\>" "/etc/systemd/system.conf"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
"${sed_command[@]}" "s/^CtrlAltDelBurstAction=\\>.*/$escaped_formatted_output/gi" "/etc/systemd/system.conf"
else
# \n is precaution for case where file ends without trailing newline
printf '%s\n' "$formatted_output" >> "/etc/systemd/system.conf"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Disable Ctrl-Alt-Del Reboot Activation
[ref]ruleBy default, SystemD will reboot the system if the Ctrl-Alt-Del
key sequence is pressed.
To configure the system to ignore the Ctrl-Alt-Del key sequence from the
command line instead of rebooting the system, do either of the following:
ln -sf /dev/null /etc/systemd/system/ctrl-alt-del.target
or
systemctl mask ctrl-alt-del.target
Do not simply delete the /usr/lib/systemd/system/ctrl-alt-del.service file,
as this file may be restored during future system updates.Rationale:A locally logged-in user who presses Ctrl-Alt-Del, when at the console,
can reboot the system. If accidentally pressed, as could happen in
the case of mixed OS environment, this can create the risk of short-term
loss of availability of systems due to unintentional reboot. References:
12, 13, 14, 15, 16, 18, 3, 5, APO01.06, DSS05.04, DSS05.07, DSS06.02, 3.4.5, CCI-000366, 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii), 4.3.3.7.3, SR 2.1, SR 5.2, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2, CM-6(a), AC-6(1), PR.AC-4, PR.DS-5, FAU_GEN.1.2, SRG-OS-000324-GPOS-00125, SRG-OS-000480-GPOS-00227, UBTU-20-010460 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
systemctl disable --now ctrl-alt-del.target
systemctl mask --now ctrl-alt-del.target
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Protect Accounts by Restricting Password-Based Login
[ref]groupConventionally, 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. |
contains 8 rules |
Set Account Expiration Parameters
[ref]groupAccounts can be configured to be automatically disabled
after a certain time period,
meaning that they will require administrator interaction to become usable again.
Expiration of accounts after inactivity can be set for all accounts by default
and also on a per-account basis, such as for accounts that are known to be temporary.
To configure automatic expiration of an account following
the expiration of its password (that is, after the password has expired and not been changed),
run the following command, substituting NUM_DAYS and USER appropriately:
$ sudo chage -I NUM_DAYS USER
Accounts, such as temporary accounts, can also be configured to expire on an explicitly-set date with the
-E option.
The file /etc/default/useradd controls
default settings for all newly-created accounts created with the system's
normal command line utilities.Warning:
This will only apply to newly created accounts |
contains 3 rules |
Set Account Expiration Following Inactivity
[ref]ruleTo specify the number of days after a password expires (which
signifies inactivity) until an account is permanently disabled, add or correct
the following line in /etc/default/useradd :
INACTIVE=35
If a password is currently on the verge of expiration, then
35
day(s) remain(s) until the account is automatically
disabled. However, if the password will not expire for another 60 days, then 60
days plus 35 day(s) could
elapse until the account would be automatically disabled. See the
useradd man page for more information.Rationale:Inactive identifiers pose a risk to systems and applications because attackers may exploit an inactive identifier and potentially obtain undetected access to the system.
Disabling inactive accounts ensures that accounts which may not have been responsibly removed are not available to attackers who may have compromised their credentials.
Owners of inactive accounts will not notice if unauthorized access to their user account has been obtained. References:
5.4.1.4, 1, 12, 13, 14, 15, 16, 18, 3, 5, 7, 8, 5.6.2.1.1, DSS01.03, DSS03.05, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, 3.5.6, CCI-000017, CCI-000795, 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.3, 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, SR 6.2, A.12.4.1, A.12.4.3, A.18.1.4, A.6.1.2, A.7.1.1, A.9.1.2, 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.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5, CIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3, IA-4(e), AC-2(3), CM-6(a), DE.CM-1, DE.CM-3, PR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, Req-8.1.4, SRG-OS-000118-GPOS-00060, UBTU-20-010409, SRG-OS-000003-VMM-000030, SRG-OS-000118-VMM-000590 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'login' 2>/dev/null | grep -q installed; then
var_account_disable_post_pw_expiration='35'
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "/etc/default/useradd"; then
sed_command+=('--follow-symlinks')
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^INACTIVE")
# shellcheck disable=SC2059
printf -v formatted_output "%s=%s" "$stripped_key" "$var_account_disable_post_pw_expiration"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^INACTIVE\\>" "/etc/default/useradd"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
"${sed_command[@]}" "s/^INACTIVE\\>.*/$escaped_formatted_output/gi" "/etc/default/useradd"
else
# \n is precaution for case where file ends without trailing newline
printf '%s\n' "$formatted_output" >> "/etc/default/useradd"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Assign Expiration Date to Temporary Accounts
[ref]ruleTemporary accounts are established as part of normal account activation
procedures when there is a need for short-term accounts. In the event
temporary accounts are required, configure the system to
terminate them after a documented time period. For every temporary account, run the following command to set an expiration date on
it, substituting USER and YYYY-MM-DD
appropriately:
$ sudo chage -E YYYY-MM-DD USER
YYYY-MM-DD indicates the documented expiration date for the
account. For U.S. Government systems, the operating system must be
configured to automatically terminate these types of accounts after a
period of 72 hours.Rationale:If temporary user accounts remain active when no longer needed or for
an excessive period, these accounts may be used to gain unauthorized access.
To mitigate this risk, automated termination of all temporary accounts
must be set upon account creation.
References:
1, 12, 13, 14, 15, 16, 18, 3, 5, 7, 8, DSS01.03, DSS03.05, DSS05.04, DSS05.05, DSS05.07, DSS06.03, CCI-000016, CCI-001682, 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, SR 1.1, 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 6.2, A.12.4.1, A.12.4.3, A.6.1.2, A.7.1.1, A.9.1.2, 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.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5, AC-2(2), AC-2(3), CM-6(a), DE.CM-1, DE.CM-3, PR.AC-1, PR.AC-4, PR.AC-6, SRG-OS-000123-GPOS-00064, SRG-OS-000002-GPOS-00002, UBTU-20-010000, SRG-OS-000002-VMM-000020, SRG-OS-000123-VMM-000620 |
Policy Requires Immediate Change of Temporary Passwords
[ref]ruleTemporary passwords for Ubuntu 20.04 operating system logons must
require an immediate change to a permanent password.
Verify that a policy exists that ensures when a user is created, it is
creating using a method that forces a user to change their password upon
their next login. Rationale:Without providing this capability, an account may be created without a
password. Nonrepudiation cannot be guaranteed once an account is created if
a user is not forced to change the temporary password upon initial logon.
Temporary passwords are typically used to allow access when new accounts
are created or passwords are changed. It is common practice for
administrators to create temporary passwords for user accounts that allow
the users to log on, yet force them to change the password once they have
successfully authenticated. |
Set Password Expiration Parameters
[ref]groupThe 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 |
contains 2 rules |
Set Password Maximum Age
[ref]ruleTo specify password maximum age for new accounts,
edit the file /etc/login.defs
and add or correct the following line:
PASS_MAX_DAYS 60
A value of 180 days is sufficient for many environments.
The DoD requirement is 60.
The profile requirement is 60 .Rationale:Any password, no matter how complex, can eventually be cracked. Therefore, passwords
need to be changed periodically. If the operating system does not limit the lifetime
of passwords and force users to change their passwords, there is the risk that the
operating system passwords could be compromised.
Setting the password maximum age ensures users are required to
periodically change their passwords. Requiring shorter password lifetimes
increases the risk of users writing down the password in a convenient
location subject to physical compromise. References:
BP28(R18), 5.4.1.1, 1, 12, 15, 16, 5, 5.6.2.1, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, 3.5.6, CCI-000199, 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, 0418, 1055, 1402, 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)(d), CM-6(a), PR.AC-1, PR.AC-6, PR.AC-7, Req-8.2.4, SRG-OS-000076-GPOS-00044, UBTU-20-010008 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'login' 2>/dev/null | grep -q installed; then
var_accounts_maximum_age_login_defs='60'
grep -q ^PASS_MAX_DAYS /etc/login.defs && \
sed -i "s/PASS_MAX_DAYS.*/PASS_MAX_DAYS $var_accounts_maximum_age_login_defs/g" /etc/login.defs
if ! [ $? -eq 0 ]; then
echo "PASS_MAX_DAYS $var_accounts_maximum_age_login_defs" >> /etc/login.defs
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Set Password Minimum Age
[ref]ruleTo specify password minimum age for new accounts,
edit the file /etc/login.defs
and add or correct the following line:
PASS_MIN_DAYS 1
A value of 1 day is considered sufficient for many
environments. The DoD requirement is 1.
The profile requirement is 1 .Rationale:Enforcing a minimum password lifetime helps to prevent repeated password
changes to defeat the password reuse or history enforcement requirement. If
users are allowed to immediately and continually change their password,
then the password could be repeatedly changed in a short period of time to
defeat the organization's policy regarding password reuse.
Setting the minimum password age protects against users cycling back to a
favorite password after satisfying the password reuse requirement. References:
5.4.1.2, 1, 12, 15, 16, 5, 5.6.2.1.1, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, 3.5.8, CCI-000198, 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, 0418, 1055, 1402, 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)(d), CM-6(a), PR.AC-1, PR.AC-6, PR.AC-7, Req-8.3.9, SRG-OS-000075-GPOS-00043, UBTU-20-010007 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'login' 2>/dev/null | grep -q installed; then
var_accounts_minimum_age_login_defs='1'
grep -q ^PASS_MIN_DAYS /etc/login.defs && \
sed -i "s/PASS_MIN_DAYS.*/PASS_MIN_DAYS $var_accounts_minimum_age_login_defs/g" /etc/login.defs
if ! [ $? -eq 0 ]; then
echo "PASS_MIN_DAYS $var_accounts_minimum_age_login_defs" >> /etc/login.defs
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Verify Proper Storage and Existence of Password
Hashes
[ref]groupBy default, password hashes for local accounts are stored
in the second field (colon-separated) in
/etc/shadow . This file should be readable only by
processes running with root credentials, preventing users from
casually accessing others' password hashes and attempting
to crack them.
However, it remains possible to misconfigure the system
and store password hashes
in world-readable files such as /etc/passwd , or
to even store passwords themselves in plaintext on the system.
Using system-provided tools for password change/creation
should allow administrators to avoid such misconfiguration. |
contains 2 rules |
Ensure sudo group has only necessary members
[ref]ruleDevelopers and implementers can increase the assurance in security
functions by employing well-defined security policy models; structured,
disciplined, and rigorous hardware and software development techniques;
and sound system/security engineering principles. Implementation may
include isolation of memory space and libraries.
The Ubuntu operating system restricts access to security functions
through the use of access control mechanisms and by implementing least
privilege capabilities. Warning:
Due to the risk of removing user rights, automated remediation is
not available for this configuration check. Rationale:Any users assigned to the sudo group would be granted administrator
access to the system. |
Ensure no duplicate UIDs exist
[ref]ruleAlthough the useradd program will not let you create a duplicate User ID (UID),
it is possible for an administrator to manually edit the /etc/passwd file
and change the UID field. Users must be assigned unique UIDs for
accountability and to ensure appropriate access protections. Warning:
Due to the risk of removing user accounts or changing user's UIDS,
automated remediation is not available for this configuration check. Rationale:Users must be assigned unique UIDs for accountability and to ensure
appropriate access protections. |
Restrict Root Logins
[ref]groupDirect root logins should be allowed only for emergency use.
In normal situations, the administrator should access the system
via a unique unprivileged account, and then use su or sudo to execute
privileged commands. Discouraging administrators from accessing the
root account directly ensures an audit trail in organizations with
multiple administrators. Locking down the channels through which
root can connect directly also reduces opportunities for
password-guessing against the root account. The login program
uses the file /etc/securetty to determine which interfaces
should allow root logins.
The virtual devices /dev/console
and /dev/tty* represent the system consoles (accessible via
the Ctrl-Alt-F1 through Ctrl-Alt-F6 keyboard sequences on a default
installation). The default securetty file also contains /dev/vc/* .
These are likely to be deprecated in most environments, but may be retained
for compatibility. Root should also be prohibited from connecting
via network protocols. Other sections of this document
include guidance describing how to prevent root from logging in via SSH. |
contains 1 rule |
Direct root Logins Are Not Allowed
[ref]ruleConfigure the operating system to prevent direct logins to the
root account by performing the following operations:
$ sudo passwd -l root Rationale:Disabling direct root logins ensures proper accountability and
multifactor authentication to privileged accounts. |
Secure Session Configuration Files for Login Accounts
[ref]groupWhen 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. |
contains 3 rules |
Ensure that Users Have Sensible Umask Values
[ref]groupThe umask setting controls the default permissions
for the creation of new files.
With a default umask setting of 077, files and directories
created by users will not be readable by any other user on the
system. Users who wish to make specific files group- or
world-readable can accomplish this by using the chmod command.
Additionally, users can make all their files readable to their
group by default by setting a umask of 027 in their shell
configuration files. If default per-user groups exist (that is, if
every user has a default group whose name is the same as that
user's username and whose only member is the user), then it may
even be safe for users to select a umask of 007, making it very
easy to intentionally share files with groups of which the user is
a member.
|
contains 1 rule |
Ensure the Default Umask is Set Correctly in login.defs
[ref]ruleTo ensure the default umask controlled by /etc/login.defs is set properly,
add or correct the UMASK setting in /etc/login.defs to read as follows:
UMASK 077 Rationale:The umask value influences the permissions assigned to files when they are created.
A misconfigured umask value could result in files with excessive permissions that can be read and
written to by unauthorized users. References:
BP28(R35), 5.4.4, 11, 18, 3, 9, APO13.01, BAI03.01, BAI03.02, BAI03.03, 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.1.1, A.14.2.1, A.14.2.2, A.14.2.3, A.14.2.4, A.14.2.5, A.6.1.5, CIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2, AC-6(1), CM-6(a), PR.IP-1, PR.IP-2, Req-8.6.1, SRG-OS-000480-GPOS-00228, UBTU-20-010016 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'login' 2>/dev/null | grep -q installed; then
var_accounts_user_umask='077'
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "/etc/login.defs"; then
sed_command+=('--follow-symlinks')
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^UMASK")
# shellcheck disable=SC2059
printf -v formatted_output "%s %s" "$stripped_key" "$var_accounts_user_umask"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^UMASK\\>" "/etc/login.defs"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
"${sed_command[@]}" "s/^UMASK\\>.*/$escaped_formatted_output/gi" "/etc/login.defs"
else
# \n is precaution for case where file ends without trailing newline
printf '%s\n' "$formatted_output" >> "/etc/login.defs"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Limit the Number of Concurrent Login Sessions Allowed Per User
[ref]ruleLimiting 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 10 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. 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, UBTU-20-010400, SRG-OS-000027-VMM-000080 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q installed; then
var_accounts_max_concurrent_login_sessions='10'
if grep -q '^[^#]*\<maxlogins\>' /etc/security/limits.d/*.conf; then
sed -i "/^[^#]*\<maxlogins\>/ s/maxlogins.*/maxlogins $var_accounts_max_concurrent_login_sessions/" /etc/security/limits.d/*.conf
elif grep -q '^[^#]*\<maxlogins\>' /etc/security/limits.conf; then
sed -i "/^[^#]*\<maxlogins\>/ s/maxlogins.*/maxlogins $var_accounts_max_concurrent_login_sessions/" /etc/security/limits.conf
else
echo "* hard maxlogins $var_accounts_max_concurrent_login_sessions" >> /etc/security/limits.conf
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Set Interactive Session Timeout
[ref]ruleSetting 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 a file loaded by /etc/profile , e.g.
/etc/profile.d/tmout.sh should read as follows:
TMOUT=600
readonly TMOUT
export TMOUTRationale: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. References:
BP28(R29), 5.4.5, 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, Req-8.6.1, SRG-OS-000163-GPOS-00072, SRG-OS-000029-GPOS-00010, UBTU-20-010013, SRG-OS-000163-VMM-000700, SRG-OS-000279-VMM-001010 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
var_accounts_tmout='600'
# if 0, no occurence of tmout found, if 1, occurence found
tmout_found=0
for f in /etc/bash.bashrc /etc/profile /etc/profile.d/*.sh; do
if grep --silent '^\s*TMOUT' $f; then
sed -i -E "s/^(\s*)TMOUT\s*=\s*(\w|\$)*(.*)$/\1TMOUT=$var_accounts_tmout\3/g" $f
tmout_found=1
if ! grep --silent '^\s*readonly TMOUT' $f ; then
echo "readonly TMOUT" >> $f
fi
if ! grep --silent '^\s*export TMOUT' $f ; then
echo "export TMOUT" >> $f
fi
fi
done
if [ $tmout_found -eq 0 ]; then
echo -e "\n# Set TMOUT to $var_accounts_tmout per security requirements" >> /etc/profile.d/tmout.sh
echo "TMOUT=$var_accounts_tmout" >> /etc/profile.d/tmout.sh
echo "readonly TMOUT" >> /etc/profile.d/tmout.sh
echo "export TMOUT" >> /etc/profile.d/tmout.sh
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
System Accounting with auditd
[ref]groupThe 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" .
|
contains 80 rules |
Configure auditd Rules for Comprehensive Auditing
[ref]groupThe 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 |
contains 69 rules |
Record Events that Modify the System's Discretionary Access Controls
[ref]groupAt 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 |
contains 13 rules |
Record Events that Modify the System's Discretionary Access Controls - chmod
[ref]ruleAt 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. References:
4.1.9, 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-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.5.5, 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, UBTU-20-010152, SRG-OS-000458-VMM-001810, SRG-OS-000474-VMM-001940 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010152
- 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.5.5
- 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:
- '"auditd" 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:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010152
- 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.5.5
- 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:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010152
- 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.5.5
- 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:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010152
- 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.5.5
- audit_rules_dac_modification_chmod
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; 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
|
Record Events that Modify the System's Discretionary Access Controls - chown
[ref]ruleAt 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. References:
4.1.9, 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-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.5.5, 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, UBTU-20-010148, SRG-OS-000458-VMM-001810, SRG-OS-000474-VMM-001940 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010148
- 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.5.5
- 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:
- '"auditd" 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:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010148
- 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.5.5
- 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:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010148
- 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.5.5
- 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:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010148
- 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.5.5
- audit_rules_dac_modification_chown
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; 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
|
Record Events that Modify the System's Discretionary Access Controls - fchmod
[ref]ruleAt 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. References:
4.1.9, 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-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.5.5, 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, UBTU-20-010153, SRG-OS-000458-VMM-001810, SRG-OS-000474-VMM-001940 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010153
- 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.5.5
- 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:
- '"auditd" 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:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010153
- 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.5.5
- 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:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010153
- 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.5.5
- 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:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010153
- 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.5.5
- audit_rules_dac_modification_fchmod
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; 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
|
Record Events that Modify the System's Discretionary Access Controls - fchmodat
[ref]ruleAt 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. References:
4.1.9, 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-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.5.5, 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, UBTU-20-010154, SRG-OS-000458-VMM-001810, SRG-OS-000474-VMM-001940 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010154
- 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.5.5
- 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:
- '"auditd" 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:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010154
- 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.5.5
- 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:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010154
- 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.5.5
- 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:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010154
- 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.5.5
- audit_rules_dac_modification_fchmodat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; 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
|
Record Events that Modify the System's Discretionary Access Controls - fchown
[ref]ruleAt 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. References:
4.1.9, 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-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.5.5, 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, UBTU-20-010149, SRG-OS-000458-VMM-001810, SRG-OS-000474-VMM-001940 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010149
- 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.5.5
- 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:
- '"auditd" 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:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010149
- 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.5.5
- 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:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010149
- 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.5.5
- 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:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010149
- 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.5.5
- audit_rules_dac_modification_fchown
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; 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
|
Record Events that Modify the System's Discretionary Access Controls - fchownat
[ref]ruleAt 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. References:
4.1.9, 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-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.5.5, 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, UBTU-20-010150, SRG-OS-000458-VMM-001810, SRG-OS-000474-VMM-001940 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010150
- 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.5.5
- 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:
- '"auditd" 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:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010150
- 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.5.5
- 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:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010150
- 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.5.5
- 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:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010150
- 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.5.5
- audit_rules_dac_modification_fchownat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; 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
|
Record Events that Modify the System's Discretionary Access Controls - fremovexattr
[ref]ruleAt 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. References:
4.1.9, 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-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.5.5, 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, UBTU-20-010147, SRG-OS-000458-VMM-001810, SRG-OS-000474-VMM-001940 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010147
- 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.5.5
- 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:
- '"auditd" 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:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010147
- 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.5.5
- 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
- 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=0 (-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=0 (?:-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=0 -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=0 (-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=0 (?:-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=0 -F
key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010147
- 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.5.5
- 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
- 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=0 (-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=0 (?:-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=0 -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=0 (-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=0 (?:-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=0 -F
key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010147
- 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.5.5
- audit_rules_dac_modification_fremovexattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; 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
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid=0"
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
|
Record Events that Modify the System's Discretionary Access Controls - fsetxattr
[ref]ruleAt 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. References:
4.1.9, 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-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.5.5, 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, UBTU-20-010144, SRG-OS-000458-VMM-001810, SRG-OS-000474-VMM-001940 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010144
- 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.5.5
- 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:
- '"auditd" 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:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010144
- 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.5.5
- 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
- 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=0 (-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=0 (?:-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=0 -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=0 (-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=0 (?:-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=0 -F
key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010144
- 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.5.5
- 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
- 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=0 (-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=0 (?:-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=0 -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=0 (-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=0 (?:-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=0 -F
key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010144
- 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.5.5
- audit_rules_dac_modification_fsetxattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; 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
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid=0"
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
|
Record Events that Modify the System's Discretionary Access Controls - lchown
[ref]ruleAt 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. References:
4.1.9, 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-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.5.5, 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, UBTU-20-010151, SRG-OS-000458-VMM-001810, SRG-OS-000474-VMM-001940 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010151
- 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.5.5
- 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:
- '"auditd" 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:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010151
- 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.5.5
- 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:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010151
- 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.5.5
- 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:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010151
- 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.5.5
- audit_rules_dac_modification_lchown
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; 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
|
Record Events that Modify the System's Discretionary Access Controls - lremovexattr
[ref]ruleAt 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. References:
4.1.9, 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-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.5.5, 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, UBTU-20-010146, SRG-OS-000458-VMM-001810, SRG-OS-000474-VMM-001940 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010146
- 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.5.5
- 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:
- '"auditd" 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:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010146
- 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.5.5
- 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
- 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=0 (-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=0 (?:-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=0 -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=0 (-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=0 (?:-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=0 -F
key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010146
- 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.5.5
- 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
- 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=0 (-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=0 (?:-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=0 -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=0 (-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=0 (?:-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=0 -F
key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010146
- 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.5.5
- audit_rules_dac_modification_lremovexattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; 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
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid=0"
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
|
Record Events that Modify the System's Discretionary Access Controls - lsetxattr
[ref]ruleAt 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. References:
4.1.9, 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-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.5.5, 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, UBTU-20-010143, SRG-OS-000458-VMM-001810, SRG-OS-000474-VMM-001940 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010143
- 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.5.5
- 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:
- '"auditd" 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:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010143
- 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.5.5
- 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
- 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=0 (-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=0 (?:-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=0 -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=0 (-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=0 (?:-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=0 -F
key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010143
- 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.5.5
- 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
- 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=0 (-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=0 (?:-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=0 -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=0 (-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=0 (?:-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=0 -F
key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010143
- 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.5.5
- audit_rules_dac_modification_lsetxattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; 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
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid=0"
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
|
Record Events that Modify the System's Discretionary Access Controls - removexattr
[ref]ruleAt 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. References:
4.1.9, 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-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.5.5, 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, UBTU-20-010145, SRG-OS-000458-VMM-001810, SRG-OS-000474-VMM-001940 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010145
- 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.5.5
- 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:
- '"auditd" 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:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010145
- 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.5.5
- 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
- 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=0 (-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=0 (?:-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=0 -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=0 (-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=0 (?:-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=0 -F
key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010145
- 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.5.5
- 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
- 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=0 (-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=0 (?:-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=0 -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=0 (-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=0 (?:-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=0 -F
key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010145
- 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.5.5
- audit_rules_dac_modification_removexattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; 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
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid=0"
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
|
Record Events that Modify the System's Discretionary Access Controls - setxattr
[ref]ruleAt 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. References:
4.1.9, 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-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.5.5, 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, UBTU-20-010142, SRG-OS-000458-VMM-001810, SRG-OS-000474-VMM-001940 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010142
- 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.5.5
- 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:
- '"auditd" 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:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010142
- 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.5.5
- 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
- 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=0 (-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=0 (?:-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=0 -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=0 (-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=0 (?:-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=0 -F
key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010142
- 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.5.5
- 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
- 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=0 (-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=0 (?:-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=0 -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=0 (-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=0 (?:-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=0 -F
key=perm_mod
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010142
- 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.5.5
- audit_rules_dac_modification_setxattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; 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
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid=0"
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
|
Record Execution Attempts to Run ACL Privileged Commands
[ref]groupAt a minimum, the audit system should collect the execution of
ACL privileged commands for all users and root. |
contains 2 rules |
Record Any Attempts to Run chacl
[ref]ruleAt a minimum, the audit system should collect any execution attempt
of the chacl command 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 lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/chacl -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F path=/usr/bin/chacl -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged Rationale:Without generating audit records that are specific to the security and
mission needs of the organization, it would be difficult to establish,
correlate, and investigate the events relating to an incident or identify
those responsible for one.
Audit records can be generated from various components within the
information system (e.g., module or policy filter). References:
CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884, 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, UBTU-20-010168 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010168
- audit_rules_execution_chacl
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/chacl
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/chacl -F perm=x -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/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.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)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/chacl -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/bin/chacl -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/chacl -F perm=x -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)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/chacl -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/bin/chacl -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010168
- audit_rules_execution_chacl
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/chacl -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# 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
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Any Attempts to Run setfacl
[ref]ruleAt a minimum, the audit system should collect any execution attempt
of the setfacl command 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 lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/setfacl -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F path=/usr/bin/setfacl -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged Rationale:Without generating audit records that are specific to the security and
mission needs of the organization, it would be difficult to establish,
correlate, and investigate the events relating to an incident or identify
those responsible for one.
Audit records can be generated from various components within the
information system (e.g., module or policy filter). References:
CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884, 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, UBTU-20-010167 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010167
- audit_rules_execution_setfacl
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/setfacl
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/setfacl -F perm=x -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/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.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)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/setfacl -F perm=x
-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{{ syscalls | join(',') }} -F path=/usr/bin/setfacl -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/setfacl -F perm=x -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)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/setfacl -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/bin/setfacl -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010167
- audit_rules_execution_setfacl
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/setfacl -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# 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
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Execution Attempts to Run SELinux Privileged Commands
[ref]groupAt a minimum, the audit system should collect the execution of
SELinux privileged commands for all users and root. |
contains 1 rule |
Record Any Attempts to Run chcon
[ref]ruleAt a minimum, the audit system should collect any execution attempt
of the chcon command 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 lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, 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.3.2.6.7, 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 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, FAU_GEN.1.1.c, 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-000468-GPOS-00212, SRG-OS-000471-GPOS-00215, SRG-OS-000463-GPOS-00207, SRG-OS-000465-GPOS-00209, UBTU-20-010165, SRG-OS-000463-VMM-001850 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010165
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_execution_chcon
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/chcon
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/chcon -F perm=x -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/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.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)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/chcon -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/bin/chcon -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/chcon -F perm=x -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)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/chcon -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/bin/chcon -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010165
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_execution_chcon
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/chcon -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# 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
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record File Deletion Events by User
[ref]groupAt 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 |
contains 4 rules |
Ensure auditd Collects File Deletion Events by User - rename
[ref]ruleAt 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 rename -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 rename -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. References:
4.1.13, 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, 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, UBTU-20-010269, SRG-OS-000466-VMM-001870, SRG-OS-000468-VMM-001890 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010269
- 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
- audit_rules_file_deletion_events_rename
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit rename tasks
set_fact:
audit_arch: b64
when:
- '"auditd" 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:
- DISA-STIG-UBTU-20-010269
- 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
- audit_rules_file_deletion_events_rename
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for rename for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- rename
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of rename 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:
- rename
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of rename 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:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010269
- 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
- audit_rules_file_deletion_events_rename
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for rename for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- rename
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of rename 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:
- rename
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of rename 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:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-20-010269
- 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
- audit_rules_file_deletion_events_rename
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; 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="rename"
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
|
Ensure auditd Collects File Deletion Events by User - renameat
[ref]ruleAt 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 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 renameat -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. References:
4.1.13, 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, 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, UBTU-20-010270, SRG-OS-000466-VMM-001870, SRG-OS-000468-VMM-001890 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010270
- 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
- audit_rules_file_deletion_events_renameat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit renameat tasks
set_fact:
audit_arch: b64
when:
- '"auditd" 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:
- DISA-STIG-UBTU-20-010270
- 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
- audit_rules_file_deletion_events_renameat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for renameat for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- renameat
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of renameat 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:
- renameat
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of renameat 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:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010270
- 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
- audit_rules_file_deletion_events_renameat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for renameat for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- renameat
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of renameat 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:
- renameat
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of renameat 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:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-20-010270
- 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
- audit_rules_file_deletion_events_renameat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; 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="renameat"
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
|
Ensure auditd Collects File Deletion Events by User - unlink
[ref]ruleAt 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. References:
4.1.13, 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, 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, UBTU-20-010267, SRG-OS-000466-VMM-001870, SRG-OS-000468-VMM-001890 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010267
- 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
- audit_rules_file_deletion_events_unlink
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit unlink tasks
set_fact:
audit_arch: b64
when:
- '"auditd" 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:
- DISA-STIG-UBTU-20-010267
- 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
- audit_rules_file_deletion_events_unlink
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for unlink for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- unlink
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of unlink 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:
- unlink
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of unlink 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:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010267
- 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
- audit_rules_file_deletion_events_unlink
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for unlink for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- unlink
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of unlink 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:
- unlink
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of unlink 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:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-20-010267
- 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
- audit_rules_file_deletion_events_unlink
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; 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="unlink"
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
|
Ensure auditd Collects File Deletion Events by User - unlinkat
[ref]ruleAt 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 unlinkat -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 unlinkat -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. References:
4.1.13, 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, 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, UBTU-20-010268, SRG-OS-000466-VMM-001870, SRG-OS-000468-VMM-001890 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010268
- 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
- audit_rules_file_deletion_events_unlinkat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit unlinkat tasks
set_fact:
audit_arch: b64
when:
- '"auditd" 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:
- DISA-STIG-UBTU-20-010268
- 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
- audit_rules_file_deletion_events_unlinkat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for unlinkat for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- unlinkat
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of unlinkat 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:
- unlinkat
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of unlinkat 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:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010268
- 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
- audit_rules_file_deletion_events_unlinkat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for unlinkat for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- unlinkat
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of unlinkat 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:
- unlinkat
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of unlinkat 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:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-20-010268
- 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
- audit_rules_file_deletion_events_unlinkat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; 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="unlinkat"
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
|
Record Unauthorized Access Attempts Events to Files (unsuccessful)
[ref]groupAt a minimum, the audit system should collect unauthorized file
accesses 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 creat,open,openat,open_by_handle_at,truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S creat,open,openat,open_by_handle_at,truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
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 creat,open,openat,open_by_handle_at,truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S creat,open,openat,open_by_handle_at,truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access |
contains 6 rules |
Record Unsuccessful Access Attempts to Files - creat
[ref]ruleAt a minimum, the audit system should collect unauthorized file
accesses 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 lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S creat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S creat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S creat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S creat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S creat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S creat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S creat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S creat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access 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:Unsuccessful attempts to access files could be an indicator of malicious activity on a system. Auditing
these events could serve as evidence of potential system compromise. References:
4.1.10, 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-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-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.4, Req-10.2.1, 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-000458-GPOS-00203, SRG-OS-000461-GPOS-00205, UBTU-20-010158, SRG-OS-000458-VMM-001810, SRG-OS-000461-VMM-001830 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010158
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_creat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit creat tasks
set_fact:
audit_arch: b64
when:
- '"auditd" 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:
- DISA-STIG-UBTU-20-010158
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_creat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for creat EACCES for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- creat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of creat 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 exit=-EACCES -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/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.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 exit=-EACCES -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 exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- creat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of creat in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -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 exit=-EACCES -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 exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010158
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_creat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for creat EACCES for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- creat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of creat 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 exit=-EACCES -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/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.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 exit=-EACCES -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 exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- creat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of creat in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -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 exit=-EACCES -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 exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-20-010158
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_creat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for creat EPERM for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- creat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of creat 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 exit=-EPERM -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/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.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 exit=-EPERM -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 exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- creat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of creat in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -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 exit=-EPERM -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 exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010158
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_creat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for creat EPERM for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- creat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of creat 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 exit=-EPERM -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/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.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 exit=-EPERM -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 exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- creat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of creat in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -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 exit=-EPERM -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 exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-20-010158
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_creat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; 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")
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="creat"
KEY="access"
SYSCALL_GROUPING="creat ftruncate truncate open openat open_by_handle_at"
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS="-F exit=-EACCES"
# 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
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS="-F exit=-EPERM"
# 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
|
Record Unsuccessful Access Attempts to Files - ftruncate
[ref]ruleAt a minimum, the audit system should collect unauthorized file
accesses 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 lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access 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:Unsuccessful attempts to access files could be an indicator of malicious activity on a system. Auditing
these events could serve as evidence of potential system compromise. References:
4.1.10, 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-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-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.4, Req-10.2.1, 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-000458-GPOS-00203, SRG-OS-000461-GPOS-00205, UBTU-20-010157, SRG-OS-000458-VMM-001810, SRG-OS-000461-VMM-001830 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010157
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_ftruncate
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit ftruncate tasks
set_fact:
audit_arch: b64
when:
- '"auditd" 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:
- DISA-STIG-UBTU-20-010157
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_ftruncate
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for ftruncate EACCES for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- ftruncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of ftruncate 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 exit=-EACCES -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/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.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 exit=-EACCES -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 exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- ftruncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of ftruncate in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -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 exit=-EACCES -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 exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010157
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_ftruncate
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for ftruncate EACCES for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- ftruncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of ftruncate 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 exit=-EACCES -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/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.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 exit=-EACCES -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 exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- ftruncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of ftruncate in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -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 exit=-EACCES -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 exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-20-010157
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_ftruncate
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for ftruncate EPERM for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- ftruncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of ftruncate 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 exit=-EPERM -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/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.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 exit=-EPERM -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 exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- ftruncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of ftruncate in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -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 exit=-EPERM -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 exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010157
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_ftruncate
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for ftruncate EPERM for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- ftruncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of ftruncate 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 exit=-EPERM -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/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.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 exit=-EPERM -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 exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- ftruncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of ftruncate in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -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 exit=-EPERM -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 exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-20-010157
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_ftruncate
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; 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")
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="ftruncate"
KEY="access"
SYSCALL_GROUPING="creat ftruncate truncate open openat open_by_handle_at"
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS="-F exit=-EACCES"
# 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
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS="-F exit=-EPERM"
# 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
|
Record Unsuccessful Access Attempts to Files - open
[ref]ruleAt a minimum, the audit system should collect unauthorized file
accesses 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 lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S open -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S open -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S open -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S open -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S open -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S open -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S open -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S open -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access 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:Unsuccessful attempts to access files could be an indicator of malicious activity on a system. Auditing
these events could serve as evidence of potential system compromise. References:
4.1.10, 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-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-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.4, Req-10.2.1, 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-000458-GPOS-00203, SRG-OS-000461-GPOS-00205, UBTU-20-010155, SRG-OS-000458-VMM-001810, SRG-OS-000461-VMM-001830 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010155
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_open
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit open tasks
set_fact:
audit_arch: b64
when:
- '"auditd" 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:
- DISA-STIG-UBTU-20-010155
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_open
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for open EACCES for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- open
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open 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 exit=-EACCES -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/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.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 exit=-EACCES -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 exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- open
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -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 exit=-EACCES -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 exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010155
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_open
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for open EACCES for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- open
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open 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 exit=-EACCES -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/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.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 exit=-EACCES -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 exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- open
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -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 exit=-EACCES -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 exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-20-010155
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_open
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for open EPERM for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- open
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open 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 exit=-EPERM -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/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.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 exit=-EPERM -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 exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- open
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -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 exit=-EPERM -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 exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010155
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_open
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for open EPERM for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- open
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open 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 exit=-EPERM -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/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.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 exit=-EPERM -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 exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- open
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -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 exit=-EPERM -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 exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-20-010155
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_open
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; 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")
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="open"
KEY="access"
SYSCALL_GROUPING="creat ftruncate truncate open openat open_by_handle_at"
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS="-F exit=-EACCES"
# 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
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS="-F exit=-EPERM"
# 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
|
Record Unsuccessful Access Attempts to Files - open_by_handle_at
[ref]ruleAt a minimum, the audit system should collect unauthorized file
accesses 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 lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S open_by_handle_at -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S open_by_handle_at -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S open_by_handle_at -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S open_by_handle_at -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S open_by_handle_at,truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S open_by_handle_at,truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S open_by_handle_at,truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S open_by_handle_at,truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access 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:Unsuccessful attempts to access files could be an indicator of malicious activity on a system. Auditing
these events could serve as evidence of potential system compromise. References:
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-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-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.4, Req-10.2.1, 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-000458-GPOS-00203, SRG-OS-000461-GPOS-00205, UBTU-20-010160, SRG-OS-000458-VMM-001810, SRG-OS-000461-VMM-001830 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010160
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_open_by_handle_at
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit open_by_handle_at tasks
set_fact:
audit_arch: b64
when:
- '"auditd" 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:
- DISA-STIG-UBTU-20-010160
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_open_by_handle_at
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for open_by_handle_at EACCES for 32bit
platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- open_by_handle_at
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open_by_handle_at 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 exit=-EACCES -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/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.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 exit=-EACCES -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 exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- open_by_handle_at
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open_by_handle_at in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -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 exit=-EACCES -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 exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010160
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_open_by_handle_at
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for open_by_handle_at EACCES for 64bit
platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- open_by_handle_at
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open_by_handle_at 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 exit=-EACCES -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/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.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 exit=-EACCES -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 exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- open_by_handle_at
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open_by_handle_at in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -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 exit=-EACCES -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 exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-20-010160
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_open_by_handle_at
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for open_by_handle_at EPERM for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- open_by_handle_at
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open_by_handle_at 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 exit=-EPERM -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/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.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 exit=-EPERM -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 exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- open_by_handle_at
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open_by_handle_at in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -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 exit=-EPERM -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 exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010160
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_open_by_handle_at
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for open_by_handle_at EPERM for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- open_by_handle_at
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open_by_handle_at 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 exit=-EPERM -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/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.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 exit=-EPERM -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 exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- open_by_handle_at
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open_by_handle_at in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -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 exit=-EPERM -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 exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-20-010160
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_open_by_handle_at
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; 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")
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="open_by_handle_at"
KEY="access"
SYSCALL_GROUPING="creat ftruncate truncate open openat open_by_handle_at"
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS="-F exit=-EACCES"
# 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
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS="-F exit=-EPERM"
# 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
|
Record Unsuccessful Access Attempts to Files - openat
[ref]ruleAt a minimum, the audit system should collect unauthorized file
accesses 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 lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S openat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S openat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S openat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S openat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S openat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S openat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S openat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S openat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access 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:Unsuccessful attempts to access files could be an indicator of malicious activity on a system. Auditing
these events could serve as evidence of potential system compromise. References:
4.1.10, 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-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-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.4, Req-10.2.1, 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-000458-GPOS-00203, SRG-OS-000461-GPOS-00205, UBTU-20-010159, SRG-OS-000458-VMM-001810, SRG-OS-000461-VMM-001830 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010159
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_openat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit openat tasks
set_fact:
audit_arch: b64
when:
- '"auditd" 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:
- DISA-STIG-UBTU-20-010159
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_openat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for openat EACCES for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- openat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of openat 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 exit=-EACCES -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/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.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 exit=-EACCES -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 exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- openat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of openat in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -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 exit=-EACCES -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 exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010159
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_openat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for openat EACCES for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- openat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of openat 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 exit=-EACCES -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/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.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 exit=-EACCES -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 exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- openat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of openat in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -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 exit=-EACCES -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 exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-20-010159
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_openat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for openat EPERM for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- openat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of openat 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 exit=-EPERM -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/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.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 exit=-EPERM -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 exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- openat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of openat in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -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 exit=-EPERM -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 exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010159
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_openat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for openat EPERM for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- openat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of openat 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 exit=-EPERM -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/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.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 exit=-EPERM -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 exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- openat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of openat in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -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 exit=-EPERM -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 exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-20-010159
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_openat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; 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")
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="openat"
KEY="access"
SYSCALL_GROUPING="creat ftruncate truncate open openat open_by_handle_at"
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS="-F exit=-EACCES"
# 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
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS="-F exit=-EPERM"
# 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
|
Record Unsuccessful Access Attempts to Files - truncate
[ref]ruleAt a minimum, the audit system should collect unauthorized file
accesses 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 lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S truncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S truncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S truncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S truncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S truncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S truncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S truncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S truncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access 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:Unsuccessful attempts to access files could be an indicator of malicious activity on a system. Auditing
these events could serve as evidence of potential system compromise. References:
4.1.10, 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-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-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.4, Req-10.2.1, 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-000458-GPOS-00203, SRG-OS-000461-GPOS-00205, UBTU-20-010156, SRG-OS-000458-VMM-001810, SRG-OS-000461-VMM-001830 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010156
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_truncate
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit truncate tasks
set_fact:
audit_arch: b64
when:
- '"auditd" 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:
- DISA-STIG-UBTU-20-010156
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_truncate
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for truncate EACCES for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- truncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of truncate 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 exit=-EACCES -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/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.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 exit=-EACCES -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 exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- truncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of truncate in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -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 exit=-EACCES -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 exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010156
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_truncate
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for truncate EACCES for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- truncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of truncate 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 exit=-EACCES -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/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.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 exit=-EACCES -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 exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- truncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of truncate in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -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 exit=-EACCES -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 exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-20-010156
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_truncate
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for truncate EPERM for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- truncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of truncate 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 exit=-EPERM -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/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.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 exit=-EPERM -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 exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- truncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of truncate in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -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 exit=-EPERM -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 exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010156
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_truncate
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for truncate EPERM for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- truncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of truncate 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 exit=-EPERM -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/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.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 exit=-EPERM -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 exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- truncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of truncate in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -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 exit=-EPERM -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 exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-20-010156
- 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.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_truncate
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; 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")
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="truncate"
KEY="access"
SYSCALL_GROUPING="creat ftruncate truncate open openat open_by_handle_at"
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS="-F exit=-EACCES"
# 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
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS="-F exit=-EPERM"
# 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
|
Record Information on Kernel Modules Loading and Unloading
[ref]groupTo capture kernel module loading and unloading events, use following lines, setting ARCH to
either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -S init_module,delete_module -F key=modules
Place to add the lines depends on a way auditd daemon is configured. If it is configured
to use the augenrules program (the default), add the lines to a file with suffix
.rules in the directory /etc/audit/rules.d .
If the auditd daemon is configured to use the auditctl utility,
add the lines to file /etc/audit/audit.rules . |
contains 3 rules |
Ensure auditd Collects Information on Kernel Module Unloading - delete_module
[ref]ruleTo capture kernel module unloading events, use following line, setting ARCH to
either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -S delete_module -F key=modules
Place to add the line depends on a way auditd daemon is configured. If it is configured
to use the augenrules program (the default), add the line to a file with suffix
.rules in the directory /etc/audit/rules.d .
If the auditd daemon is configured to use the auditctl utility,
add the line to file /etc/audit/audit.rules .Rationale:The removal of kernel modules can be used to alter the behavior of
the kernel and potentially introduce malicious code into kernel space. It is important
to have an audit trail of modules that have been introduced into the kernel. References:
4.1.16, 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-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-2(d), AU-12(c), AC-6(9), 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, 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-000471-GPOS-00216, SRG-OS-000477-GPOS-00222, UBTU-20-010302, SRG-OS-000477-VMM-001970 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
# Note: 32-bit and 64-bit kernel syscall numbers not always line up =>
# it's required on a 64-bit system to check also for the presence
# of 32-bit's equivalent of the corresponding rule.
# (See `man 7 audit.rules` for details )
[ "$(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=""
SYSCALL="delete_module"
KEY="modules"
SYSCALL_GROUPING="delete_module"
# 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
|
Ensure auditd Collects Information on Kernel Module Loading and Unloading - finit_module
[ref]ruleIf the auditd daemon is configured to use the augenrules program
to read audit rules during daemon startup (the default), add the following lines to a file
with suffix .rules in the directory /etc/audit/rules.d to capture kernel module
loading and unloading events, setting ARCH to either b32 or b64 as appropriate for your system:
-a always,exit -F arch=ARCH -S finit_module -F key=modules
If the auditd daemon is configured to use the auditctl utility to read audit
rules during daemon startup, add the following lines to /etc/audit/audit.rules file
in order to capture kernel module loading and unloading events, setting ARCH to either b32 or
b64 as appropriate for your system:
-a always,exit -F arch=ARCH -S finit_module -F key=modules Rationale:The addition/removal of kernel modules can be used to alter the behavior of
the kernel and potentially introduce malicious code into kernel space. It is important
to have an audit trail of modules that have been introduced into the kernel. References:
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-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-2(d), AU-12(c), AC-6(9), 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, 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-000471-GPOS-00216, SRG-OS-000477-GPOS-00222, UBTU-20-010180, SRG-OS-000477-VMM-001970 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
# Note: 32-bit and 64-bit kernel syscall numbers not always line up =>
# it's required on a 64-bit system to check also for the presence
# of 32-bit's equivalent of the corresponding rule.
# (See `man 7 audit.rules` for details )
[ "$(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=""
SYSCALL="finit_module"
KEY="modules"
SYSCALL_GROUPING="init_module finit_module"
# 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
|
Ensure auditd Collects Information on Kernel Module Loading - init_module
[ref]ruleTo capture kernel module loading events, use following line, setting ARCH to
either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -S init_module -F key=modules
Place to add the line depends on a way auditd daemon is configured. If it is configured
to use the augenrules program (the default), add the line to a file with suffix
.rules in the directory /etc/audit/rules.d .
If the auditd daemon is configured to use the auditctl utility,
add the line to file /etc/audit/audit.rules .Rationale:The addition of kernel modules can be used to alter the behavior of
the kernel and potentially introduce malicious code into kernel space. It is important
to have an audit trail of modules that have been introduced into the kernel. References:
4.1.16, 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-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-2(d), AU-12(c), AC-6(9), 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, 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-000471-GPOS-00216, SRG-OS-000477-GPOS-00222, UBTU-20-010179, SRG-OS-000477-VMM-001970 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
# Note: 32-bit and 64-bit kernel syscall numbers not always line up =>
# it's required on a 64-bit system to check also for the presence
# of 32-bit's equivalent of the corresponding rule.
# (See `man 7 audit.rules` for details )
[ "$(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=""
SYSCALL="init_module"
KEY="modules"
SYSCALL_GROUPING="init_module finit_module"
# 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
|
Record Attempts to Alter Logon and Logout Events
[ref]groupThe audit system already collects login information 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 lines to a file with suffix .rules in the
directory /etc/audit/rules.d in order to watch for attempted manual
edits of files involved in storing logon events:
-w /var/log/tallylog -p wa -k logins
-w /var/run/faillock -p wa -k logins
-w /var/log/lastlog -p wa -k logins
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file in order to watch for unattempted manual
edits of files involved in storing logon events:
-w /var/log/tallylog -p wa -k logins
-w /var/run/faillock -p wa -k logins
-w /var/log/lastlog -p wa -k logins |
contains 3 rules |
Record Attempts to Alter Logon and Logout Events - faillog
[ref]ruleThe audit system already collects login information 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 lines to a file with suffix .rules in the
directory /etc/audit/rules.d in order to watch for attempted manual
edits of files involved in storing logon events:
-w /var/log/faillog -p wa -k logins
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file in order to watch for unattempted manual
edits of files involved in storing logon events:
-w /var/log/faillog -p wa -k logins Rationale:Manual editing of these files may indicate nefarious activity, such
as an attacker attempting to remove evidence of an intrusion. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010170
- audit_rules_login_events_faillog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /var/log/faillog already exists in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: ^\s*-w\s+/var/log/faillog\s+-p\s+wa(\s|$)+
patterns: '*.rules'
register: find_existing_watch_rules_d
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010170
- audit_rules_login_events_faillog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Search /etc/audit/rules.d for other rules with specified key logins
find:
paths: /etc/audit/rules.d
contains: ^.*(?:-F key=|-k\s+)logins$
patterns: '*.rules'
register: find_watch_key
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- DISA-STIG-UBTU-20-010170
- audit_rules_login_events_faillog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use /etc/audit/rules.d/logins.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/logins.rules
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- DISA-STIG-UBTU-20-010170
- audit_rules_login_events_faillog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- DISA-STIG-UBTU-20-010170
- audit_rules_login_events_faillog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /var/log/faillog in /etc/audit/rules.d/
lineinfile:
path: '{{ all_files[0] }}'
line: -w /var/log/faillog -p wa -k logins
create: true
mode: '0640'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- DISA-STIG-UBTU-20-010170
- audit_rules_login_events_faillog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /var/log/faillog already exists in /etc/audit/audit.rules
find:
paths: /etc/audit/
contains: ^\s*-w\s+/var/log/faillog\s+-p\s+wa(\s|$)+
patterns: audit.rules
register: find_existing_watch_audit_rules
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010170
- audit_rules_login_events_faillog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /var/log/faillog in /etc/audit/audit.rules
lineinfile:
line: -w /var/log/faillog -p wa -k logins
state: present
dest: /etc/audit/audit.rules
create: true
mode: '0640'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
== 0
tags:
- DISA-STIG-UBTU-20-010170
- audit_rules_login_events_faillog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "^[\s]*-w[\s]+/var/log/faillog" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/log/faillog $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "wa" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s#\($sp*-w$sp\+/var/log/faillog$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w /var/log/faillog -p wa -k logins" >> "$audit_rules_file"
fi
done
# 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 the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/logins.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/var/log/faillog" /etc/audit/rules.d/*.rules)
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
# Append '/etc/audit/rules.d/logins.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/logins.rules"
# If the logins.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0640 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "^[\s]*-w[\s]+/var/log/faillog" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/log/faillog $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "wa" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s#\($sp*-w$sp\+/var/log/faillog$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w /var/log/faillog -p wa -k logins" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Attempts to Alter Logon and Logout Events - lastlog
[ref]ruleThe audit system already collects login information 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 lines to a file with suffix .rules in the
directory /etc/audit/rules.d in order to watch for attempted manual
edits of files involved in storing logon events:
-w /var/log/lastlog -p wa -k logins
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file in order to watch for unattempted manual
edits of files involved in storing logon events:
-w /var/log/lastlog -p wa -k logins Rationale:Manual editing of these files may indicate nefarious activity, such
as an attacker attempting to remove evidence of an intrusion. References:
4.1.7, 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-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-2(d), AU-12(c), AC-6(9), 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.3, 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-000473-GPOS-00218, SRG-OS-000470-GPOS-00214, UBTU-20-010171, SRG-OS-000473-VMM-001930, SRG-OS-000470-VMM-001900 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010171
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.3
- audit_rules_login_events_lastlog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /var/log/lastlog already exists in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: ^\s*-w\s+/var/log/lastlog\s+-p\s+wa(\s|$)+
patterns: '*.rules'
register: find_existing_watch_rules_d
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010171
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.3
- audit_rules_login_events_lastlog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Search /etc/audit/rules.d for other rules with specified key logins
find:
paths: /etc/audit/rules.d
contains: ^.*(?:-F key=|-k\s+)logins$
patterns: '*.rules'
register: find_watch_key
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- DISA-STIG-UBTU-20-010171
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.3
- audit_rules_login_events_lastlog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use /etc/audit/rules.d/logins.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/logins.rules
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- DISA-STIG-UBTU-20-010171
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.3
- audit_rules_login_events_lastlog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- DISA-STIG-UBTU-20-010171
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.3
- audit_rules_login_events_lastlog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /var/log/lastlog in /etc/audit/rules.d/
lineinfile:
path: '{{ all_files[0] }}'
line: -w /var/log/lastlog -p wa -k logins
create: true
mode: '0640'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- DISA-STIG-UBTU-20-010171
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.3
- audit_rules_login_events_lastlog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /var/log/lastlog already exists in /etc/audit/audit.rules
find:
paths: /etc/audit/
contains: ^\s*-w\s+/var/log/lastlog\s+-p\s+wa(\s|$)+
patterns: audit.rules
register: find_existing_watch_audit_rules
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010171
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.3
- audit_rules_login_events_lastlog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /var/log/lastlog in /etc/audit/audit.rules
lineinfile:
line: -w /var/log/lastlog -p wa -k logins
state: present
dest: /etc/audit/audit.rules
create: true
mode: '0640'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
== 0
tags:
- DISA-STIG-UBTU-20-010171
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.3
- audit_rules_login_events_lastlog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "^[\s]*-w[\s]+/var/log/lastlog" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/log/lastlog $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "wa" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s#\($sp*-w$sp\+/var/log/lastlog$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w /var/log/lastlog -p wa -k logins" >> "$audit_rules_file"
fi
done
# 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 the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/logins.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/var/log/lastlog" /etc/audit/rules.d/*.rules)
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
# Append '/etc/audit/rules.d/logins.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/logins.rules"
# If the logins.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0640 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "^[\s]*-w[\s]+/var/log/lastlog" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/log/lastlog $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "wa" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s#\($sp*-w$sp\+/var/log/lastlog$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w /var/log/lastlog -p wa -k logins" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Attempts to Alter Logon and Logout Events - tallylog
[ref]ruleThe audit system already collects login information 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 lines to a file with suffix .rules in the
directory /etc/audit/rules.d in order to watch for attempted manual
edits of files involved in storing logon events:
-w /var/log/tallylog -p wa -k logins
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file in order to watch for unattempted manual
edits of files involved in storing logon events:
-w /var/log/tallylog -p wa -k logins Rationale:Manual editing of these files may indicate nefarious activity, such
as an attacker attempting to remove evidence of an intrusion. References:
4.1.7, 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-000172, CCI-002884, CCI-000126, 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-2(d), AU-12(c), AC-6(9), 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.3, SRG-OS-000392-GPOS-00172, SRG-OS-000470-GPOS-00214, SRG-OS-000473-GPOS-00218, UBTU-20-010169, SRG-OS-000473-VMM-001930, SRG-OS-000470-VMM-001900 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010169
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.3
- audit_rules_login_events_tallylog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /var/log/tallylog already exists in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: ^\s*-w\s+/var/log/tallylog\s+-p\s+wa(\s|$)+
patterns: '*.rules'
register: find_existing_watch_rules_d
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010169
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.3
- audit_rules_login_events_tallylog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Search /etc/audit/rules.d for other rules with specified key logins
find:
paths: /etc/audit/rules.d
contains: ^.*(?:-F key=|-k\s+)logins$
patterns: '*.rules'
register: find_watch_key
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- DISA-STIG-UBTU-20-010169
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.3
- audit_rules_login_events_tallylog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use /etc/audit/rules.d/logins.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/logins.rules
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- DISA-STIG-UBTU-20-010169
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.3
- audit_rules_login_events_tallylog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- DISA-STIG-UBTU-20-010169
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.3
- audit_rules_login_events_tallylog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /var/log/tallylog in /etc/audit/rules.d/
lineinfile:
path: '{{ all_files[0] }}'
line: -w /var/log/tallylog -p wa -k logins
create: true
mode: '0640'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- DISA-STIG-UBTU-20-010169
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.3
- audit_rules_login_events_tallylog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /var/log/tallylog already exists in /etc/audit/audit.rules
find:
paths: /etc/audit/
contains: ^\s*-w\s+/var/log/tallylog\s+-p\s+wa(\s|$)+
patterns: audit.rules
register: find_existing_watch_audit_rules
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010169
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.3
- audit_rules_login_events_tallylog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /var/log/tallylog in /etc/audit/audit.rules
lineinfile:
line: -w /var/log/tallylog -p wa -k logins
state: present
dest: /etc/audit/audit.rules
create: true
mode: '0640'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
== 0
tags:
- DISA-STIG-UBTU-20-010169
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.3
- audit_rules_login_events_tallylog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "^[\s]*-w[\s]+/var/log/tallylog" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/log/tallylog $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "wa" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s#\($sp*-w$sp\+/var/log/tallylog$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w /var/log/tallylog -p wa -k logins" >> "$audit_rules_file"
fi
done
# 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 the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/logins.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/var/log/tallylog" /etc/audit/rules.d/*.rules)
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
# Append '/etc/audit/rules.d/logins.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/logins.rules"
# If the logins.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0640 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "^[\s]*-w[\s]+/var/log/tallylog" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/log/tallylog $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "wa" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s#\($sp*-w$sp\+/var/log/tallylog$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w /var/log/tallylog -p wa -k logins" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Information on the Use of Privileged Commands
[ref]groupAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. |
contains 21 rules |
Record Any Attempts to Run apparmor_parser
[ref]ruleAt a minimum, the audit system should collect any execution attempt
of the apparmor_parser command 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 lines to a file with suffix .rules in the
directory /etc/audit/rules.d :
-a always,exit -F path=/sbin/apparmor_parser -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following
lines to /etc/audit/audit.rules file:
-a always,exit -F path=/sbin/apparmor_parser -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged Rationale:Without generating audit records that are specific to the security and
mission needs of the organization, it would be difficult to establish,
correlate, and investigate the events relating to an incident or identify
those responsible for one.
Audit records can be generated from various components within the
information system (e.g., module or policy filter). Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010166
- audit_rules_privileged_commands_apparmor_parser
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /sbin/apparmor_parser
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/sbin/apparmor_parser -F perm=x -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/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.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)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/sbin/apparmor_parser -F perm=x
-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{{ syscalls | join(',') }} -F path=/sbin/apparmor_parser
-F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/sbin/apparmor_parser -F perm=x -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)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/sbin/apparmor_parser -F perm=x -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{{ syscalls | join(',') }} -F path=/sbin/apparmor_parser
-F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010166
- audit_rules_privileged_commands_apparmor_parser
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/sbin/apparmor_parser -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# 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
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - chage
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands 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 a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/chage -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/chage -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
4.1.11, 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, 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.3.2.6.7, 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 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, CIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3, AC-2(4), AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, 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-000468-GPOS-00212, SRG-OS-000471-GPOS-00215, UBTU-20-010175, SRG-OS-000471-VMM-001910 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010175
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_chage
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/chage
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/chage -F perm=x -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/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.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)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/chage -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/bin/chage -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/chage -F perm=x -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)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/chage -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/bin/chage -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010175
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_chage
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/chage -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# 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
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - chfn
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands 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 a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/chfn -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/chfn -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged Rationale:Without generating audit records that are specific to the security and
mission needs of the organization, it would be difficult to establish,
correlate, and investigate the events relating to an incident or identify
those responsible for one.
Audit records can be generated from various components within the
information system (e.g., module or policy filter). Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010137
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-3
- NIST-800-53-MA-4(1)(a)
- audit_rules_privileged_commands_chfn
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/chfn
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/chfn -F perm=x -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/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.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)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/chfn -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/bin/chfn -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/chfn -F perm=x -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)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/chfn -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/bin/chfn -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010137
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-3
- NIST-800-53-MA-4(1)(a)
- audit_rules_privileged_commands_chfn
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/chfn -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# 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
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - chsh
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands 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 a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/chsh -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/chsh -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
4.1.11, 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, 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.3.2.6.7, 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 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, CIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3, AC-2(4), AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, 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, UBTU-20-010163, SRG-OS-000471-VMM-001910 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010163
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_chsh
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/chsh
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/chsh -F perm=x -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/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.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)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/chsh -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/bin/chsh -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/chsh -F perm=x -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)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/chsh -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/bin/chsh -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010163
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_chsh
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/chsh -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# 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
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - crontab
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands 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 a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/crontab -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/crontab -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
4.1.11, 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, 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.3.2.6.7, 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 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, 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, UBTU-20-010177, SRG-OS-000471-VMM-001910 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010177
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_crontab
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/crontab
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/crontab -F perm=x -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/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.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)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/crontab -F perm=x
-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{{ syscalls | join(',') }} -F path=/usr/bin/crontab -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/crontab -F perm=x -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)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/crontab -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/bin/crontab -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010177
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_crontab
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/crontab -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# 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
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - fdisk
[ref]ruleConfigure the operating system to audit the execution of the partition
management program "fdisk". Rationale:Without generating audit records that are specific to the security
and mission needs of the organization, it would be difficult to
establish, correlate, and investigate the events relating to an
incident or identify those responsible for one.
Audit records can be generated from various components within the
information system (e.g., module or policy filter). Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "^[\s]*-w[\s]+/sbin/fdisk" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s#$sp*-w$sp\+/sbin/fdisk $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "x" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s#\($sp*-w$sp\+/sbin/fdisk$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w /sbin/fdisk -p x -k modules" >> "$audit_rules_file"
fi
done
# 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 the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/modules.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/sbin/fdisk" /etc/audit/rules.d/*.rules)
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
# Append '/etc/audit/rules.d/modules.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/modules.rules"
# If the modules.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0640 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "^[\s]*-w[\s]+/sbin/fdisk" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s#$sp*-w$sp\+/sbin/fdisk $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "x" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s#\($sp*-w$sp\+/sbin/fdisk$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w /sbin/fdisk -p x -k modules" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - gpasswd
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands 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 a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/gpasswd -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/gpasswd -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
4.1.11, 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, 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.3.2.6.7, 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 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, CIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3, AC-2(4), AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, FAU_GEN.1.1.c, 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, UBTU-20-010174, SRG-OS-000471-VMM-001910 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010174
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_gpasswd
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/gpasswd
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/gpasswd -F perm=x -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/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.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)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/gpasswd -F perm=x
-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{{ syscalls | join(',') }} -F path=/usr/bin/gpasswd -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/gpasswd -F perm=x -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)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/gpasswd -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/bin/gpasswd -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010174
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_gpasswd
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/gpasswd -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# 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
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - kmod
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands 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 a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-w /bin/kmod -p x -k modules
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-w /bin/kmod -p x -k modules Rationale:Without generating audit records that are specific to the security and
mission needs of the organization, it would be difficult to establish,
correlate, and investigate the events relating to an incident or identify
those responsible for one.
Audit records can be generated from various components within the
information system (e.g., module or policy filter). References:
CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884, AU-3, AU-3.1, AU-12(a), AU-12.1(ii), AU-12.1(iv)AU-12(c), MA-4(1)(a), 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-000471-GPOS-00216, SRG-OS-000477-GPOS-00222, UBTU-20-010297 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010297
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)AU-12(c)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- audit_rules_privileged_commands_kmod
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /bin/kmod
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/bin/kmod -F perm=x -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/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.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)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/bin/kmod -F perm=x -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{{ syscalls | join(',') }} -F path=/bin/kmod -F perm=x -F
auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/bin/kmod -F perm=x -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)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/bin/kmod -F perm=x -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{{ syscalls | join(',') }} -F path=/bin/kmod -F perm=x -F
auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010297
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)AU-12(c)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- audit_rules_privileged_commands_kmod
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/bin/kmod -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# 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
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - modprobe
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands 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 a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-w /sbin/modprobe -p x -k modules
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-w /sbin/modprobe -p x -k modules Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
4.1.16, CCI-000130, CCI-000169, CCI-000172, CCI-002884, AU-12(a), AU-12.1(ii), AU-3, AU-3.1, AU-12(c), AU-12.1(iv), MA-4(1)(a), SRG-OS-000037-GPOS-00015, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, UBTU-20-010296 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "^[\s]*-w[\s]+/sbin/modprobe" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s#$sp*-w$sp\+/sbin/modprobe $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "x" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s#\($sp*-w$sp\+/sbin/modprobe$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w /sbin/modprobe -p x -k modules" >> "$audit_rules_file"
fi
done
# 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 the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/modules.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/sbin/modprobe" /etc/audit/rules.d/*.rules)
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
# Append '/etc/audit/rules.d/modules.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/modules.rules"
# If the modules.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0640 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "^[\s]*-w[\s]+/sbin/modprobe" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s#$sp*-w$sp\+/sbin/modprobe $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "x" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s#\($sp*-w$sp\+/sbin/modprobe$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w /sbin/modprobe -p x -k modules" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - mount
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands 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 a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/mount -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/mount -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
4.1.11, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884, AU-2(d), AU-12(c), AC-6(9), CM-6(a), FAU_GEN.1.1.c, 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, UBTU-20-010138, SRG-OS-000471-VMM-001910 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010138
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_mount
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/mount
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/mount -F perm=x -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/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.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)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/mount -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/bin/mount -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/mount -F perm=x -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)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/mount -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/bin/mount -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010138
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_mount
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/mount -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# 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
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - newgrp
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands 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 a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/newgrp -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/newgrp -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
4.1.11, 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000130, CCI-000169, CCI-000135, 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.3.2.6.7, 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 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, CIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3, AC-2(4), AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, FAU_GEN.1.1.c, 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, UBTU-20-010164, SRG-OS-000471-VMM-001910 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010164
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_newgrp
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/newgrp
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/newgrp -F perm=x -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/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.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)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/newgrp -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/bin/newgrp -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/newgrp -F perm=x -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)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/newgrp -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/bin/newgrp -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010164
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_newgrp
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/newgrp -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# 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
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - pam_timestamp_check
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands 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 a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/sbin/pam_timestamp_check
-F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/sbin/pam_timestamp_check
-F perm=x -F auid>=1000 -F auid!=unset -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, 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.3.2.6.7, 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 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, 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, UBTU-20-010178, SRG-OS-000471-VMM-001910 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010178
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_pam_timestamp_check
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/sbin/pam_timestamp_check
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/sbin/pam_timestamp_check -F perm=x -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/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.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)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/sbin/pam_timestamp_check
-F perm=x -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{{ syscalls | join(',') }} -F path=/usr/sbin/pam_timestamp_check
-F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/sbin/pam_timestamp_check -F perm=x -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)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/sbin/pam_timestamp_check -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/sbin/pam_timestamp_check
-F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010178
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_pam_timestamp_check
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/sbin/pam_timestamp_check -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# 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
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - passwd
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands 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 a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/passwd -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/passwd -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, 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.3.2.6.7, 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 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, CIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3, AC-2(4), AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, FAU_GEN.1.1.c, 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, UBTU-20-010172, SRG-OS-000471-VMM-001910 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010172
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_passwd
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/passwd
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/passwd -F perm=x -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/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.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)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/passwd -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/bin/passwd -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/passwd -F perm=x -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)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/passwd -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/bin/passwd -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010172
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_passwd
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/passwd -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# 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
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Any Attempts to Run ssh-agent
[ref]ruleAt a minimum, the audit system should collect any execution attempt
of the ssh-agent command 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 lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/ssh-agent -F perm=x -F auid>=1000 -F auid!=unset -k privileged-ssh-agent
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F path=/usr/bin/ssh-agent -F perm=x -F auid>=1000 -F auid!=unset -k privileged-ssh-agent Rationale:Without generating audit records that are specific to the security and
mission needs of the organization, it would be difficult to establish,
correlate, and investigate the events relating to an incident or identify
those responsible for one.
Audit records can be generated from various components within the
information system (e.g., module or policy filter). References:
4.1.11, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884, 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, UBTU-20-010140 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010140
- audit_rules_privileged_commands_ssh_agent
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/ssh-agent
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/ssh-agent -F perm=x -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/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.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)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/ssh-agent -F perm=x
-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{{ syscalls | join(',') }} -F path=/usr/bin/ssh-agent -F
perm=x -F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/ssh-agent -F perm=x -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)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/ssh-agent -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/bin/ssh-agent -F
perm=x -F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010140
- audit_rules_privileged_commands_ssh_agent
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/ssh-agent -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# 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
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - ssh-keysign
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands 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 a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/libexec/openssh/ssh-keysign -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/libexec/openssh/ssh-keysign -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
4.1.11, 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, 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.3.2.6.7, 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 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, FAU_GEN.1.1.c, 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, UBTU-20-010141, SRG-OS-000471-VMM-001910 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010141
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_ssh_keysign
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/libexec/openssh/ssh-keysign
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/libexec/openssh/ssh-keysign -F perm=x -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/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.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)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/libexec/openssh/ssh-keysign
-F perm=x -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{{ syscalls | join(',') }} -F path=/usr/libexec/openssh/ssh-keysign
-F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/libexec/openssh/ssh-keysign -F perm=x -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)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/libexec/openssh/ssh-keysign -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/libexec/openssh/ssh-keysign
-F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010141
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_ssh_keysign
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/libexec/openssh/ssh-keysign -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# 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
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - su
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands 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 a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/su -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/su -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
4.1.11, 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, 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.3.2.6.7, 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 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, FAU_GEN.1.1.c, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000064-GPOS-0003, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000466-GPOS-00210, UBTU-20-010136, SRG-OS-000471-VMM-001910 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010136
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_su
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/su
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/su -F perm=x -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/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.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)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/su -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/bin/su -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/su -F perm=x -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)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/su -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/bin/su -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010136
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_su
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/su -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# 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
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - sudo
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands 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 a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/sudo -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/sudo -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
BP28(R19), 4.1.11, 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, 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.3.2.6.7, 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 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, FAU_GEN.1.1.c, 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, UBTU-20-010161, SRG-OS-000471-VMM-001910 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010161
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_sudo
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/sudo
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/sudo -F perm=x -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/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.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)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/sudo -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/bin/sudo -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/sudo -F perm=x -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)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/sudo -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/bin/sudo -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010161
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_sudo
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/sudo -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# 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
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - sudoedit
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands 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 a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/sudoedit -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/sudoedit -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
4.1.11, 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, 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.3.2.6.7, 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 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, FAU_GEN.1.1.c, 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, UBTU-20-010162, SRG-OS-000471-VMM-001910 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010162
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_sudoedit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/sudoedit
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/sudoedit -F perm=x -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/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.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)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/sudoedit -F perm=x
-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{{ syscalls | join(',') }} -F path=/usr/bin/sudoedit -F
perm=x -F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/sudoedit -F perm=x -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)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/sudoedit -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/bin/sudoedit -F
perm=x -F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010162
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_sudoedit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/sudoedit -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# 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
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - umount
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands 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 a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/umount -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/umount -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
4.1.11, 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000130, CCI-000169, CCI-000135, 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.3.2.6.7, 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 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, 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, UBTU-20-010139, SRG-OS-000471-VMM-001910 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010139
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_umount
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/umount
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/umount -F perm=x -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/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.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)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/umount -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/bin/umount -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/umount -F perm=x -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)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/umount -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/bin/umount -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010139
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_umount
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/umount -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# 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
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - unix_update
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands 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 a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/sbin/unix_update -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/sbin/unix_update -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, UBTU-20-010173 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010173
- audit_rules_privileged_commands_unix_update
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/sbin/unix_update
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/sbin/unix_update -F perm=x -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/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.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)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/sbin/unix_update -F perm=x
-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{{ syscalls | join(',') }} -F path=/usr/sbin/unix_update
-F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/sbin/unix_update -F perm=x -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)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/sbin/unix_update -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/sbin/unix_update
-F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010173
- audit_rules_privileged_commands_unix_update
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/sbin/unix_update -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# 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
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - usermod
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands 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 a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/sbin/usermod -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/sbin/usermod -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884, 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, UBTU-20-010176 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010176
- audit_rules_privileged_commands_usermod
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/sbin/usermod
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/sbin/usermod -F perm=x -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/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.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)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/sbin/usermod -F perm=x
-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{{ syscalls | join(',') }} -F path=/usr/sbin/usermod -F
perm=x -F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/sbin/usermod -F perm=x -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)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/sbin/usermod -F perm=x -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{{ syscalls | join(',') }} -F path=/usr/sbin/usermod -F
perm=x -F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010176
- audit_rules_privileged_commands_usermod
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/sbin/usermod -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# 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
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Attempts to Alter Process and Session Initiation Information btmp
[ref]ruleThe audit system already collects process information 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 lines to a file with suffix .rules in the
directory /etc/audit/rules.d in order to watch for attempted manual
edits of files involved in storing such process information:
-w /var/log/btmp -p wa -k session
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file in order to watch for attempted manual
edits of files involved in storing such process information:
-w /var/log/btmp -p wa -k session Rationale:Manual editing of these files may indicate nefarious activity, such
as an attacker attempting to remove evidence of an intrusion. Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "^[\s]*-w[\s]+/var/log/btmp" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/log/btmp $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "wa" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s#\($sp*-w$sp\+/var/log/btmp$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w /var/log/btmp -p wa -k session" >> "$audit_rules_file"
fi
done
# 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 the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/session.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/var/log/btmp" /etc/audit/rules.d/*.rules)
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
# Append '/etc/audit/rules.d/session.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/session.rules"
# If the session.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0640 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "^[\s]*-w[\s]+/var/log/btmp" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/log/btmp $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "wa" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s#\($sp*-w$sp\+/var/log/btmp$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w /var/log/btmp -p wa -k session" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Attempts to Alter Process and Session Initiation Information utmp
[ref]ruleThe audit system already collects process information 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 lines to a file with suffix .rules in the
directory /etc/audit/rules.d in order to watch for attempted manual
edits of files involved in storing such process information:
-w /run/utmp -p wa -k session
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file in order to watch for attempted manual
edits of files involved in storing such process information:
-w /run/utmp -p wa -k session Rationale:Manual editing of these files may indicate nefarious activity, such
as an attacker attempting to remove evidence of an intrusion. Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "^[\s]*-w[\s]+/run/utmp" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s#$sp*-w$sp\+/run/utmp $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "wa" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s#\($sp*-w$sp\+/run/utmp$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w /run/utmp -p wa -k session" >> "$audit_rules_file"
fi
done
# 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 the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/session.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/run/utmp" /etc/audit/rules.d/*.rules)
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
# Append '/etc/audit/rules.d/session.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/session.rules"
# If the session.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0640 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "^[\s]*-w[\s]+/run/utmp" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s#$sp*-w$sp\+/run/utmp $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "wa" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s#\($sp*-w$sp\+/run/utmp$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w /run/utmp -p wa -k session" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Attempts to Alter Process and Session Initiation Information wtmp
[ref]ruleThe audit system already collects process information 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 lines to a file with suffix .rules in the
directory /etc/audit/rules.d in order to watch for attempted manual
edits of files involved in storing such process information:
-w /var/log/wtmp -p wa -k session
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file in order to watch for attempted manual
edits of files involved in storing such process information:
-w /var/log/wtmp -p wa -k session Rationale:Manual editing of these files may indicate nefarious activity, such
as an attacker attempting to remove evidence of an intrusion. Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "^[\s]*-w[\s]+/var/log/wtmp" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/log/wtmp $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "wa" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s#\($sp*-w$sp\+/var/log/wtmp$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w /var/log/wtmp -p wa -k session" >> "$audit_rules_file"
fi
done
# 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 the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/session.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/var/log/wtmp" /etc/audit/rules.d/*.rules)
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
# Append '/etc/audit/rules.d/session.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/session.rules"
# If the session.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0640 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "^[\s]*-w[\s]+/var/log/wtmp" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/log/wtmp $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "wa" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s#\($sp*-w$sp\+/var/log/wtmp$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w /var/log/wtmp -p wa -k session" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Events When Privileged Executables Are Run
[ref]ruleVerify the system generates an audit record when privileged functions are executed.
If audit is using the "auditctl" tool to load the rules, run the following command:
$ sudo grep execve /etc/audit/audit.rules
If audit is using the "augenrules" tool to load the rules, run the following command:
$ sudo grep -r execve /etc/audit/rules.d
-a always,exit -F arch=b32 -S execve -C uid!=euid -F euid=0 -k setuid
-a always,exit -F arch=b64 -S execve -C uid!=euid -F euid=0 -k setuid
-a always,exit -F arch=b32 -S execve -C gid!=egid -F egid=0 -k setgid
-a always,exit -F arch=b64 -S execve -C gid!=egid -F egid=0 -k setgid
If both the "b32" and "b64" audit rules for "SUID" files are not defined, this is a finding.
If both the "b32" and "b64" audit rules for "SGID" files are not defined, this is a finding.Warning:
Note that these rules can be configured in a
number of ways while still achieving the desired effect. Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have
compromised information system accounts, is a serious and ongoing concern
and can have significant adverse impacts on organizations. Auditing the use
of privileged functions is one way to detect such misuse and identify the
risk from insider threats and the advanced persistent threat. References:
4.1.15, CCI-001814, CCI-001882, CCI-001889, CCI-001880, CCI-001881, CCI-001878, CCI-001879, CCI-001875, CCI-001877, CCI-001914, CCI-002233, CCI-002234, CM-5(1), AU-7(a), AU-7(b), AU-8(b), AU-12(3), AC-6(9), SRG-OS-000326-GPOS-00126, SRG-OS-000327-GPOS-00127, UBTU-20-010211 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; 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="-C uid!=euid -F euid=0"
AUID_FILTERS=""
SYSCALL="execve"
KEY="setuid"
SYSCALL_GROUPING=""
# 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
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS="-C gid!=egid -F egid=0"
AUID_FILTERS=""
SYSCALL="execve"
KEY="setgid"
SYSCALL_GROUPING=""
# 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
|
Record Events that Modify User/Group Information - /etc/group
[ref]ruleIf the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following lines to a file with suffix .rules in the
directory /etc/audit/rules.d , in order to capture events that modify
account changes:
-w /etc/group -p wa -k audit_rules_usergroup_modification
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file, in order to capture events that modify
account changes:
-w /etc/group -p wa -k audit_rules_usergroup_modification Rationale:In addition to auditing new user and group accounts, these watches
will alert the system administrator(s) to any modifications. Any unexpected
users, groups, or modifications should be investigated for legitimacy. References:
4.1.4, 1, 11, 12, 13, 14, 15, 16, 18, 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, DSS06.03, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000018, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-001403, CCI-001404, CCI-001405, CCI-001683, CCI-001684, CCI-001685, CCI-001686, CCI-002130, CCI-002132, 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.2.2, 4.3.3.3.9, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.8, 4.3.3.6.6, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 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.1, SR 1.13, 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 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.1.2, A.6.2.1, A.6.2.2, A.7.1.1, A.9.1.2, 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.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5, CIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3, AC-2(4), AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-1, PR.AC-3, PR.AC-4, PR.AC-6, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1.1.c, Req-10.2.5, SRG-OS-000004-GPOS-00004, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000304-GPOS-00121, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000470-GPOS-00214, SRG-OS-000471-GPOS-00215, SRG-OS-000239-GPOS-00089, SRG-OS-000240-GPOS-00090, SRG-OS-000241-GPOS-00091, SRG-OS-000303-GPOS-00120, SRG-OS-000466-GPOS-00210, SRG-OS-000476-GPOS-00221, UBTU-20-010101, SRG-OS-000004-VMM-000040, SRG-OS-000239-VMM-000810, SRG-OS-000240-VMM-000820, SRG-OS-000241-VMM-000830, SRG-OS-000274-VMM-000960, SRG-OS-000275-VMM-000970, SRG-OS-000276-VMM-000980, SRG-OS-000277-VMM-000990, SRG-OS-000303-VMM-001090, SRG-OS-000304-VMM-001100, SRG-OS-000476-VMM-001960 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010101
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_group
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /etc/group already exists in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: ^\s*-w\s+/etc/group\s+-p\s+wa(\s|$)+
patterns: '*.rules'
register: find_existing_watch_rules_d
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010101
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_group
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Search /etc/audit/rules.d for other rules with specified key audit_rules_usergroup_modification
find:
paths: /etc/audit/rules.d
contains: ^.*(?:-F key=|-k\s+)audit_rules_usergroup_modification$
patterns: '*.rules'
register: find_watch_key
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010101
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_group
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use /etc/audit/rules.d/audit_rules_usergroup_modification.rules as the recipient
for the rule
set_fact:
all_files:
- /etc/audit/rules.d/audit_rules_usergroup_modification.rules
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010101
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_group
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010101
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_group
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /etc/group in /etc/audit/rules.d/
lineinfile:
path: '{{ all_files[0] }}'
line: -w /etc/group -p wa -k audit_rules_usergroup_modification
create: true
mode: '0640'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010101
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_group
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /etc/group already exists in /etc/audit/audit.rules
find:
paths: /etc/audit/
contains: ^\s*-w\s+/etc/group\s+-p\s+wa(\s|$)+
patterns: audit.rules
register: find_existing_watch_audit_rules
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010101
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_group
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /etc/group in /etc/audit/audit.rules
lineinfile:
line: -w /etc/group -p wa -k audit_rules_usergroup_modification
state: present
dest: /etc/audit/audit.rules
create: true
mode: '0640'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010101
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_group
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "^[\s]*-w[\s]+/etc/group" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/group $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "wa" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s#\($sp*-w$sp\+/etc/group$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w /etc/group -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
fi
done
# 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 the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/group" /etc/audit/rules.d/*.rules)
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
# Append '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification.rules"
# If the audit_rules_usergroup_modification.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0640 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "^[\s]*-w[\s]+/etc/group" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/group $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "wa" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s#\($sp*-w$sp\+/etc/group$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w /etc/group -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Events that Modify User/Group Information - /etc/gshadow
[ref]ruleIf the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following lines to a file with suffix .rules in the
directory /etc/audit/rules.d , in order to capture events that modify
account changes:
-w /etc/gshadow -p wa -k audit_rules_usergroup_modification
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file, in order to capture events that modify
account changes:
-w /etc/gshadow -p wa -k audit_rules_usergroup_modification Rationale:In addition to auditing new user and group accounts, these watches
will alert the system administrator(s) to any modifications. Any unexpected
users, groups, or modifications should be investigated for legitimacy. References:
4.1.4, 1, 11, 12, 13, 14, 15, 16, 18, 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, DSS06.03, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000018, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-001403, CCI-001404, CCI-001405, CCI-001683, CCI-001684, CCI-001685, CCI-001686, CCI-002130, CCI-002132, 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.2.2, 4.3.3.3.9, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.8, 4.3.3.6.6, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 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.1, SR 1.13, 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 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.1.2, A.6.2.1, A.6.2.2, A.7.1.1, A.9.1.2, 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.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5, CIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3, AC-2(4), AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-1, PR.AC-3, PR.AC-4, PR.AC-6, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1.1.c, Req-10.2.5, SRG-OS-000004-GPOS-00004, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000304-GPOS-00121, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000470-GPOS-00214, SRG-OS-000471-GPOS-00215, SRG-OS-000239-GPOS-00089, SRG-OS-000240-GPOS-00090, SRG-OS-000241-GPOS-00091, SRG-OS-000303-GPOS-00120, SRG-OS-000466-GPOS-00210, SRG-OS-000476-GPOS-00221, UBTU-20-010103, SRG-OS-000004-VMM-000040, SRG-OS-000239-VMM-000810, SRG-OS-000240-VMM-000820, SRG-OS-000241-VMM-000830, SRG-OS-000274-VMM-000960, SRG-OS-000275-VMM-000970, SRG-OS-000276-VMM-000980, SRG-OS-000277-VMM-000990, SRG-OS-000303-VMM-001090, SRG-OS-000304-VMM-001100, SRG-OS-000476-VMM-001960 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010103
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_gshadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /etc/gshadow already exists in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: ^\s*-w\s+/etc/gshadow\s+-p\s+wa(\s|$)+
patterns: '*.rules'
register: find_existing_watch_rules_d
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010103
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_gshadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Search /etc/audit/rules.d for other rules with specified key audit_rules_usergroup_modification
find:
paths: /etc/audit/rules.d
contains: ^.*(?:-F key=|-k\s+)audit_rules_usergroup_modification$
patterns: '*.rules'
register: find_watch_key
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010103
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_gshadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use /etc/audit/rules.d/audit_rules_usergroup_modification.rules as the recipient
for the rule
set_fact:
all_files:
- /etc/audit/rules.d/audit_rules_usergroup_modification.rules
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010103
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_gshadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010103
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_gshadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /etc/gshadow in /etc/audit/rules.d/
lineinfile:
path: '{{ all_files[0] }}'
line: -w /etc/gshadow -p wa -k audit_rules_usergroup_modification
create: true
mode: '0640'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010103
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_gshadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /etc/gshadow already exists in /etc/audit/audit.rules
find:
paths: /etc/audit/
contains: ^\s*-w\s+/etc/gshadow\s+-p\s+wa(\s|$)+
patterns: audit.rules
register: find_existing_watch_audit_rules
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010103
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_gshadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /etc/gshadow in /etc/audit/audit.rules
lineinfile:
line: -w /etc/gshadow -p wa -k audit_rules_usergroup_modification
state: present
dest: /etc/audit/audit.rules
create: true
mode: '0640'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010103
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_gshadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "^[\s]*-w[\s]+/etc/gshadow" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/gshadow $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "wa" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s#\($sp*-w$sp\+/etc/gshadow$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w /etc/gshadow -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
fi
done
# 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 the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/gshadow" /etc/audit/rules.d/*.rules)
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
# Append '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification.rules"
# If the audit_rules_usergroup_modification.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0640 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "^[\s]*-w[\s]+/etc/gshadow" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/gshadow $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "wa" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s#\($sp*-w$sp\+/etc/gshadow$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w /etc/gshadow -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Events that Modify User/Group Information - /etc/security/opasswd
[ref]ruleIf the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following lines to a file with suffix .rules in the
directory /etc/audit/rules.d , in order to capture events that modify
account changes:
-w /etc/security/opasswd -p wa -k audit_rules_usergroup_modification
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file, in order to capture events that modify
account changes:
-w /etc/security/opasswd -p wa -k audit_rules_usergroup_modification Rationale:In addition to auditing new user and group accounts, these watches
will alert the system administrator(s) to any modifications. Any unexpected
users, groups, or modifications should be investigated for legitimacy. References:
4.1.4, 1, 11, 12, 13, 14, 15, 16, 18, 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, DSS06.03, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000018, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-001403, CCI-001404, CCI-001405, CCI-001683, CCI-001684, CCI-001685, CCI-001686, CCI-002130, CCI-002132, 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.2.2, 4.3.3.3.9, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.8, 4.3.3.6.6, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 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.1, SR 1.13, 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 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.1.2, A.6.2.1, A.6.2.2, A.7.1.1, A.9.1.2, 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.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5, CIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3, AC-2(4), AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-1, PR.AC-3, PR.AC-4, PR.AC-6, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1.1.c, Req-10.2.5, SRG-OS-000004-GPOS-00004, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000304-GPOS-00121, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000470-GPOS-00214, SRG-OS-000471-GPOS-00215, SRG-OS-000239-GPOS-00089, SRG-OS-000240-GPOS-00090, SRG-OS-000241-GPOS-00091, SRG-OS-000303-GPOS-00120, SRG-OS-000466-GPOS-00210, SRG-OS-000476-GPOS-00221, UBTU-20-010104, SRG-OS-000004-VMM-000040, SRG-OS-000239-VMM-000810, SRG-OS-000240-VMM-000820, SRG-OS-000241-VMM-000830, SRG-OS-000274-VMM-000960, SRG-OS-000275-VMM-000970, SRG-OS-000276-VMM-000980, SRG-OS-000277-VMM-000990, SRG-OS-000303-VMM-001090, SRG-OS-000304-VMM-001100, SRG-OS-000476-VMM-001960 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010104
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_opasswd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /etc/security/opasswd already exists in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: ^\s*-w\s+/etc/security/opasswd\s+-p\s+wa(\s|$)+
patterns: '*.rules'
register: find_existing_watch_rules_d
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010104
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_opasswd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Search /etc/audit/rules.d for other rules with specified key audit_rules_usergroup_modification
find:
paths: /etc/audit/rules.d
contains: ^.*(?:-F key=|-k\s+)audit_rules_usergroup_modification$
patterns: '*.rules'
register: find_watch_key
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010104
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_opasswd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use /etc/audit/rules.d/audit_rules_usergroup_modification.rules as the recipient
for the rule
set_fact:
all_files:
- /etc/audit/rules.d/audit_rules_usergroup_modification.rules
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010104
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_opasswd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010104
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_opasswd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /etc/security/opasswd in /etc/audit/rules.d/
lineinfile:
path: '{{ all_files[0] }}'
line: -w /etc/security/opasswd -p wa -k audit_rules_usergroup_modification
create: true
mode: '0640'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010104
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_opasswd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /etc/security/opasswd already exists in /etc/audit/audit.rules
find:
paths: /etc/audit/
contains: ^\s*-w\s+/etc/security/opasswd\s+-p\s+wa(\s|$)+
patterns: audit.rules
register: find_existing_watch_audit_rules
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010104
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_opasswd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /etc/security/opasswd in /etc/audit/audit.rules
lineinfile:
line: -w /etc/security/opasswd -p wa -k audit_rules_usergroup_modification
state: present
dest: /etc/audit/audit.rules
create: true
mode: '0640'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010104
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_opasswd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "^[\s]*-w[\s]+/etc/security/opasswd" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/security/opasswd $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "wa" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s#\($sp*-w$sp\+/etc/security/opasswd$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w /etc/security/opasswd -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
fi
done
# 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 the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/security/opasswd" /etc/audit/rules.d/*.rules)
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
# Append '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification.rules"
# If the audit_rules_usergroup_modification.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0640 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "^[\s]*-w[\s]+/etc/security/opasswd" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/security/opasswd $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "wa" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s#\($sp*-w$sp\+/etc/security/opasswd$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w /etc/security/opasswd -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Events that Modify User/Group Information - /etc/passwd
[ref]ruleIf the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following lines to a file with suffix .rules in the
directory /etc/audit/rules.d , in order to capture events that modify
account changes:
-w /etc/passwd -p wa -k audit_rules_usergroup_modification
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file, in order to capture events that modify
account changes:
-w /etc/passwd -p wa -k audit_rules_usergroup_modification Rationale:In addition to auditing new user and group accounts, these watches
will alert the system administrator(s) to any modifications. Any unexpected
users, groups, or modifications should be investigated for legitimacy. References:
4.1.4, 1, 11, 12, 13, 14, 15, 16, 18, 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, DSS06.03, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000018, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-001403, CCI-001404, CCI-001405, CCI-001683, CCI-001684, CCI-001685, CCI-001686, CCI-002130, CCI-002132, 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.2.2, 4.3.3.3.9, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.8, 4.3.3.6.6, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 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.1, SR 1.13, 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 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.1.2, A.6.2.1, A.6.2.2, A.7.1.1, A.9.1.2, 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.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5, CIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3, AC-2(4), AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-1, PR.AC-3, PR.AC-4, PR.AC-6, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1.1.c, Req-10.2.5, SRG-OS-000004-GPOS-00004, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000304-GPOS-00121, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000470-GPOS-00214, SRG-OS-000471-GPOS-00215, SRG-OS-000239-GPOS-00089, SRG-OS-000240-GPOS-00090, SRG-OS-000241-GPOS-00091, SRG-OS-000303-GPOS-00120, SRG-OS-000304-GPOS-00121, SRG-OS-000466-GPOS-00210, SRG-OS-000476-GPOS-00221, SRG-OS-000274-GPOS-00104, SRG-OS-000275-GPOS-00105, SRG-OS-000276-GPOS-00106, SRG-OS-000277-GPOS-00107, UBTU-20-010100, SRG-OS-000004-VMM-000040, SRG-OS-000239-VMM-000810, SRG-OS-000240-VMM-000820, SRG-OS-000241-VMM-000830, SRG-OS-000274-VMM-000960, SRG-OS-000275-VMM-000970, SRG-OS-000276-VMM-000980, SRG-OS-000277-VMM-000990, SRG-OS-000303-VMM-001090, SRG-OS-000304-VMM-001100, SRG-OS-000476-VMM-001960 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010100
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_passwd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /etc/passwd already exists in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: ^\s*-w\s+/etc/passwd\s+-p\s+wa(\s|$)+
patterns: '*.rules'
register: find_existing_watch_rules_d
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010100
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_passwd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Search /etc/audit/rules.d for other rules with specified key audit_rules_usergroup_modification
find:
paths: /etc/audit/rules.d
contains: ^.*(?:-F key=|-k\s+)audit_rules_usergroup_modification$
patterns: '*.rules'
register: find_watch_key
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010100
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_passwd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use /etc/audit/rules.d/audit_rules_usergroup_modification.rules as the recipient
for the rule
set_fact:
all_files:
- /etc/audit/rules.d/audit_rules_usergroup_modification.rules
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010100
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_passwd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010100
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_passwd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /etc/passwd in /etc/audit/rules.d/
lineinfile:
path: '{{ all_files[0] }}'
line: -w /etc/passwd -p wa -k audit_rules_usergroup_modification
create: true
mode: '0640'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010100
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_passwd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /etc/passwd already exists in /etc/audit/audit.rules
find:
paths: /etc/audit/
contains: ^\s*-w\s+/etc/passwd\s+-p\s+wa(\s|$)+
patterns: audit.rules
register: find_existing_watch_audit_rules
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010100
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_passwd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /etc/passwd in /etc/audit/audit.rules
lineinfile:
line: -w /etc/passwd -p wa -k audit_rules_usergroup_modification
state: present
dest: /etc/audit/audit.rules
create: true
mode: '0640'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010100
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_passwd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "^[\s]*-w[\s]+/etc/passwd" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/passwd $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "wa" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s#\($sp*-w$sp\+/etc/passwd$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w /etc/passwd -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
fi
done
# 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 the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/passwd" /etc/audit/rules.d/*.rules)
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
# Append '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification.rules"
# If the audit_rules_usergroup_modification.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0640 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "^[\s]*-w[\s]+/etc/passwd" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/passwd $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "wa" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s#\($sp*-w$sp\+/etc/passwd$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w /etc/passwd -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Events that Modify User/Group Information - /etc/shadow
[ref]ruleIf the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following lines to a file with suffix .rules in the
directory /etc/audit/rules.d , in order to capture events that modify
account changes:
-w /etc/shadow -p wa -k audit_rules_usergroup_modification
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file, in order to capture events that modify
account changes:
-w /etc/shadow -p wa -k audit_rules_usergroup_modification Rationale:In addition to auditing new user and group accounts, these watches
will alert the system administrator(s) to any modifications. Any unexpected
users, groups, or modifications should be investigated for legitimacy. References:
4.1.4, 1, 11, 12, 13, 14, 15, 16, 18, 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, DSS06.03, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000018, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-001403, CCI-001404, CCI-001405, CCI-001683, CCI-001684, CCI-001685, CCI-001686, CCI-002130, CCI-002132, 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.2.2, 4.3.3.3.9, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.8, 4.3.3.6.6, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 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.1, SR 1.13, 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 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.1.2, A.6.2.1, A.6.2.2, A.7.1.1, A.9.1.2, 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.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5, CIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3, AC-2(4), AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-1, PR.AC-3, PR.AC-4, PR.AC-6, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1.1.c, Req-10.2.5, SRG-OS-000004-GPOS-00004, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000304-GPOS-00121, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000470-GPOS-00214, SRG-OS-000471-GPOS-00215, SRG-OS-000239-GPOS-00089, SRG-OS-000240-GPOS-00090, SRG-OS-000241-GPOS-00091, SRG-OS-000303-GPOS-00120, SRG-OS-000466-GPOS-00210, SRG-OS-000476-GPOS-00221, UBTU-20-010102, SRG-OS-000004-VMM-000040, SRG-OS-000239-VMM-000810, SRG-OS-000240-VMM-000820, SRG-OS-000241-VMM-000830, SRG-OS-000274-VMM-000960, SRG-OS-000275-VMM-000970, SRG-OS-000276-VMM-000980, SRG-OS-000277-VMM-000990, SRG-OS-000303-VMM-001090, SRG-OS-000304-VMM-001100, SRG-OS-000476-VMM-001960 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010102
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_shadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /etc/shadow already exists in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: ^\s*-w\s+/etc/shadow\s+-p\s+wa(\s|$)+
patterns: '*.rules'
register: find_existing_watch_rules_d
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010102
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_shadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Search /etc/audit/rules.d for other rules with specified key audit_rules_usergroup_modification
find:
paths: /etc/audit/rules.d
contains: ^.*(?:-F key=|-k\s+)audit_rules_usergroup_modification$
patterns: '*.rules'
register: find_watch_key
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010102
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_shadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use /etc/audit/rules.d/audit_rules_usergroup_modification.rules as the recipient
for the rule
set_fact:
all_files:
- /etc/audit/rules.d/audit_rules_usergroup_modification.rules
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010102
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_shadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010102
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_shadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /etc/shadow in /etc/audit/rules.d/
lineinfile:
path: '{{ all_files[0] }}'
line: -w /etc/shadow -p wa -k audit_rules_usergroup_modification
create: true
mode: '0640'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010102
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_shadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /etc/shadow already exists in /etc/audit/audit.rules
find:
paths: /etc/audit/
contains: ^\s*-w\s+/etc/shadow\s+-p\s+wa(\s|$)+
patterns: audit.rules
register: find_existing_watch_audit_rules
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010102
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_shadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /etc/shadow in /etc/audit/audit.rules
lineinfile:
line: -w /etc/shadow -p wa -k audit_rules_usergroup_modification
state: present
dest: /etc/audit/audit.rules
create: true
mode: '0640'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-20-010102
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- audit_rules_usergroup_modification_shadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "^[\s]*-w[\s]+/etc/shadow" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/shadow $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "wa" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s#\($sp*-w$sp\+/etc/shadow$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w /etc/shadow -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
fi
done
# 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 the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/shadow" /etc/audit/rules.d/*.rules)
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
# Append '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification.rules"
# If the audit_rules_usergroup_modification.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0640 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "^[\s]*-w[\s]+/etc/shadow" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/shadow $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "wa" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s#\($sp*-w$sp\+/etc/shadow$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w /etc/shadow -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Attempts to perform maintenance activities
[ref]ruleThe Ubuntu 20.04 operating system must generate audit records for
privileged activities, nonlocal maintenance, diagnostic sessions and
other system-level access.
Verify the operating system audits activities performed during nonlocal
maintenance and diagnostic sessions. Run the following command:
$ sudo auditctl -l | grep sudo.log
-w /var/log/sudo.log -p wa -k maintenance Rationale:If events associated with nonlocal administrative access or diagnostic
sessions are not logged, a major tool for assessing and investigating
attacks would not be available.
This requirement addresses auditing-related issues associated with
maintenance tools used specifically for diagnostic and repair actions
on organizational information systems.
Nonlocal maintenance and diagnostic activities are those activities
conducted by individuals communicating through a network, either an
external network (e.g., the internet) or an internal network. Local
maintenance and diagnostic activities are those activities carried
out by individuals physically present at the information system or
information system component and not communicating across a network
connection.
This requirement applies to hardware/software diagnostic test
equipment or tools. This requirement does not cover hardware/software
components that may support information system maintenance, yet are a
part of the system, for example, the software implementing "ping,"
"ls," "ipconfig," or the hardware and software implementing the
monitoring port of an Ethernet switch. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010244
- audit_sudo_log_events
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /var/log/sudo.log already exists in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: ^\s*-w\s+/var/log/sudo.log\s+-p\s+wa(\s|$)+
patterns: '*.rules'
register: find_existing_watch_rules_d
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010244
- audit_sudo_log_events
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Search /etc/audit/rules.d for other rules with specified key logins
find:
paths: /etc/audit/rules.d
contains: ^.*(?:-F key=|-k\s+)logins$
patterns: '*.rules'
register: find_watch_key
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- DISA-STIG-UBTU-20-010244
- audit_sudo_log_events
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use /etc/audit/rules.d/logins.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/logins.rules
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- DISA-STIG-UBTU-20-010244
- audit_sudo_log_events
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- DISA-STIG-UBTU-20-010244
- audit_sudo_log_events
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /var/log/sudo.log in /etc/audit/rules.d/
lineinfile:
path: '{{ all_files[0] }}'
line: -w /var/log/sudo.log -p wa -k logins
create: true
mode: '0640'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- DISA-STIG-UBTU-20-010244
- audit_sudo_log_events
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /var/log/sudo.log already exists in /etc/audit/audit.rules
find:
paths: /etc/audit/
contains: ^\s*-w\s+/var/log/sudo.log\s+-p\s+wa(\s|$)+
patterns: audit.rules
register: find_existing_watch_audit_rules
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010244
- audit_sudo_log_events
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /var/log/sudo.log in /etc/audit/audit.rules
lineinfile:
line: -w /var/log/sudo.log -p wa -k logins
state: present
dest: /etc/audit/audit.rules
create: true
mode: '0640'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
== 0
tags:
- DISA-STIG-UBTU-20-010244
- audit_sudo_log_events
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# 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 the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "^[\s]*-w[\s]+/var/log/sudo.log" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/log/sudo.log $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "wa" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s#\($sp*-w$sp\+/var/log/sudo.log$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w /var/log/sudo.log -p wa -k logins" >> "$audit_rules_file"
fi
done
# 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 the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/logins.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/var/log/sudo.log" /etc/audit/rules.d/*.rules)
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect+=("$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ "${#files_to_inspect[@]}" -eq "0" ]
then
# Append '/etc/audit/rules.d/logins.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/logins.rules"
# If the logins.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0640 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "^[\s]*-w[\s]+/var/log/sudo.log" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/log/sudo.log $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "wa" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s#\($sp*-w$sp\+/var/log/sudo.log$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w /var/log/sudo.log -p wa -k logins" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
System Audit Logs Must Have Mode 0750 or Less Permissive
[ref]rule
If log_group in /etc/audit/auditd.conf is set to a group other than the root
group account, change the mode of the audit log files with the following command:
$ sudo chmod 0750 /var/log/audit
Otherwise, change the mode of the audit log files with the following command:
$ sudo chmod 0700 /var/log/audit Rationale:If users can write to audit logs, audit trails can be modified or destroyed. References:
1, 11, 12, 13, 14, 15, 16, 18, 19, 3, 4, 5, 6, 7, 8, APO01.06, APO11.04, APO12.06, BAI03.05, BAI08.02, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.04, DSS05.07, DSS06.02, MEA02.01, CCI-000162, CCI-000163, CCI-000164, 4.2.3.10, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.7.3, 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 2.1, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 5.2, SR 6.1, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, 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.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.16.1.4, A.16.1.5, A.16.1.7, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CIP-003-8 R5.1.1, CIP-003-8 R5.2, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-004-6 R3.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2, CIP-007-3 R6.5, CM-6(a), AC-6(1), AU-9, DE.AE-3, DE.AE-5, PR.AC-4, PR.DS-5, PR.PT-1, RS.AN-1, RS.AN-4, SRG-OS-000057-GPOS-00027, SRG-OS-000058-GPOS-00028, SRG-OS-000059-GPOS-00029, UBTU-20-010128 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
if LC_ALL=C grep -iw ^log_file /etc/audit/auditd.conf; then
DIR=$(awk -F "=" '/^log_file/ {print $2}' /etc/audit/auditd.conf | tr -d ' ' | rev | cut -d"/" -f2- | rev)
else
DIR="/var/log/audit"
fi
if LC_ALL=C grep -m 1 -q ^log_group /etc/audit/auditd.conf; then
GROUP=$(awk -F "=" '/log_group/ {print $2}' /etc/audit/auditd.conf | tr -d ' ')
if ! [ "${GROUP}" == 'root' ] ; then
chmod 0750 $DIR
else
chmod 0700 $DIR
fi
else
chmod 0700 $DIR
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
System Audit Logs Must Be Group Owned By Root
[ref]ruleAll audit logs must be group owned by root user. The path for audit log can
be configured via log_file parameter in /etc/audit/auditd.conf
or, by default, the path for audit log is /var/log/audit/ .
To properly set the group owner of /var/log/audit/* , run the command:
$ sudo chgrp root /var/log/audit/*
If log_group in /etc/audit/auditd.conf is set to a group other
than the root group account, change the group ownership of the audit logs
to this specific group.Rationale:Unauthorized disclosure of audit records can reveal system and configuration data to
attackers, thus compromising its confidentiality. References:
1, 11, 12, 13, 14, 15, 16, 18, 19, 3, 4, 5, 6, 7, 8, 5.4.1.1, APO01.06, APO11.04, APO12.06, BAI03.05, BAI08.02, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.04, DSS05.07, DSS06.02, MEA02.01, 3.3.1, CCI-000162, CCI-000163, CCI-000164, CCI-001314, 4.2.3.10, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.7.3, 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 2.1, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 5.2, SR 6.1, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, 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.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.16.1.4, A.16.1.5, A.16.1.7, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CM-6(a), AC-6(1), AU-9(4), DE.AE-3, DE.AE-5, PR.AC-4, PR.DS-5, PR.PT-1, RS.AN-1, RS.AN-4, Req-10.5.1, SRG-OS-000057-GPOS-00027, SRG-OS-000058-GPOS-00028, SRG-OS-000059-GPOS-00029, SRG-OS-000206-GPOS-00084, UBTU-20-010124 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
if LC_ALL=C grep -iw log_file /etc/audit/auditd.conf; then
FILE=$(awk -F "=" '/^log_file/ {print $2}' /etc/audit/auditd.conf | tr -d ' ')
else
FILE="/var/log/audit/audit.log"
fi
if LC_ALL=C grep -m 1 -q ^log_group /etc/audit/auditd.conf; then
GROUP=$(awk -F "=" '/log_group/ {print $2}' /etc/audit/auditd.conf | tr -d ' ')
if ! [ "${GROUP}" == 'root' ]; then
chgrp ${GROUP} $FILE*
else
chgrp root $FILE*
fi
else
chgrp root $FILE*
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Audit Configuration Files Must Be Owned By Group root
[ref]ruleAll audit configuration files must be owned by group root.
chown :root /etc/audit/audit*.{rules,conf} /etc/audit/rules.d/* Rationale:Without the capability to restrict which roles and individuals can
select which events are audited, unauthorized personnel may be able
to prevent the auditing of critical events.
Misconfigured audits may degrade the system's performance by
overwhelming the audit log. Misconfigured audits may also make it more
difficult to establish, correlate, and investigate the events relating
to an incident or identify those responsible for one. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010135
- configure_strategy
- file_groupownership_audit_configuration
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /etc/audit/ file(s) matching ^audit(\.rules|d\.conf)$
command: find -H /etc/audit/ -maxdepth 1 -type f ! -gid 0 -regex "^audit(\.rules|d\.conf)$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010135
- configure_strategy
- file_groupownership_audit_configuration
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner on /etc/audit/ file(s) matching ^audit(\.rules|d\.conf)$
file:
path: '{{ item }}'
group: '0'
state: file
with_items:
- '{{ files_found.stdout_lines }}'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010135
- configure_strategy
- file_groupownership_audit_configuration
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /etc/audit/rules.d/ file(s) matching ^.*\.rules$
command: find -H /etc/audit/rules.d/ -maxdepth 1 -type f ! -gid 0 -regex "^.*\.rules$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010135
- configure_strategy
- file_groupownership_audit_configuration
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner on /etc/audit/rules.d/ file(s) matching ^.*\.rules$
file:
path: '{{ item }}'
group: '0'
state: file
with_items:
- '{{ files_found.stdout_lines }}'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010135
- configure_strategy
- file_groupownership_audit_configuration
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
find /etc/audit/ -maxdepth 1 -type f ! -gid 0 -regex '^audit(\.rules|d\.conf)$' -exec chgrp 0 {} \;
find /etc/audit/rules.d/ -maxdepth 1 -type f ! -gid 0 -regex '^.*\.rules$' -exec chgrp 0 {} \;
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Audit Configuration Files Must Be Owned By Root
[ref]ruleAll audit configuration files must be owned by root user.
To properly set the owner of /etc/audit/ , run the command:
$ sudo chown root /etc/audit/
To properly set the owner of /etc/audit/rules.d/ , run the command:
$ sudo chown root /etc/audit/rules.d/ Rationale:Without the capability to restrict which roles and individuals can
select which events are audited, unauthorized personnel may be able
to prevent the auditing of critical events.
Misconfigured audits may degrade the system's performance by
overwhelming the audit log. Misconfigured audits may also make it more
difficult to establish, correlate, and investigate the events relating
to an incident or identify those responsible for one. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010134
- configure_strategy
- file_ownership_audit_configuration
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /etc/audit/ file(s) matching ^audit(\.rules|d\.conf)$
command: find -H /etc/audit/ -maxdepth 1 -type f ! -uid 0 -regex "^audit(\.rules|d\.conf)$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010134
- configure_strategy
- file_ownership_audit_configuration
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on /etc/audit/ file(s) matching ^audit(\.rules|d\.conf)$
file:
path: '{{ item }}'
owner: '0'
state: file
with_items:
- '{{ files_found.stdout_lines }}'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010134
- configure_strategy
- file_ownership_audit_configuration
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /etc/audit/rules.d/ file(s) matching ^.*\.rules$
command: find -H /etc/audit/rules.d/ -maxdepth 1 -type f ! -uid 0 -regex "^.*\.rules$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010134
- configure_strategy
- file_ownership_audit_configuration
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on /etc/audit/rules.d/ file(s) matching ^.*\.rules$
file:
path: '{{ item }}'
owner: '0'
state: file
with_items:
- '{{ files_found.stdout_lines }}'
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010134
- configure_strategy
- file_ownership_audit_configuration
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
find /etc/audit/ -maxdepth 1 -type f ! -uid 0 -regex '^audit(\.rules|d\.conf)$' -exec chown 0 {} \;
find /etc/audit/rules.d/ -maxdepth 1 -type f ! -uid 0 -regex '^.*\.rules$' -exec chown 0 {} \;
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
System Audit Logs Must Be Owned By Root
[ref]ruleAll audit logs must be owned by root user. The path for audit log can be
configured via log_file parameter in /etc/audit/auditd.conf
or by default, the path for audit log is /var/log/audit/ .
To properly set the owner of /var/log/audit/* , run the command:
$ sudo chown root /var/log/audit/* Rationale:Unauthorized disclosure of audit records can reveal system and configuration data to
attackers, thus compromising its confidentiality. References:
1, 11, 12, 13, 14, 15, 16, 18, 19, 3, 4, 5, 6, 7, 8, 5.4.1.1, APO01.06, APO11.04, APO12.06, BAI03.05, BAI08.02, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.04, DSS05.07, DSS06.02, MEA02.01, 3.3.1, CCI-000162, CCI-000163, CCI-000164, CCI-001314, 4.2.3.10, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.7.3, 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 2.1, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 5.2, SR 6.1, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, 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.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.16.1.4, A.16.1.5, A.16.1.7, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CM-6(a), AC-6(1), AU-9(4), DE.AE-3, DE.AE-5, PR.AC-4, PR.DS-5, PR.PT-1, RS.AN-1, RS.AN-4, Req-10.5.1, SRG-OS-000057-GPOS-00027, SRG-OS-000058-GPOS-00028, SRG-OS-000059-GPOS-00029, SRG-OS-000206-GPOS-00084, UBTU-20-010123 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
if LC_ALL=C grep -iw log_file /etc/audit/auditd.conf; then
FILE=$(awk -F "=" '/^log_file/ {print $2}' /etc/audit/auditd.conf | tr -d ' ')
chown root $FILE*
else
chown root /var/log/audit/audit.log*
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
System Audit Logs Must Have Mode 0640 or Less Permissive
[ref]rule
If log_group in /etc/audit/auditd.conf is set to a group other than the
root
group account, change the mode of the audit log files with the following command:
$ sudo chmod 0640 audit_file
Otherwise, change the mode of the audit log files with the following command:
$ sudo chmod 0600 audit_file Rationale:If users can write to audit logs, audit trails can be modified or destroyed. References:
1, 11, 12, 13, 14, 15, 16, 18, 19, 3, 4, 5, 6, 7, 8, 5.4.1.1, APO01.06, APO11.04, APO12.06, BAI03.05, BAI08.02, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.04, DSS05.07, DSS06.02, MEA02.01, 3.3.1, CCI-000162, CCI-000163, CCI-000164, CCI-001314, 4.2.3.10, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.7.3, 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 2.1, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 5.2, SR 6.1, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, 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.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.16.1.4, A.16.1.5, A.16.1.7, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2, CM-6(a), AC-6(1), AU-9(4), DE.AE-3, DE.AE-5, PR.AC-4, PR.DS-5, PR.PT-1, RS.AN-1, RS.AN-4, Req-10.5, SRG-OS-000057-GPOS-00027, SRG-OS-000058-GPOS-00028, SRG-OS-000059-GPOS-00029, SRG-OS-000206-GPOS-00084, UBTU-20-010122 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
if LC_ALL=C grep -iw ^log_file /etc/audit/auditd.conf; then
FILE=$(awk -F "=" '/^log_file/ {print $2}' /etc/audit/auditd.conf | tr -d ' ')
else
FILE="/var/log/audit/audit.log"
fi
if LC_ALL=C grep -m 1 -q ^log_group /etc/audit/auditd.conf; then
GROUP=$(awk -F "=" '/log_group/ {print $2}' /etc/audit/auditd.conf | tr -d ' ')
if ! [ "${GROUP}" == 'root' ] ; then
chmod 0640 $FILE
chmod 0440 $FILE.*
else
chmod 0600 $FILE
chmod 0400 $FILE.*
fi
else
chmod 0600 $FILE
chmod 0400 $FILE.*
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Configure auditd Data Retention
[ref]groupThe audit system writes data to /var/log/audit/audit.log . By default,
auditd rotates 5 logs by size (6MB), retaining a maximum of 30MB of
data in total, and refuses to write entries when the disk is too
full. This minimizes the risk of audit data filling its partition
and impacting other services. This also minimizes the risk of the audit
daemon temporarily disabling the system if it cannot write audit log (which
it can be configured to do).
For a busy
system or a system which is thoroughly auditing system activity, the default settings
for data retention may be
insufficient. The log file size needed will depend heavily on what types
of events are being audited. First configure auditing to log all the events of
interest. Then monitor the log size manually for awhile to determine what file
size will allow you to keep the required data for the correct time period.
Using a dedicated partition for /var/log/audit prevents the
auditd logs from disrupting system functionality if they fill, and,
more importantly, prevents other activity in /var from filling the
partition and stopping the audit trail. (The audit logs are size-limited and
therefore unlikely to grow without bound unless configured to do so.) Some
machines may have requirements that no actions occur which cannot be audited.
If this is the case, then auditd can be configured to halt the machine
if it runs out of space. Note: Since older logs are rotated,
configuring auditd this way does not prevent older logs from being
rotated away before they can be viewed.
If your system is configured to halt when logging cannot be performed, make
sure this can never happen under normal circumstances! Ensure that
/var/log/audit is on its own partition, and that this partition is
larger than the maximum amount of data auditd will retain
normally. |
contains 7 rules |
Configure audispd Plugin To Send Logs To Remote Server
[ref]ruleConfigure the audispd plugin to off-load audit records onto a different
system or media from the system being audited.
First, set the active option in
/etc/audisp/plugins.d/au-remote.conf
Set the remote_server option in /etc/audit/audisp-remote.conf
with an IP address or hostname of the system that the audispd plugin should
send audit records to. For example
remote_server = logcollector Rationale:Information stored in one location is vulnerable to accidental or incidental
deletion or alteration.Off-loading is a common process in information systems
with limited audit storage capacity. Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
var_audispd_remote_server='logcollector'
AUDITCONFIG=/etc/audit/audisp-remote.conf
AUREMOTECONFIG=/etc/audisp/plugins.d/au-remote.conf
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$AUREMOTECONFIG"; then
sed_command+=('--follow-symlinks')
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^active")
# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "yes"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^active\\>" "$AUREMOTECONFIG"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
"${sed_command[@]}" "s/^active\\>.*/$escaped_formatted_output/gi" "$AUREMOTECONFIG"
else
# \n is precaution for case where file ends without trailing newline
printf '%s\n' "$formatted_output" >> "$AUREMOTECONFIG"
fi
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$AUDITCONFIG"; then
sed_command+=('--follow-symlinks')
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^remote_server")
# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_audispd_remote_server"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^remote_server\\>" "$AUDITCONFIG"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
"${sed_command[@]}" "s/^remote_server\\>.*/$escaped_formatted_output/gi" "$AUDITCONFIG"
else
# \n is precaution for case where file ends without trailing newline
printf '%s\n' "$formatted_output" >> "$AUDITCONFIG"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Configure a Sufficiently Large Partition for Audit Logs
[ref]ruleThe Ubuntu 20.04 operating system must allocate audit record storage
capacity to store at least one weeks worth of audit records when audit
records are not immediately sent to a central audit record storage
facility.
The partition size needed to capture a week's worth of audit records is
based on the activity level of the system and the total storage capacity
available. In normal circumstances, 10.0 GB of storage space for audit
records will be sufficient.
Determine which partition the audit records are being written to with the
following command:
$ sudo grep log_file /etc/audit/auditd.conf
log_file = /var/log/audit/audit.log
Check the size of the partition that audit records are written to with the
following command:
$ sudo df -h /var/log/audit/
/dev/sda2 24G 10.4G 13.6G 43% /var/log/audit Rationale:Information stored in one location is vulnerable to accidental or incidental
deletion or alteration. Off-loading is a common process in information
systems with limited audit storage capacity. |
Configure auditd Disk Full Action when Disk Space Is Full
[ref]ruleThe auditd service can be configured to take an action
when disk space is running low but prior to running out of space completely.
Edit the file /etc/audit/auditd.conf . Add or modify the following line,
substituting ACTION appropriately:
disk_full_action = ACTION
Set this value to single to cause the system to switch to single-user
mode for corrective action. Acceptable values also include syslog ,
exec ,
single , and halt . For certain systems, the need for availability
outweighs the need to log all actions, and a different setting should be
determined. Details regarding all possible values for ACTION are described in the
auditd.conf man page.Rationale:Taking appropriate action in case of a filled audit storage volume will minimize
the possibility of losing audit records. References:
1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, APO11.04, APO12.06, APO13.01, BAI03.05, BAI04.04, BAI08.02, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.04, DSS05.07, MEA02.01, CCI-000140, 4.2.3.10, 4.3.3.3.9, 4.3.3.5.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 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 7.1, SR 7.2, 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.16.1.4, A.16.1.5, A.16.1.7, A.17.2.1, AU-5(b), AU-5(2), AU-5(1), AU-5(4), CM-6(a), DE.AE-3, DE.AE-5, PR.DS-4, PR.PT-1, RS.AN-1, RS.AN-4, SRG-OS-000047-GPOS-00023, UBTU-20-010118 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
var_auditd_disk_full_action='halt'
var_auditd_disk_full_action="$(echo $var_auditd_disk_full_action | cut -d \| -f 1)"
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "/etc/audit/auditd.conf"; then
sed_command+=('--follow-symlinks')
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^disk_full_action")
# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_auditd_disk_full_action"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^disk_full_action\\>" "/etc/audit/auditd.conf"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
"${sed_command[@]}" "s/^disk_full_action\\>.*/$escaped_formatted_output/gi" "/etc/audit/auditd.conf"
else
# \n is precaution for case where file ends without trailing newline
printf '%s\n' "$formatted_output" >> "/etc/audit/auditd.conf"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Configure auditd mail_acct Action on Low Disk Space
[ref]ruleThe auditd service can be configured to send email to
a designated account in certain situations. Add or correct the following line
in /etc/audit/auditd.conf to ensure that administrators are notified
via email for those situations:
action_mail_acct = root Rationale:Email sent to the root account is typically aliased to the
administrators of the system, who can take appropriate action. References:
4.1.2.3, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 5.4.1.1, APO11.04, APO12.06, APO13.01, BAI03.05, BAI04.04, BAI08.02, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.04, DSS05.07, MEA02.01, 3.3.1, CCI-000139, CCI-001855, 164.312(a)(2)(ii), 4.2.3.10, 4.3.3.3.9, 4.3.3.5.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 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 7.1, SR 7.2, 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.16.1.4, A.16.1.5, A.16.1.7, A.17.2.1, CIP-003-8 R1.3, CIP-003-8 R3, CIP-003-8 R3.1, CIP-003-8 R3.2, CIP-003-8 R3.3, CIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.2.3, CIP-004-6 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.2, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3, IA-5(1), AU-5(a), AU-5(2), CM-6(a), DE.AE-3, DE.AE-5, PR.DS-4, PR.PT-1, RS.AN-1, RS.AN-4, Req-10.7.a, SRG-OS-000046-GPOS-00022, SRG-OS-000343-GPOS-00134, UBTU-20-010117, SRG-OS-000046-VMM-000210, SRG-OS-000343-VMM-001240 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
var_auditd_action_mail_acct='root'
AUDITCONFIG=/etc/audit/auditd.conf
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$AUDITCONFIG"; then
sed_command+=('--follow-symlinks')
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^action_mail_acct")
# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_auditd_action_mail_acct"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^action_mail_acct\\>" "$AUDITCONFIG"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
"${sed_command[@]}" "s/^action_mail_acct\\>.*/$escaped_formatted_output/gi" "$AUDITCONFIG"
else
# \n is precaution for case where file ends without trailing newline
printf '%s\n' "$formatted_output" >> "$AUDITCONFIG"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Configure auditd space_left Action on Low Disk Space
[ref]ruleThe auditd service can be configured to take an action
when disk space starts to run low.
Edit the file /etc/audit/auditd.conf . Modify the following line,
substituting ACTION appropriately:
space_left_action = ACTION
Possible values for ACTION are described in the auditd.conf man page.
These include:
syslog email exec suspend single halt
Set this to email (instead of the default,
which is suspend ) as it is more likely to get prompt attention. Acceptable values
also include suspend , single , and halt .Rationale:Notifying administrators of an impending disk space problem may
allow them to take corrective action prior to any disruption. References:
4.1.2.3, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 5.4.1.1, APO11.04, APO12.06, APO13.01, BAI03.05, BAI04.04, BAI08.02, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.04, DSS05.07, MEA02.01, 3.3.1, CCI-001855, 164.312(a)(2)(ii), 4.2.3.10, 4.3.3.3.9, 4.3.3.5.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 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 7.1, SR 7.2, 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.16.1.4, A.16.1.5, A.16.1.7, A.17.2.1, AU-5(b), AU-5(2), AU-5(1), AU-5(4), CM-6(a), DE.AE-3, DE.AE-5, PR.DS-4, PR.PT-1, RS.AN-1, RS.AN-4, Req-10.7, SRG-OS-000343-GPOS-00134, UBTU-20-010217, SRG-OS-000343-VMM-001240 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
var_auditd_space_left_action='email'
#
# If space_left_action present in /etc/audit/auditd.conf, change value
# to var_auditd_space_left_action, else
# add "space_left_action = $var_auditd_space_left_action" to /etc/audit/auditd.conf
#
AUDITCONFIG=/etc/audit/auditd.conf
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$AUDITCONFIG"; then
sed_command+=('--follow-symlinks')
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^space_left_action")
# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_auditd_space_left_action"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^space_left_action\\>" "$AUDITCONFIG"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
"${sed_command[@]}" "s/^space_left_action\\>.*/$escaped_formatted_output/gi" "$AUDITCONFIG"
else
# \n is precaution for case where file ends without trailing newline
printf '%s\n' "$formatted_output" >> "$AUDITCONFIG"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Configure auditd space_left on Low Disk Space
[ref]ruleThe auditd service can be configured to take an action
when disk space is running low but prior to running out of space completely.
Edit the file /etc/audit/auditd.conf . Add or modify the following line,
substituting PERCENTAGE appropriately:
space_left = PERCENTAGE%
Set this value to at least 25 to cause the system to
notify the user of an issue.Rationale:Notifying administrators of an impending disk space problem may allow them to
take corrective action prior to any disruption. References:
1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, APO11.04, APO12.06, APO13.01, BAI03.05, BAI04.04, BAI08.02, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.04, DSS05.07, MEA02.01, CCI-001855, 4.2.3.10, 4.3.3.3.9, 4.3.3.5.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 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 7.1, SR 7.2, 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.16.1.4, A.16.1.5, A.16.1.7, A.17.2.1, AU-5(b), AU-5(2), AU-5(1), AU-5(4), CM-6(a), DE.AE-3, DE.AE-5, PR.DS-4, PR.PT-1, RS.AN-1, RS.AN-4, Req-10.7, SRG-OS-000343-GPOS-00134, UBTU-20-010217, SRG-OS-000343-VMM-001240 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010217
- NIST-800-53-AU-5(1)
- NIST-800-53-AU-5(2)
- NIST-800-53-AU-5(4)
- NIST-800-53-AU-5(b)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.7
- auditd_data_retention_space_left_percentage
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: XCCDF Value var_auditd_space_left_percentage # promote to variable
set_fact:
var_auditd_space_left_percentage: !!str 25
tags:
- always
- name: Configure auditd space_left on Low Disk Space
lineinfile:
dest: /etc/audit/auditd.conf
line: space_left = {{ var_auditd_space_left_percentage }}%
regexp: ^\s*space_left\s*=\s*.*$
state: present
create: true
when:
- '"auditd" in ansible_facts.packages'
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010217
- NIST-800-53-AU-5(1)
- NIST-800-53-AU-5(2)
- NIST-800-53-AU-5(4)
- NIST-800-53-AU-5(b)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.7
- auditd_data_retention_space_left_percentage
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; then
var_auditd_space_left_percentage='25'
grep -q "^space_left[[:space:]]*=.*$" /etc/audit/auditd.conf && \
sed -i "s/^space_left[[:space:]]*=.*$/space_left = $var_auditd_space_left_percentage%/g" /etc/audit/auditd.conf || \
echo "space_left = $var_auditd_space_left_percentage%" >> /etc/audit/auditd.conf
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Offload audit Logs to External Media
[ref]ruleThe operating system must have a crontab script running weekly to
offload audit events of standalone systems. Warning:
Due to different needs and possibilities, automated remediation is not
available for this configuration check. Rationale:Information stored in one location is vulnerable to accidental or
incidental deletion or alteration.
Offloading is a common process in information systems with limited
audit storage capacity. |
Ensure the default plugins for the audit dispatcher are Installed
[ref]ruleThe audit-audispd-plugins package should be installed. Rationale:Information stored in one location is vulnerable to accidental or incidental deletion or alteration. Off-loading is a common process in information systems with limited audit storage capacity. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Ensure audispd-plugins is installed
package:
name: audispd-plugins
state: present
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010216
- PCI-DSS-Req-10.5.3
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- package_audit-audispd-plugins_installed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
DEBIAN_FRONTEND=noninteractive apt-get install -y "audispd-plugins"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include install_audispd-plugins
class install_audispd-plugins {
package { 'audispd-plugins':
ensure => 'installed',
}
}
Remediation script: (show)
[[packages]]
name = "audispd-plugins"
version = "*"
|
Ensure the audit Subsystem is Installed
[ref]ruleThe audit package should be installed. Rationale:The auditd service is an access monitoring and accounting daemon, watching system calls to audit any access, in comparison with potential local access control policy such as SELinux policy. References:
BP28(R50), 4.1.1.1, CCI-000130, CCI-000131, CCI-000132, CCI-000133, CCI-000134, CCI-000135, CCI-000154, CCI-000158, CCI-000172, CCI-001464, CCI-001487, CCI-001814, CCI-001875, CCI-001876, CCI-001877, CCI-001878, CCI-001879, CCI-001880, CCI-001881, CCI-001882, CCI-001889, CCI-001914, CCI-002884, CCI-000169, CIP-004-6 R3.3, CIP-007-3 R6.5, AC-7(a), AU-7(1), AU-7(2), AU-14, AU-12(2), AU-2(a), CM-6(a), FAU_GEN.1, Req-10.2.1, SRG-OS-000062-GPOS-00031, SRG-OS-000037-GPOS-00015, SRG-OS-000038-GPOS-00016, SRG-OS-000039-GPOS-00017, SRG-OS-000040-GPOS-00018, SRG-OS-000041-GPOS-00019, SRG-OS-000042-GPOS-00021, SRG-OS-000051-GPOS-00024, SRG-OS-000054-GPOS-00025, SRG-OS-000122-GPOS-00063, SRG-OS-000254-GPOS-00095, SRG-OS-000255-GPOS-00096, SRG-OS-000337-GPOS-00129, SRG-OS-000348-GPOS-00136, SRG-OS-000349-GPOS-00137, SRG-OS-000350-GPOS-00138, SRG-OS-000351-GPOS-00139, SRG-OS-000352-GPOS-00140, SRG-OS-000353-GPOS-00141, SRG-OS-000354-GPOS-00142, SRG-OS-000358-GPOS-00145, SRG-OS-000365-GPOS-00152, SRG-OS-000392-GPOS-00172, SRG-OS-000475-GPOS-00220, UBTU-20-010182 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Ensure auditd is installed
package:
name: auditd
state: present
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010182
- NIST-800-53-AC-7(a)
- NIST-800-53-AU-12(2)
- NIST-800-53-AU-14
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-7(1)
- NIST-800-53-AU-7(2)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- package_audit_installed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
DEBIAN_FRONTEND=noninteractive apt-get install -y "auditd"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include install_auditd
class install_auditd {
package { 'auditd':
ensure => 'installed',
}
}
Remediation script: (show)
[[packages]]
name = "auditd"
version = "*"
|
Enable auditd Service
[ref]ruleThe auditd service is an essential userspace component of
the Linux Auditing System, as it is responsible for writing audit records to
disk.
The auditd service can be enabled with the following command:
$ sudo systemctl enable auditd.service Rationale:Without establishing what type of events occurred, it would be difficult
to establish, correlate, and investigate the events leading up to an outage or attack.
Ensuring the auditd service is active ensures audit records
generated by the kernel are appropriately recorded.
Additionally, a properly configured audit subsystem ensures that actions of
individual system users can be uniquely traced to those users so they
can be held accountable for their actions. References:
4.1.1.2, 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.3.1, 3.3.2, 3.3.6, CCI-000126, CCI-000130, CCI-000131, CCI-000132, CCI-000133, CCI-000134, CCI-000135, CCI-000154, CCI-000158, CCI-000172, CCI-000366, CCI-001464, CCI-001487, CCI-001814, CCI-001875, CCI-001876, CCI-001877, CCI-002884, CCI-001878, CCI-001879, CCI-001880, CCI-001881, CCI-001882, CCI-001889, CCI-001914, CCI-000169, 164.308(a)(1)(ii)(D), 164.308(a)(5)(ii)(C), 164.310(a)(2)(iv), 164.310(d)(2)(iii), 164.312(b), 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, CIP-004-6 R3.3, CIP-007-3 R6.5, AC-2(g), AU-3, AU-10, AU-2(d), AU-12(c), AU-14(1), AC-6(9), CM-6(a), SI-4(23), 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, Req-10.1, SRG-OS-000062-GPOS-00031, SRG-OS-000037-GPOS-00015, SRG-OS-000038-GPOS-00016, SRG-OS-000039-GPOS-00017, SRG-OS-000040-GPOS-00018, SRG-OS-000041-GPOS-00019, SRG-OS-000042-GPOS-00021, SRG-OS-000051-GPOS-00024, SRG-OS-000054-GPOS-00025, SRG-OS-000122-GPOS-00063, SRG-OS-000254-GPOS-00095, SRG-OS-000255-GPOS-00096, SRG-OS-000337-GPOS-00129, SRG-OS-000348-GPOS-00136, SRG-OS-000349-GPOS-00137, SRG-OS-000350-GPOS-00138, SRG-OS-000351-GPOS-00139, SRG-OS-000352-GPOS-00140, SRG-OS-000353-GPOS-00141, SRG-OS-000354-GPOS-00142, SRG-OS-000358-GPOS-00145, SRG-OS-000365-GPOS-00152, SRG-OS-000392-GPOS-00172, SRG-OS-000475-GPOS-00220, SRG-OS-000037-VMM-000150, SRG-OS-000063-VMM-000310, SRG-OS-000038-VMM-000160, SRG-OS-000039-VMM-000170, SRG-OS-000040-VMM-000180, SRG-OS-000041-VMM-000190 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- NIST-800-171-3.3.1
- NIST-800-171-3.3.2
- NIST-800-171-3.3.6
- NIST-800-53-AC-2(g)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-10
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-14(1)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-3
- NIST-800-53-CM-6(a)
- NIST-800-53-SI-4(23)
- PCI-DSS-Req-10.1
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- service_auditd_enabled
- name: Enable service auditd
block:
- name: Gather the package facts
package_facts:
manager: auto
- name: Enable service auditd
service:
name: auditd
enabled: 'yes'
state: started
masked: 'no'
when:
- '"auditd" in ansible_facts.packages'
when:
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- '"auditd" in ansible_facts.packages'
tags:
- CJIS-5.4.1.1
- NIST-800-171-3.3.1
- NIST-800-171-3.3.2
- NIST-800-171-3.3.6
- NIST-800-53-AC-2(g)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-10
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-14(1)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-3
- NIST-800-53-CM-6(a)
- NIST-800-53-SI-4(23)
- PCI-DSS-Req-10.1
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- service_auditd_enabled
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && { dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q installed; }; then
SYSTEMCTL_EXEC='/usr/bin/systemctl'
"$SYSTEMCTL_EXEC" unmask 'auditd.service'
"$SYSTEMCTL_EXEC" start 'auditd.service'
"$SYSTEMCTL_EXEC" enable 'auditd.service'
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include enable_auditd
class enable_auditd {
service {'auditd':
enable => true,
ensure => 'running',
}
}
Remediation script: (show)
[customizations.services]
enabled = ["auditd"]
|
Enable Auditing for Processes Which Start Prior to the Audit Daemon
[ref]ruleTo ensure all processes can be audited, even those which start
prior to the audit daemon, add the argument audit=1 to the default
GRUB 2 command line for the Linux operating system.
To ensure that audit=1 is added as a kernel command line
argument to newly installed kernels, add audit=1 to the
default Grub2 command line for Linux operating systems. Modify the line within
/etc/default/grub as shown below:
GRUB_CMDLINE_LINUX="... audit=1 ..."
Run the following command to update command line for already installed kernels:# update-grub Rationale:Each process on the system carries an "auditable" flag which indicates whether
its activities can be audited. Although auditd takes care of enabling
this for all processes which launch after it does, adding the kernel argument
ensures it is set for every process during boot. References:
4.1.1.3, 1, 11, 12, 13, 14, 15, 16, 19, 3, 4, 5, 6, 7, 8, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.02, DSS05.03, DSS05.04, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.3.1, CCI-001464, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(5)(ii)(C), 164.310(a)(2)(iv), 164.310(d)(2)(iii), 164.312(b), 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 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.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, AC-17(1), AU-14(1), AU-10, CM-6(a), IR-5(1), DE.AE-3, DE.AE-5, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1, Req-10.3, 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-000473-GPOS-00218, SRG-OS-000254-GPOS-00095, UBTU-20-010198, SRG-OS-000254-VMM-000880 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && { dpkg-query --show --showformat='${db:Status-Status}\n' 'grub2-common' 2>/dev/null | grep -q installed; }; then
# Correct the form of default kernel command line in GRUB
if grep -q '^GRUB_CMDLINE_LINUX=.*audit=.*"' '/etc/default/grub' ; then
# modify the GRUB command-line if an audit= arg already exists
sed -i "s/\(^GRUB_CMDLINE_LINUX=\".*\)audit=[^[:space:]]\+\(.*\"\)/\1audit=1\2/" '/etc/default/grub'
else
# no audit=arg is present, append it
sed -i "s/\(^GRUB_CMDLINE_LINUX=\".*\)\"/\1 audit=1\"/" '/etc/default/grub'
fi
update-grub
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation script: (show)
[customizations.kernel]
append = "audit=1"
|
AppArmor
[ref]groupMany security vulnerabilities result from bugs in trusted programs. A trusted
program runs with privileges that attackers want to possess. The program fails
to keep that trust if there is a bug in the program that allows the attacker to
acquire said privilege.
AppArmor® is an application security solution designed specifically to apply
privilege confinement to suspect programs. AppArmor allows the administrator to
specify the domain of activities the program can perform by developing a
security profile. A security profile is a listing of files that the program may
access and the operations the program may perform. AppArmor secures
applications by enforcing good application behavior without relying on attack
signatures, so it can prevent attacks even if previously unknown
vulnerabilities are being exploited. |
contains 2 rules |
Ensure AppArmor is installed
[ref]ruleAppArmor provide Mandatory Access Controls. Rationale:Without a Mandatory Access Control system installed only the default
Discretionary Access Control system will be available. References:
1.7.1.1, CCI-001764, CCI-001774, CCI-002165, CCI-002235, SRG-OS-000368-GPOS-00154, SRG-OS-000312-GPOS-00122, SRG-OS-000312-GPOS-00123, SRG-OS-000312-GPOS-00124, SRG-OS-000324-GPOS-00125, SRG-OS-000370-GPOS-00155, UBTU-20-010439 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Ensure apparmor is installed
package:
name: apparmor
state: present
tags:
- DISA-STIG-UBTU-20-010439
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- package_apparmor_installed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
DEBIAN_FRONTEND=noninteractive apt-get install -y "apparmor"
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include install_apparmor
class install_apparmor {
package { 'apparmor':
ensure => 'installed',
}
}
Remediation script: (show)
[[packages]]
name = "apparmor"
version = "*"
|
Ensure AppArmor is Active and Configured
[ref]ruleVerify that the Apparmor tool is configured to
control whitelisted applications and user home directory access
control.
The apparmor service can be enabled with the following command:
$ sudo systemctl enable apparmor.service Rationale:Using a whitelist provides a configuration management method for allowing
the execution of only authorized software. Using only authorized software
decreases risk by limiting the number of potential vulnerabilities.
The organization must identify authorized software programs and permit
execution of authorized software by adding each authorized program to the
"pam_apparmor" exception policy. The process used to identify software
programs that are authorized to execute on organizational information
systems is commonly referred to as whitelisting.
Verification of whitelisted software occurs prior to execution or at system
startup.
Users' home directories/folders may contain information of a sensitive
nature. Nonprivileged users should coordinate any sharing of information
with a System Administrator (SA) through shared resources.
Apparmor can confine users to their home directory, not allowing them to
make any changes outside of their own home directories. Confining users to
their home directory will minimize the risk of sharing information. References:
CCI-001764, CCI-001774, CCI-002165, CCI-002233, CCI-002235, AC-3(4), AC-6(8), AC-6(10), CM-7(5)(b), CM-7(2), SC-7(21), CM-6(a), SRG-OS-000312-GPOS-00122, SRG-OS-000312-GPOS-00123SRG-OS-000312-GPOS-00124, SRG-OS-000324-GPOS-00125, SRG-OS-000326-GPOS-00126, SRG-OS-000370-GPOS-00155, SRG-OS-000480-GPOS-00230, SRG-OS-000480-GPOS-00227, SRG-OS-000480-GPOS-00231, SRG-OS-000480-GPOS-00232, UBTU-20-010439 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
# Enable apparmor
/usr/bin/systemctl enable "apparmor"
/usr/bin/systemctl start "apparmor"
# The service may not be running because it has been started and failed,
# so let's reset the state so OVAL checks pass.
# Service should be 'inactive', not 'failed' after reboot though.
/usr/bin/systemctl reset-failed "apparmor"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include enable_apparmor
class enable_apparmor {
service {'apparmor':
enable => true,
ensure => 'running',
}
}
Remediation script: (show)
[customizations.services]
enabled = ["apparmor"]
|
GRUB2 bootloader configuration
[ref]groupDuring the boot process, the boot loader is
responsible for starting the execution of the kernel and passing
options to it. The boot loader allows for the selection of
different kernels - possibly on different partitions or media.
The default Ubuntu 20.04 boot loader for x86 systems is called GRUB2.
Options it can pass to the kernel include single-user mode, which
provides root access without any authentication, and the ability to
disable SELinux. To prevent local users from modifying the boot
parameters and endangering security, protect the boot loader configuration
with a password and ensure its configuration file's permissions
are set properly. |
contains 2 rules |
Non-UEFI GRUB2 bootloader configuration
[ref]groupNon-UEFI GRUB2 bootloader configuration |
contains 1 rule |
Set Boot Loader Password in grub2
[ref]ruleThe grub2 boot loader should have a superuser account and password
protection enabled to protect boot-time settings.
Since plaintext passwords are a security risk, generate a hash for the password
by running the following command:
# grub2-mkpasswd-pbkdf2
When prompted, enter the password that was selected.
Using the hash from the output, modify the /etc/grub.d/40_custom
file with the following content:
set superusers="boot"
password_pbkdf2 boot grub.pbkdf2.sha512.VeryLongString
NOTE: the bootloader superuser account and password MUST differ from the
root account and password.
Once the superuser password has been added,
update the
grub.cfg file by running:
update-grub Warning:
To prevent hard-coded passwords, automatic remediation of this control is not available. Remediation
must be automated as a component of machine provisioning, or followed manually as outlined above.
Also, do NOT manually add the superuser account and password to the
grub.cfg file as the grub2-mkconfig command overwrites this file. Rationale:Password protection on the boot loader configuration ensures
users with physical access cannot trivially alter
important bootloader settings. These include which kernel to use,
and whether to enter single-user mode. References:
BP28(R17), 1.5.1, 1, 11, 12, 14, 15, 16, 18, 3, 5, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.06, DSS06.10, 3.4.5, CCI-000213, 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii), 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 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.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, A.18.1.4, A.6.1.2, A.7.1.1, A.9.1.2, 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.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5, CM-6(a), PR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.PT-3, FIA_UAU.1, SRG-OS-000080-GPOS-00048, UBTU-20-010009 |
UEFI GRUB2 bootloader configuration
[ref]groupUEFI GRUB2 bootloader configuration |
contains 1 rule |
Set the UEFI Boot Loader Password
[ref]ruleThe grub2 boot loader should have a superuser account and password
protection enabled to protect boot-time settings.
Since plaintext passwords are a security risk, generate a hash for the password
by running the following command:
# grub2-mkpasswd-pbkdf2
When prompted, enter the password that was selected.
Using the hash from the output, modify the /etc/grub.d/40_custom
file with the following content:
set superusers="boot"
password_pbkdf2 boot grub.pbkdf2.sha512.VeryLongString
NOTE: the bootloader superuser account and password MUST differ from the
root account and password.
Once the superuser password has been added,
update the
grub.cfg file by running:
update-grub Warning:
To prevent hard-coded passwords, automatic remediation of this control is not available. Remediation
must be automated as a component of machine provisioning, or followed manually as outlined above.
Also, do NOT manually add the superuser account and password to the
grub.cfg file as the grub2-mkconfig command overwrites this file. Rationale:Password protection on the boot loader configuration ensures
users with physical access cannot trivially alter
important bootloader settings. These include which kernel to use,
and whether to enter single-user mode. References:
BP28(R17), 11, 12, 14, 15, 16, 18, 3, 5, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.03, DSS06.06, 3.4.5, CCI-000213, 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii), 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 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.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, A.6.1.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CM-6(a), PR.AC-4, PR.AC-6, PR.PT-3, FIA_UAU.1, SRG-OS-000080-GPOS-00048, UBTU-20-010009 |
Configure Syslog
[ref]groupThe syslog service has been the default Unix logging mechanism for
many years. It has a number of downsides, including inconsistent log format,
lack of authentication for received messages, and lack of authentication,
encryption, or reliable transport for messages sent over a network. However,
due to its long history, syslog is a de facto standard which is supported by
almost all Unix applications.
In Ubuntu 20.04, rsyslog has replaced ksyslogd as the
syslog daemon of choice, and it includes some additional security features
such as reliable, connection-oriented (i.e. TCP) transmission of logs, the
option to log to database formats, and the encryption of log data en route to
a central logging server.
This section discusses how to configure rsyslog for
best effect, and how to use tools provided with the system to maintain and
monitor logs. |
contains 3 rules |
Ensure Proper Configuration of Log Files
[ref]groupThe file /etc/rsyslog.conf controls where log message are written.
These are controlled by lines called rules, which consist of a
selector and an action.
These rules are often customized depending on the role of the system, the
requirements of the environment, and whatever may enable
the administrator to most effectively make use of log data.
The default rules in Ubuntu 20.04 are:
*.info;mail.none;authpriv.none;cron.none /var/log/messages
authpriv.* /var/log/secure
mail.* -/var/log/maillog
cron.* /var/log/cron
*.emerg *
uucp,news.crit /var/log/spooler
local7.* /var/log/boot.log
See the man page rsyslog.conf(5) for more information.
Note that the rsyslog daemon can be configured to use a timestamp format that
some log processing programs may not understand. If this occurs,
edit the file /etc/rsyslog.conf and add or edit the following line:
$ ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat |
contains 1 rule |
Ensure remote access methods are monitored in Rsyslog
[ref]ruleLogging of remote access methods must be implemented to help identify cyber
attacks and ensure ongoing compliance with remote access policies are being
audited and upheld. An examples of a remote access method is the use of the
Remote Desktop Protocol (RDP) from an external, non-organization controlled
network. The /etc/rsyslog.conf or
/etc/rsyslog.d/*.conf file should contain a match for the following
selectors: auth.* , authpriv.* , and daemon.* . If
not, use the following as an example configuration:
auth.*;authpriv.*;daemon.* /var/log/secure Rationale:Logging remote access methods can be used to trace the decrease the risks
associated with remote user access management. It can also be used to spot
cyber attacks and ensure ongoing compliance with organizational policies
surrounding the use of remote access methods. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: 'Ensure remote access methods are monitored in Rsyslog: Set facts'
set_fact:
conf_files:
- /etc/rsyslog.conf
remote_methods:
- selector: auth.*
regexp: ^.*auth\.\*.*$
- selector: authpriv.*
regexp: ^.*authpriv\.\*.*$
- selector: daemon.*
regexp: ^.*daemon\.\*.*$
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010403
- NIST-800-53-AC-17(1)
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- rsyslog_remote_access_monitoring
- name: 'Ensure remote access methods are monitored in Rsyslog: Ensure rsyslog.conf
exists'
file:
path: '{{ conf_files.0 }}'
state: touch
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010403
- NIST-800-53-AC-17(1)
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- rsyslog_remote_access_monitoring
- name: 'Ensure remote access methods are monitored in Rsyslog: Gather conf.d files'
find:
patterns:
- '*.conf'
paths:
- /etc/rsyslog.d
register: rsyslogd
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010403
- NIST-800-53-AC-17(1)
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- rsyslog_remote_access_monitoring
- name: 'Ensure remote access methods are monitored in Rsyslog: Set conf file(s)'
set_fact:
conf_files: '{{ conf_files + [item.path] }}'
loop: '{{ rsyslogd.files }}'
when:
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- rsyslogd.matched > 0
tags:
- DISA-STIG-UBTU-20-010403
- NIST-800-53-AC-17(1)
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- rsyslog_remote_access_monitoring
- name: 'Ensure remote access methods are monitored in Rsyslog: Check for existing
values'
lineinfile:
path: '{{ item.1 }}'
regexp: '{{ item.0.regexp }}'
state: absent
check_mode: true
changed_when: false
register: remote_method_values
loop: '{{ remote_methods|product(conf_files)|list }}'
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010403
- NIST-800-53-AC-17(1)
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- rsyslog_remote_access_monitoring
- name: 'Ensure remote access methods are monitored in Rsyslog: Configure'
lineinfile:
path: /etc/rsyslog.conf
line: '{{ item.item.0.selector }} /var/log/secure'
insertafter: ^.*\/var\/log\/secure.*$
create: true
loop: '{{ remote_method_values.results }}'
when:
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- item.found == 0
tags:
- DISA-STIG-UBTU-20-010403
- NIST-800-53-AC-17(1)
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- rsyslog_remote_access_monitoring
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
if [ ! -f /etc/rsyslog.d/50-default.conf ]; then
# Something is not right, create the file
touch /etc/rsyslog.d/50-default.conf
fi
# Check to see if auth exists
if ! grep -Erq "^auth\.\*,authpriv\.\*" /etc/rsyslog.*; then
echo "auth.*,authpriv.* /var/log/secure" >> /etc/rsyslog.d/50-default.conf
fi
if ! grep -Erq "^daemon\.\*" /etc/rsyslog.*; then
echo "daemon.* /var/log/messages" >> /etc/rsyslog.d/50-default.conf
fi
systemctl restart rsyslog.service
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Enable rsyslog Service
[ref]ruleThe rsyslog service provides syslog-style logging by default on Ubuntu 20.04.
The rsyslog service can be enabled with the following command:
$ sudo systemctl enable rsyslog.service Rationale:The rsyslog service must be running in order to provide
logging services, which are essential to system administration. References:
BP28(R5), NT28(R46), 4.2.1.2, 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO13.01, BAI03.05, BAI04.04, DSS01.03, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, CCI-001311, CCI-001312, CCI-001557, CCI-001851, CCI-000366, 164.312(a)(2)(ii), 4.3.2.6.7, 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 6.1, SR 6.2, SR 7.1, SR 7.2, 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.14.2.7, A.15.2.1, A.15.2.2, A.17.2.1, CM-6(a), AU-4(1), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.DS-4, PR.PT-1, SRG-OS-000480-GPOS-00227, UBTU-20-010432 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Enable service rsyslog
block:
- name: Gather the package facts
package_facts:
manager: auto
- name: Enable service rsyslog
service:
name: rsyslog
enabled: 'yes'
state: started
masked: 'no'
when:
- '"rsyslog" in ansible_facts.packages'
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010432
- NIST-800-53-AU-4(1)
- NIST-800-53-CM-6(a)
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- service_rsyslog_enabled
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
SYSTEMCTL_EXEC='/usr/bin/systemctl'
"$SYSTEMCTL_EXEC" unmask 'rsyslog.service'
"$SYSTEMCTL_EXEC" start 'rsyslog.service'
"$SYSTEMCTL_EXEC" enable 'rsyslog.service'
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include enable_rsyslog
class enable_rsyslog {
service {'rsyslog':
enable => true,
ensure => 'running',
}
}
Remediation script: (show)
[customizations.services]
enabled = ["rsyslog"]
|
Ensure real-time clock is set to UTC
[ref]ruleEnsure that the system real-time clock (RTC) is set to Coordinated Universal Time (UTC). Rationale:If time stamps are not consistently applied and there is no common
time reference, it is difficult to perform forensic analysis.
Time stamps generated by the operating system include date and time.
Time is commonly expressed in UTC, a modern continuation of GMT, or
local time with an offset from UTC. Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
if timedatectl status | grep -i "time zone" | grep -iv 'UTC\|GMT'; then
timedatectl set-timezone UTC
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Network Configuration and Firewalls
[ref]groupMost systems must be connected to a network of some
sort, and this brings with it the substantial risk of network
attack. This section discusses the security impact of decisions
about networking which must be made when configuring a system.
This section also discusses firewalls, network access
controls, and other network security frameworks, which allow
system-level rules to be written that can limit an attackers' ability
to connect to your system. These rules can specify that network
traffic should be allowed or denied from certain IP addresses,
hosts, and networks. The rules can also specify which of the
system's network services are available to particular hosts or
networks. |
contains 7 rules |
Kernel Parameters Which Affect Networking
[ref]groupThe sysctl utility is used to set
parameters which affect the operation of the Linux kernel. Kernel parameters
which affect networking and have security implications are described here. |
contains 1 rule |
Network Related Kernel Runtime Parameters for Hosts and Routers
[ref]groupCertain kernel parameters should be set for systems which are
acting as either hosts or routers to improve the system's ability defend
against certain types of IPv4 protocol attacks. |
contains 1 rule |
Enable Kernel Parameter to Use TCP Syncookies on Network Interfaces
[ref]ruleTo set the runtime status of the net.ipv4.tcp_syncookies kernel parameter, run the following command: $ sudo sysctl -w net.ipv4.tcp_syncookies=1
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d : net.ipv4.tcp_syncookies = 1 Rationale:A TCP SYN flood attack can cause a denial of service by filling a
system's TCP connection table with connections in the SYN_RCVD state.
Syncookies can be used to track a connection when a subsequent ACK is received,
verifying the initiator is attempting a valid connection and is not a flood
source. This feature is activated when a flood condition is detected, and
enables the system to continue servicing valid connection requests. References:
BP28(R22), 3.3.8, 1, 12, 13, 14, 15, 16, 18, 2, 4, 6, 7, 8, 9, 5.10.1.1, APO01.06, APO13.01, BAI04.04, DSS01.03, DSS01.05, DSS03.01, DSS03.05, DSS05.02, DSS05.04, DSS05.07, DSS06.02, 3.1.20, CCI-000366, CCI-001095, 4.2.3.4, 4.3.3.4, 4.4.3.3, 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.2, SR 7.1, SR 7.2, SR 7.6, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.1.1, A.12.1.2, A.12.1.3, A.13.1.1, A.13.1.2, A.13.1.3, A.13.2.1, A.13.2.2, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.17.2.1, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CM-7(a), CM-7(b), SC-5(1), SC-5(2), SC-5(3)(a), CM-6(a), DE.AE-1, DE.CM-1, ID.AM-3, PR.AC-5, PR.DS-4, PR.DS-5, PR.PT-4, Req-1.4.1, SRG-OS-000480-GPOS-00227, SRG-OS-000420-GPOS-00186, SRG-OS-000142-GPOS-00071, UBTU-20-010412 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: List /etc/sysctl.d/*.conf files
find:
paths:
- /etc/sysctl.d/
- /run/sysctl.d/
- /usr/local/lib/sysctl.d/
- /usr/lib/sysctl.d/
contains: ^[\s]*net.ipv4.tcp_syncookies.*$
patterns: '*.conf'
file_type: any
register: find_sysctl_d
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.10.1.1
- DISA-STIG-UBTU-20-010412
- NIST-800-171-3.1.20
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- NIST-800-53-SC-5(1)
- NIST-800-53-SC-5(2)
- NIST-800-53-SC-5(3)(a)
- PCI-DSS-Req-1.4.1
- disable_strategy
- low_complexity
- medium_disruption
- medium_severity
- reboot_required
- sysctl_net_ipv4_tcp_syncookies
- name: Comment out any occurrences of net.ipv4.tcp_syncookies from config files
replace:
path: '{{ item.path }}'
regexp: ^[\s]*net.ipv4.tcp_syncookies
replace: '#net.ipv4.tcp_syncookies'
loop: '{{ find_sysctl_d.files }}'
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.10.1.1
- DISA-STIG-UBTU-20-010412
- NIST-800-171-3.1.20
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- NIST-800-53-SC-5(1)
- NIST-800-53-SC-5(2)
- NIST-800-53-SC-5(3)(a)
- PCI-DSS-Req-1.4.1
- disable_strategy
- low_complexity
- medium_disruption
- medium_severity
- reboot_required
- sysctl_net_ipv4_tcp_syncookies
- name: XCCDF Value sysctl_net_ipv4_tcp_syncookies_value # promote to variable
set_fact:
sysctl_net_ipv4_tcp_syncookies_value: !!str 1
tags:
- always
- name: Ensure sysctl net.ipv4.tcp_syncookies is set
sysctl:
name: net.ipv4.tcp_syncookies
value: '{{ sysctl_net_ipv4_tcp_syncookies_value }}'
state: present
reload: true
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.10.1.1
- DISA-STIG-UBTU-20-010412
- NIST-800-171-3.1.20
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- NIST-800-53-SC-5(1)
- NIST-800-53-SC-5(2)
- NIST-800-53-SC-5(3)(a)
- PCI-DSS-Req-1.4.1
- disable_strategy
- low_complexity
- medium_disruption
- medium_severity
- reboot_required
- sysctl_net_ipv4_tcp_syncookies
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
# Comment out any occurrences of net.ipv4.tcp_syncookies from /etc/sysctl.d/*.conf files
for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf /usr/lib/sysctl.d/*.conf; do
matching_list=$(grep -P '^(?!#).*[\s]*net.ipv4.tcp_syncookies.*$' $f | uniq )
if ! test -z "$matching_list"; then
while IFS= read -r entry; do
escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
# comment out "net.ipv4.tcp_syncookies" matches to preserve user data
sed -i "s/^${escaped_entry}$/# &/g" $f
done <<< "$matching_list"
fi
done
sysctl_net_ipv4_tcp_syncookies_value='1'
#
# Set runtime for net.ipv4.tcp_syncookies
#
/sbin/sysctl -q -n -w net.ipv4.tcp_syncookies="$sysctl_net_ipv4_tcp_syncookies_value"
#
# If net.ipv4.tcp_syncookies present in /etc/sysctl.conf, change value to appropriate value
# else, add "net.ipv4.tcp_syncookies = value" to /etc/sysctl.conf
#
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "/etc/sysctl.conf"; then
sed_command+=('--follow-symlinks')
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^net.ipv4.tcp_syncookies")
# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv4_tcp_syncookies_value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^net.ipv4.tcp_syncookies\\>" "/etc/sysctl.conf"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
"${sed_command[@]}" "s/^net.ipv4.tcp_syncookies\\>.*/$escaped_formatted_output/gi" "/etc/sysctl.conf"
else
# \n is precaution for case where file ends without trailing newline
printf '%s\n' "$formatted_output" >> "/etc/sysctl.conf"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Uncomplicated Firewall (ufw)
[ref]groupThe Linux kernel in Ubuntu provides a packet filtering system called
netfilter, and the traditional interface for manipulating netfilter are
the iptables suite of commands. iptables provide a complete firewall
solution that is both highly configurable and highly flexible.
Becoming proficient in iptables takes time, and getting started with
netfilter firewalling using only iptables can be a daunting task. As a
result, many frontends for iptables have been created over the years,
each trying to achieve a different result and targeting a different
audience.
The Uncomplicated Firewall (ufw) is a frontend for iptables and is
particularly well-suited for host-based firewalls. ufw provides a
framework for managing netfilter, as well as a command-line interface
for manipulating the firewall. ufw aims to provide an easy to use
interface for people unfamiliar with firewall concepts, while at the
same time simplifies complicated iptables commands to help an
administrator who knows what he or she is doing. ufw is an upstream
for other distributions and graphical frontends. |
contains 4 rules |
Install ufw Package
[ref]ruleThe ufw package can be installed with the following command:
$ apt-get install ufw Rationale:ufw controls the Linux kernel network packet filtering
code. ufw allows system operators to set up firewalls and IP
masquerading, etc. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Ensure ufw is installed
package:
name: ufw
state: present
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010433
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- package_ufw_installed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
DEBIAN_FRONTEND=noninteractive apt-get install -y "ufw"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include install_ufw
class install_ufw {
package { 'ufw':
ensure => 'installed',
}
}
Remediation script: (show)
[[packages]]
name = "ufw"
version = "*"
|
Verify ufw Enabled
[ref]rule
The ufw service can be enabled with the following command:
$ sudo systemctl enable ufw.service Rationale:The ufw service must be enabled and running in order for ufw to protect the system Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Enable service ufw
block:
- name: Gather the package facts
package_facts:
manager: auto
- name: Enable service ufw
service:
name: ufw
enabled: 'yes'
state: started
masked: 'no'
when:
- '"ufw" in ansible_facts.packages'
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010434
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- service_ufw_enabled
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
SYSTEMCTL_EXEC='/usr/bin/systemctl'
"$SYSTEMCTL_EXEC" unmask 'ufw.service'
"$SYSTEMCTL_EXEC" start 'ufw.service'
"$SYSTEMCTL_EXEC" enable 'ufw.service'
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include enable_ufw
class enable_ufw {
service {'ufw':
enable => true,
ensure => 'running',
}
}
Remediation script: (show)
[customizations.services]
enabled = ["ufw"]
|
Only Allow Authorized Network Services in ufw
[ref]ruleCheck the firewall configuration for any unnecessary or prohibited
functions, ports, protocols, and/or services by running the following
command:
$ sudo ufw show raw
Chain OUTPUT (policy ACCEPT)
target prot opt sources destination
Chain INPUT (policy ACCEPT 1 packets, 40 bytes)
pkts bytes target prot opt in out source destination
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Ask the System Administrator for the site or program PPSM CLSA. Verify
the services allowed by the firewall match the PPSM CLSA.Rationale:To prevent unauthorized connection of devices, unauthorized transfer of
information, or unauthorized tunneling (i.e., embedding of data types
within data types), organizations must disable or restrict unused or
unnecessary physical and logical ports/protocols on information systems.
Operating systems are capable of providing a wide variety of functions
and services. Some of the functions and services provided by default
may not be necessary to support essential organizational operations.
Additionally, it is sometimes convenient to provide multiple services
from a single component (e.g., VPN and IPS); however, doing so
increases risk over limiting the services provided by any one component.
To support the requirements and principles of least functionality, the
operating system must support the organizational requirements, providing
only essential capabilities and limiting the use of ports, protocols,
and/or services to only those required, authorized, and approved to
conduct official business or to address authorized quality of life
issues. |
ufw Must rate-limit network interfaces
[ref]ruleThe operating system must configure the uncomplicated firewall to
rate-limit impacted network interfaces. Rationale:This requirement addresses the configuration of the operating system to
mitigate the impact of DoS attacks that have occurred or are ongoing on
system availability. For each system, known and potential DoS attacks
must be identified and solutions for each type implemented. A variety
of technologies exist to limit or, in some cases, eliminate the effects
of DoS attacks (e.g., limiting processes or establishing memory
partitions). Employing increased capacity and bandwidth, combined with
service redundancy, may reduce the susceptibility to some DoS attacks. |
Wireless Networking
[ref]groupWireless networking, such as 802.11
(WiFi) and Bluetooth, can present a security risk to sensitive or
classified systems and networks. Wireless networking hardware is
much more likely to be included in laptop or portable systems than
in desktops or servers.
Removal of hardware provides the greatest assurance that the wireless
capability remains disabled. Acquisition policies often include provisions to
prevent the purchase of equipment that will be used in sensitive spaces and
includes wireless capabilities. If it is impractical to remove the wireless
hardware, and policy permits the device to enter sensitive spaces as long
as wireless is disabled, efforts should instead focus on disabling wireless capability
via software. |
contains 1 rule |
Disable Wireless Through Software Configuration
[ref]groupIf it is impossible to remove the wireless hardware
from the device in question, disable as much of it as possible
through software. The following methods can disable software
support for wireless networking, but note that these methods do not
prevent malicious software or careless users from re-activating the
devices. |
contains 1 rule |
Deactivate Wireless Network Interfaces
[ref]ruleDeactivating wireless network interfaces should prevent normal usage of the wireless
capability.
Configure the system to disable all wireless network interfaces with the following command:
$ sudo nmcli radio all off Rationale:The use of wireless networking can introduce many different attack vectors into
the organization's network. Common attack vectors such as malicious association
and ad hoc networks will allow an attacker to spoof a wireless access point
(AP), allowing validated systems to connect to the malicious AP and enabling the
attacker to monitor and record network traffic. These malicious APs can also
serve to create a man-in-the-middle attack or be used to create a denial of
service to valid network resources. References:
3.1.2, 11, 12, 14, 15, 3, 8, 9, APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.04, DSS05.02, DSS05.03, DSS05.05, DSS06.06, 3.1.16, CCI-000085, CCI-002418, CCI-002421, CCI-001443, CCI-001444, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 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.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3, SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, 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, 1315, 1319, A.11.2.6, A.12.1.2, A.12.5.1, A.12.6.2, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.6.2.1, A.6.2.2, A.9.1.2, AC-18(a), AC-18(3), CM-7(a), CM-7(b), CM-6(a), MP-7, PR.AC-3, PR.IP-1, PR.PT-3, PR.PT-4, Req-1.3.3, SRG-OS-000299-GPOS-00117, SRG-OS-000300-GPOS-00118, SRG-OS-000424-GPOS-00188, SRG-OS-000481-GPOS-000481, UBTU-20-010455 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | false |
---|
Strategy: | unknown |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-20-010455
- NIST-800-171-3.1.16
- NIST-800-53-AC-18(3)
- NIST-800-53-AC-18(a)
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- NIST-800-53-MP-7
- PCI-DSS-Req-1.3.3
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- unknown_strategy
- wireless_disable_interfaces
- name: Ensure NetworkManager is installed
ansible.builtin.package:
name: '{{ item }}'
state: present
with_items:
- NetworkManager
tags:
- DISA-STIG-UBTU-20-010455
- NIST-800-171-3.1.16
- NIST-800-53-AC-18(3)
- NIST-800-53-AC-18(a)
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- NIST-800-53-MP-7
- PCI-DSS-Req-1.3.3
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- unknown_strategy
- wireless_disable_interfaces
- name: Deactivate Wireless Network Interfaces
command: nmcli radio wifi off
when: '''NetworkManager'' in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-20-010455
- NIST-800-171-3.1.16
- NIST-800-53-AC-18(3)
- NIST-800-53-AC-18(a)
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- NIST-800-53-MP-7
- PCI-DSS-Req-1.3.3
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- unknown_strategy
- wireless_disable_interfaces
Remediation Shell script: (show)
DEBIAN_FRONTEND=noninteractive apt-get install -y "NetworkManager"
nmcli radio all off
|
Transport Layer Security Support
[ref]groupSupport for Transport Layer Security (TLS), and its predecessor, the Secure
Sockets Layer (SSL), is included in Red Hat Enterprise Linux in the OpenSSL software (RPM package
openssl ). TLS provides encrypted and authenticated network
communications, and many network services include support for it. TLS or SSL
can be leveraged to avoid any plaintext transmission of sensitive data.
For information on how to use OpenSSL, see
http://www.openssl.org/docs/. Information on FIPS validation
of OpenSSL is available at http://www.openssl.org/docs/fips.html
and http://csrc.nist.gov/groups/STM/cmvp/documents/140-1/140val-all.htm. |
contains 1 rule |
Only Allow DoD PKI-established CAs
[ref]ruleThe operating system must only allow the use of DoD PKI-established
certificate authorities for verification of the establishment of
protected sessions. Rationale:Untrusted Certificate Authorities (CA) can issue certificates, but they
may be issued by organizations or individuals that seek to compromise
DoD systems or by organizations with insufficient security controls. If
the CA used for verifying the certificate is not a DoD-approved CA,
trust of this CA has not been established.
The DoD will only accept PKI-certificates obtained from a DoD-approved
internal or external certificate authority. Reliance on CAs for the
establishment of secure sessions includes, for example, the use of
SSL/TLS certificates. |
File Permissions and Masks
[ref]groupTraditional Unix security relies heavily on file and
directory permissions to prevent unauthorized users from reading or
modifying files to which they should not have access.
Several of the commands in this section search filesystems
for files or directories with certain characteristics, and are
intended to be run on every local partition on a given system.
When the variable PART appears in one of the commands below,
it means that the command is intended to be run repeatedly, with the
name of each local partition substituted for PART in turn.
The following command prints a list of all xfs partitions on the local
system, which is the default filesystem for Ubuntu 20.04
installations:
$ mount -t xfs | awk '{print $3}'
For any systems that use a different
local filesystem type, modify this command as appropriate. |
contains 27 rules |
Verify Permissions on Important Files and
Directories
[ref]groupPermissions for many files on a system must be set
restrictively to ensure sensitive information is properly protected.
This section discusses important
permission restrictions which can be verified
to ensure that no harmful discrepancies have
arisen. |
contains 25 rules |
Verify Permissions on Files within /var/log Directory
[ref]groupThe /var/log directory contains files with logs of error
messages in the system and should only be accessed by authorized
personnel. |
contains 6 rules |
Verify Group Who Owns /var/log Directory
[ref]rule To properly set the group owner of /var/log , run the command: $ sudo chgrp syslog /var/log Rationale:The /var/log directory contains files with logs of error
messages in the system and should only be accessed by authorized
personnel. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Ensure group owner on /var/log/
file:
path: /var/log/
state: directory
group: '110'
tags:
- DISA-STIG-UBTU-20-010417
- configure_strategy
- file_groupowner_var_log
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
find -H /var/log/ -maxdepth 1 -type d -exec chgrp 110 {} \;
|
Verify Group Who Owns /var/log/syslog File
[ref]rule To properly set the group owner of /var/log/syslog , run the command: $ sudo chgrp adm /var/log/syslog Rationale:The /var/log/syslog file contains logs of error messages in
the system and should only be accessed by authorized personnel. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Test for existence /var/log/syslog
stat:
path: /var/log/syslog
register: file_exists
tags:
- DISA-STIG-UBTU-20-010420
- configure_strategy
- file_groupowner_var_log_syslog
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner 4 on /var/log/syslog
file:
path: /var/log/syslog
group: '4'
when: file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-20-010420
- configure_strategy
- file_groupowner_var_log_syslog
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
chgrp 4 /var/log/syslog
|
Verify User Who Owns /var/log Directory
[ref]rule To properly set the owner of /var/log , run the command: $ sudo chown root /var/log Rationale:The /var/log directory contains files with logs of error
messages in the system and should only be accessed by authorized
personnel. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Ensure owner on directory /var/log/
file:
path: /var/log/
state: directory
owner: '0'
tags:
- DISA-STIG-UBTU-20-010418
- configure_strategy
- file_owner_var_log
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
find -H /var/log/ -maxdepth 1 -type d -exec chown 0 {} \;
|
Verify User Who Owns /var/log/syslog File
[ref]rule To properly set the owner of /var/log/syslog , run the command: $ sudo chown syslog /var/log/syslog Rationale:The /var/log/syslog file contains logs of error messages in
the system and should only be accessed by authorized personnel. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Test for existence /var/log/syslog
stat:
path: /var/log/syslog
register: file_exists
tags:
- DISA-STIG-UBTU-20-010421
- configure_strategy
- file_owner_var_log_syslog
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner 104 on /var/log/syslog
file:
path: /var/log/syslog
owner: '104'
when: file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-20-010421
- configure_strategy
- file_owner_var_log_syslog
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
chown 104 /var/log/syslog
|
Verify Permissions on /var/log Directory
[ref]rule
To properly set the permissions of /var/log , run the command:
$ sudo chmod 0755 /var/log Rationale:The /var/log directory contains files with logs of error
messages in the system and should only be accessed by authorized
personnel. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Set permissions for /var/log/
file:
path: /var/log/
state: directory
mode: u-s,g-ws,o-wt
tags:
- DISA-STIG-UBTU-20-010419
- configure_strategy
- file_permissions_var_log
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
chmod 0755 /var/log/
if grep -q "^z \/var\/log " /usr/lib/tmpfiles.d/00rsyslog.conf; then
sed -i --follow-symlinks "s/\(^z[[:space:]]\+\/var\/log[[:space:]]\+\)\(\([[:digit:]]\+\)[^ $]*\)/\10755/" /usr/lib/tmpfiles.d/00rsyslog.conf
fi
|
Verify Permissions on /var/log/syslog File
[ref]rule
To properly set the permissions of /var/log/syslog , run the command:
$ sudo chmod 0640 /var/log/syslog Rationale:The /var/log/syslog file contains logs of error messages in
the system and should only be accessed by authorized personnel. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Test for existence /var/log/syslog
stat:
path: /var/log/syslog
register: file_exists
tags:
- DISA-STIG-UBTU-20-010422
- configure_strategy
- file_permissions_var_log_syslog
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure permission u-xs,g-xws,o-xwrt on /var/log/syslog
file:
path: /var/log/syslog
mode: u-xs,g-xws,o-xwrt
when: file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-20-010422
- configure_strategy
- file_permissions_var_log_syslog
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
chmod u-xs,g-xws,o-xwrt /var/log/syslog
|
Verify File Permissions Within Some Important Directories
[ref]groupSome directories contain files whose confidentiality or integrity
is notably important and may also be susceptible to misconfiguration over time, particularly if
unpackaged software is installed. As such,
an argument exists to verify that files' permissions within these directories remain
configured correctly and restrictively. |
contains 15 rules |
Verify that Shared Library Directories Have Root Group Ownership
[ref]ruleSystem-wide shared library files, which are linked to executables
during process load time or run time, are stored in the following directories
by default:
/lib
/lib64
/usr/lib
/usr/lib64
Kernel modules, which can be added to the kernel during runtime, are also
stored in /lib/modules . All files in these directories should be
group-owned by the root user. If the directories, is found to be owned
by a user other than root correct its
ownership with the following command:
$ sudo chgrp root DIR Rationale:Files from shared library directories are loaded into the address
space of processes (including privileged ones) or of the kernel itself at
runtime. Proper ownership of library directories is necessary to protect
the integrity of the system. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Ensure group owner on /lib/ recursively
file:
path: /lib/
state: directory
recurse: true
group: '0'
tags:
- DISA-STIG-UBTU-20-010431
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- dir_group_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner on /lib64/ recursively
file:
path: /lib64/
state: directory
recurse: true
group: '0'
tags:
- DISA-STIG-UBTU-20-010431
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- dir_group_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner on /usr/lib/ recursively
file:
path: /usr/lib/
state: directory
recurse: true
group: '0'
tags:
- DISA-STIG-UBTU-20-010431
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- dir_group_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner on /usr/lib64/ recursively
file:
path: /usr/lib64/
state: directory
recurse: true
group: '0'
tags:
- DISA-STIG-UBTU-20-010431
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- dir_group_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
find -H /lib/ -type d -exec chgrp 0 {} \;
find -H /lib64/ -type d -exec chgrp 0 {} \;
find -H /usr/lib/ -type d -exec chgrp 0 {} \;
find -H /usr/lib64/ -type d -exec chgrp 0 {} \;
|
Verify that system commands directories are group owned by root
[ref]ruleSystem commands files are stored in the following directories by default:
/bin
/sbin
/usr/bin
/usr/sbin
/usr/local/bin
/usr/local/sbin
All these directories should be owned by the root group.
If the directory is found to be owned by a group other than root correct
its ownership with the following command:
$ sudo chgrp root DIR Rationale:If the operating system allows any user to make changes to software
libraries, then those changes might be implemented without undergoing the
appropriate testing and approvals that are part of a robust change management
process.
This requirement applies to operating systems with software libraries
that are accessible and configurable, as in the case of interpreted languages.
Software libraries also include privileged programs which execute with
escalated privileges. Only qualified and authorized individuals must be
allowed to obtain access to information system components for purposes
of initiating changes, including upgrades and modifications. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Ensure group owner on /bin/ recursively
file:
path: /bin/
state: directory
recurse: true
group: '0'
tags:
- DISA-STIG-UBTU-20-010425
- configure_strategy
- dir_groupownership_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner on /sbin/ recursively
file:
path: /sbin/
state: directory
recurse: true
group: '0'
tags:
- DISA-STIG-UBTU-20-010425
- configure_strategy
- dir_groupownership_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner on /usr/bin/ recursively
file:
path: /usr/bin/
state: directory
recurse: true
group: '0'
tags:
- DISA-STIG-UBTU-20-010425
- configure_strategy
- dir_groupownership_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner on /usr/sbin/ recursively
file:
path: /usr/sbin/
state: directory
recurse: true
group: '0'
tags:
- DISA-STIG-UBTU-20-010425
- configure_strategy
- dir_groupownership_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner on /usr/local/bin/ recursively
file:
path: /usr/local/bin/
state: directory
recurse: true
group: '0'
tags:
- DISA-STIG-UBTU-20-010425
- configure_strategy
- dir_groupownership_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner on /usr/local/sbin/ recursively
file:
path: /usr/local/sbin/
state: directory
recurse: true
group: '0'
tags:
- DISA-STIG-UBTU-20-010425
- configure_strategy
- dir_groupownership_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
find -H /bin/ -type d -exec chgrp 0 {} \;
find -H /sbin/ -type d -exec chgrp 0 {} \;
find -H /usr/bin/ -type d -exec chgrp 0 {} \;
find -H /usr/sbin/ -type d -exec chgrp 0 {} \;
find -H /usr/local/bin/ -type d -exec chgrp 0 {} \;
find -H /usr/local/sbin/ -type d -exec chgrp 0 {} \;
|
Verify that System Executable Have Root Ownership
[ref]rule/bin
/sbin
/usr/bin
/usr/sbin
/usr/local/bin
/usr/local/sbin
All these directories should be owned by the root user.
If any directory DIR in these directories is found
to be owned by a user other than root, correct its ownership with the
following command:
$ sudo chown root DIR Rationale:System binaries are executed by privileged users as well as system services,
and restrictive permissions are necessary to ensure that their
execution of these programs cannot be co-opted. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Ensure owner on directory /bin/ recursively
file:
path: /bin/
state: directory
recurse: true
owner: '0'
tags:
- DISA-STIG-UBTU-20-010424
- configure_strategy
- dir_ownership_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on directory /sbin/ recursively
file:
path: /sbin/
state: directory
recurse: true
owner: '0'
tags:
- DISA-STIG-UBTU-20-010424
- configure_strategy
- dir_ownership_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on directory /usr/bin/ recursively
file:
path: /usr/bin/
state: directory
recurse: true
owner: '0'
tags:
- DISA-STIG-UBTU-20-010424
- configure_strategy
- dir_ownership_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on directory /usr/sbin/ recursively
file:
path: /usr/sbin/
state: directory
recurse: true
owner: '0'
tags:
- DISA-STIG-UBTU-20-010424
- configure_strategy
- dir_ownership_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on directory /usr/local/bin/ recursively
file:
path: /usr/local/bin/
state: directory
recurse: true
owner: '0'
tags:
- DISA-STIG-UBTU-20-010424
- configure_strategy
- dir_ownership_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on directory /usr/local/sbin/ recursively
file:
path: /usr/local/sbin/
state: directory
recurse: true
owner: '0'
tags:
- DISA-STIG-UBTU-20-010424
- configure_strategy
- dir_ownership_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
find -H /bin/ -type d -exec chown 0 {} \;
find -H /sbin/ -type d -exec chown 0 {} \;
find -H /usr/bin/ -type d -exec chown 0 {} \;
find -H /usr/sbin/ -type d -exec chown 0 {} \;
find -H /usr/local/bin/ -type d -exec chown 0 {} \;
find -H /usr/local/sbin/ -type d -exec chown 0 {} \;
|
Verify that Shared Library Directories Have Root Ownership
[ref]ruleSystem-wide shared library files, which are linked to executables
during process load time or run time, are stored in the following directories
by default:
/lib
/lib64
/usr/lib
/usr/lib64
Kernel modules, which can be added to the kernel during runtime, are also
stored in /lib/modules . All files in these directories should be
owned by the root user. If the directories, is found to be owned
by a user other than root correct its
ownership with the following command:
$ sudo chown root DIR Rationale:Files from shared library directories are loaded into the address
space of processes (including privileged ones) or of the kernel itself at
runtime. Proper ownership of library directories is necessary to protect
the integrity of the system. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Ensure owner on directory /lib/ recursively
file:
path: /lib/
state: directory
recurse: true
owner: '0'
tags:
- DISA-STIG-UBTU-20-010429
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- dir_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on directory /lib64/ recursively
file:
path: /lib64/
state: directory
recurse: true
owner: '0'
tags:
- DISA-STIG-UBTU-20-010429
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- dir_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on directory /usr/lib/ recursively
file:
path: /usr/lib/
state: directory
recurse: true
owner: '0'
tags:
- DISA-STIG-UBTU-20-010429
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- dir_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on directory /usr/lib64/ recursively
file:
path: /usr/lib64/
state: directory
recurse: true
owner: '0'
tags:
- DISA-STIG-UBTU-20-010429
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- dir_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
find -H /lib/ -type d -exec chown 0 {} \;
find -H /lib64/ -type d -exec chown 0 {} \;
find -H /usr/lib/ -type d -exec chown 0 {} \;
find -H /usr/lib64/ -type d -exec chown 0 {} \;
|
Verify that System Executable Directories Have Restrictive Permissions
[ref]ruleSystem executables are stored in the following directories by default:
/bin
/sbin
/usr/bin
/usr/sbin
/usr/local/bin
/usr/local/sbin
These directories should not be group-writable or world-writable.
If any directory DIR in these directories is found to be
group-writable or world-writable, correct its permission with the
following command:
$ sudo chmod go-w DIR Rationale:System binaries are executed by privileged users, as well as system services,
and restrictive permissions are necessary to ensure execution of these programs
cannot be co-opted. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Set permissions for /bin/ recursively
file:
path: /bin/
state: directory
recurse: true
mode: u-s,g-ws,o-wt
tags:
- DISA-STIG-UBTU-20-010423
- configure_strategy
- dir_permissions_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set permissions for /sbin/ recursively
file:
path: /sbin/
state: directory
recurse: true
mode: u-s,g-ws,o-wt
tags:
- DISA-STIG-UBTU-20-010423
- configure_strategy
- dir_permissions_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set permissions for /usr/bin/ recursively
file:
path: /usr/bin/
state: directory
recurse: true
mode: u-s,g-ws,o-wt
tags:
- DISA-STIG-UBTU-20-010423
- configure_strategy
- dir_permissions_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set permissions for /usr/sbin/ recursively
file:
path: /usr/sbin/
state: directory
recurse: true
mode: u-s,g-ws,o-wt
tags:
- DISA-STIG-UBTU-20-010423
- configure_strategy
- dir_permissions_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set permissions for /usr/local/bin/ recursively
file:
path: /usr/local/bin/
state: directory
recurse: true
mode: u-s,g-ws,o-wt
tags:
- DISA-STIG-UBTU-20-010423
- configure_strategy
- dir_permissions_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set permissions for /usr/local/sbin/ recursively
file:
path: /usr/local/sbin/
state: directory
recurse: true
mode: u-s,g-ws,o-wt
tags:
- DISA-STIG-UBTU-20-010423
- configure_strategy
- dir_permissions_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
find -H /bin/ -perm /u+s,g+ws,o+wt -type d -exec chmod u-s,g-ws,o-wt {} \;
find -H /sbin/ -perm /u+s,g+ws,o+wt -type d -exec chmod u-s,g-ws,o-wt {} \;
find -H /usr/bin/ -perm /u+s,g+ws,o+wt -type d -exec chmod u-s,g-ws,o-wt {} \;
find -H /usr/sbin/ -perm /u+s,g+ws,o+wt -type d -exec chmod u-s,g-ws,o-wt {} \;
find -H /usr/local/bin/ -perm /u+s,g+ws,o+wt -type d -exec chmod u-s,g-ws,o-wt {} \;
find -H /usr/local/sbin/ -perm /u+s,g+ws,o+wt -type d -exec chmod u-s,g-ws,o-wt {} \;
|
Verify that Shared Library Directories Have Restrictive Permissions
[ref]ruleSystem-wide shared library directories, which contain are linked to executables
during process load time or run time, are stored in the following directories
by default:
/lib
/lib64
/usr/lib
/usr/lib64
Kernel modules, which can be added to the kernel during runtime, are
stored in /lib/modules . All sub-directories in these directories
should not be group-writable or world-writable. If any file in these
directories is found to be group-writable or world-writable, correct
its permission with the following command:
$ sudo chmod go-w DIR Rationale:If the operating system were to allow any user to make changes to software libraries,
then those changes might be implemented without undergoing the appropriate testing
and approvals that are part of a robust change management process.
This requirement applies to operating systems with software libraries that are accessible
and configurable, as in the case of interpreted languages. Software libraries also include
privileged programs which execute with escalated privileges. Only qualified and authorized
individuals must be allowed to obtain access to information system components for purposes
of initiating changes, including upgrades and modifications. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Set permissions for /lib/ recursively
file:
path: /lib/
state: directory
recurse: true
mode: g-w,o-w
tags:
- DISA-STIG-UBTU-20-010427
- NIST-800-53-CM-5
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- dir_permissions_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set permissions for /lib64/ recursively
file:
path: /lib64/
state: directory
recurse: true
mode: g-w,o-w
tags:
- DISA-STIG-UBTU-20-010427
- NIST-800-53-CM-5
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- dir_permissions_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set permissions for /usr/lib/ recursively
file:
path: /usr/lib/
state: directory
recurse: true
mode: g-w,o-w
tags:
- DISA-STIG-UBTU-20-010427
- NIST-800-53-CM-5
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- dir_permissions_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set permissions for /usr/lib64/ recursively
file:
path: /usr/lib64/
state: directory
recurse: true
mode: g-w,o-w
tags:
- DISA-STIG-UBTU-20-010427
- NIST-800-53-CM-5
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- dir_permissions_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
find -H /lib/ -perm /g+w,o+w -type d -exec chmod g-w,o-w {} \;
find -H /lib64/ -perm /g+w,o+w -type d -exec chmod g-w,o-w {} \;
find -H /usr/lib/ -perm /g+w,o+w -type d -exec chmod g-w,o-w {} \;
find -H /usr/lib64/ -perm /g+w,o+w -type d -exec chmod g-w,o-w {} \;
|
Verify that audit tools are owned by group root
[ref]ruleThe Ubuntu 20.04 operating system audit tools must have the proper
ownership configured to protected against unauthorized access.
Verify it by running the following command:
$ stat -c "%n %G" /sbin/auditctl /sbin/aureport /sbin/ausearch /sbin/autrace /sbin/auditd /sbin/audispd /sbin/augenrules
/sbin/auditctl root
/sbin/aureport root
/sbin/ausearch root
/sbin/autrace root
/sbin/auditd root
/sbin/audispd root
/sbin/augenrules root
Audit tools needed to successfully view and manipulate audit information
system activity and records. Audit tools include custom queries and report
generatorsRationale:Protecting audit information also includes identifying and protecting the
tools used to view and manipulate log data. Therefore, protecting audit
tools is necessary to prevent unauthorized operation on audit information.
Operating systems providing tools to interface with audit information
will leverage user permissions and roles identifying the user accessing the
tools and the corresponding rights the user enjoys to make access decisions
regarding the access to audit tools. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Test for existence /sbin/auditctl
stat:
path: /sbin/auditctl
register: file_exists
tags:
- DISA-STIG-UBTU-20-010201
- configure_strategy
- file_groupownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner 0 on /sbin/auditctl
file:
path: /sbin/auditctl
group: '0'
when: file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-20-010201
- configure_strategy
- file_groupownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/aureport
stat:
path: /sbin/aureport
register: file_exists
tags:
- DISA-STIG-UBTU-20-010201
- configure_strategy
- file_groupownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner 0 on /sbin/aureport
file:
path: /sbin/aureport
group: '0'
when: file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-20-010201
- configure_strategy
- file_groupownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/ausearch
stat:
path: /sbin/ausearch
register: file_exists
tags:
- DISA-STIG-UBTU-20-010201
- configure_strategy
- file_groupownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner 0 on /sbin/ausearch
file:
path: /sbin/ausearch
group: '0'
when: file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-20-010201
- configure_strategy
- file_groupownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/autrace
stat:
path: /sbin/autrace
register: file_exists
tags:
- DISA-STIG-UBTU-20-010201
- configure_strategy
- file_groupownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner 0 on /sbin/autrace
file:
path: /sbin/autrace
group: '0'
when: file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-20-010201
- configure_strategy
- file_groupownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/auditd
stat:
path: /sbin/auditd
register: file_exists
tags:
- DISA-STIG-UBTU-20-010201
- configure_strategy
- file_groupownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner 0 on /sbin/auditd
file:
path: /sbin/auditd
group: '0'
when: file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-20-010201
- configure_strategy
- file_groupownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/audispd
stat:
path: /sbin/audispd
register: file_exists
tags:
- DISA-STIG-UBTU-20-010201
- configure_strategy
- file_groupownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner 0 on /sbin/audispd
file:
path: /sbin/audispd
group: '0'
when: file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-20-010201
- configure_strategy
- file_groupownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/augenrules
stat:
path: /sbin/augenrules
register: file_exists
tags:
- DISA-STIG-UBTU-20-010201
- configure_strategy
- file_groupownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner 0 on /sbin/augenrules
file:
path: /sbin/augenrules
group: '0'
when: file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-20-010201
- configure_strategy
- file_groupownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
chgrp 0 /sbin/auditctl
chgrp 0 /sbin/aureport
chgrp 0 /sbin/ausearch
chgrp 0 /sbin/autrace
chgrp 0 /sbin/auditd
chgrp 0 /sbin/audispd
chgrp 0 /sbin/augenrules
|
Verify that system commands files are group owned by root or a system account
[ref]ruleSystem commands files are stored in the following directories by default:
/bin
/sbin
/usr/bin
/usr/sbin
/usr/local/bin
/usr/local/sbin
All files in these directories should be owned by the root group,
or a system account.
If the directory, or any file in these directories, is found to be owned
by a group other than root or a a system account correct its ownership
with the following command:
$ sudo chgrp root FILE Rationale:If the operating system allows any user to make changes to software
libraries, then those changes might be implemented without undergoing the
appropriate testing and approvals that are part of a robust change management
process.
This requirement applies to operating systems with software libraries
that are accessible and configurable, as in the case of interpreted languages.
Software libraries also include privileged programs which execute with
escalated privileges. Only qualified and authorized individuals must be
allowed to obtain access to information system components for purposes
of initiating changes, including upgrades and modifications. Remediation Shell script: (show)
for SYSCMDFILES in /bin /sbin /usr/bin /usr/sbin /usr/local/bin /usr/local/sbin
do
find -L $SYSCMDFILES \! -group root -type f -exec chgrp root '{}' \;
done
|
Verify that audit tools are owned by root
[ref]ruleThe Ubuntu 20.04 operating system audit tools must have the proper
ownership configured to protected against unauthorized access.
Verify it by running the following command:
$ stat -c "%n %U" /sbin/auditctl /sbin/aureport /sbin/ausearch /sbin/autrace /sbin/auditd /sbin/audispd /sbin/augenrules
/sbin/auditctl root
/sbin/aureport root
/sbin/ausearch root
/sbin/autrace root
/sbin/auditd root
/sbin/audispd root
/sbin/augenrules root
Audit tools needed to successfully view and manipulate audit information
system activity and records. Audit tools include custom queries and report
generatorsRationale:Protecting audit information also includes identifying and protecting the
tools used to view and manipulate log data. Therefore, protecting audit
tools is necessary to prevent unauthorized operation on audit information.
Operating systems providing tools to interface with audit information
will leverage user permissions and roles identifying the user accessing the
tools and the corresponding rights the user enjoys to make access decisions
regarding the access to audit tools. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Test for existence /sbin/auditctl
stat:
path: /sbin/auditctl
register: file_exists
tags:
- DISA-STIG-UBTU-20-010200
- configure_strategy
- file_ownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner 0 on /sbin/auditctl
file:
path: /sbin/auditctl
owner: '0'
when: file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-20-010200
- configure_strategy
- file_ownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/aureport
stat:
path: /sbin/aureport
register: file_exists
tags:
- DISA-STIG-UBTU-20-010200
- configure_strategy
- file_ownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner 0 on /sbin/aureport
file:
path: /sbin/aureport
owner: '0'
when: file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-20-010200
- configure_strategy
- file_ownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/ausearch
stat:
path: /sbin/ausearch
register: file_exists
tags:
- DISA-STIG-UBTU-20-010200
- configure_strategy
- file_ownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner 0 on /sbin/ausearch
file:
path: /sbin/ausearch
owner: '0'
when: file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-20-010200
- configure_strategy
- file_ownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/autrace
stat:
path: /sbin/autrace
register: file_exists
tags:
- DISA-STIG-UBTU-20-010200
- configure_strategy
- file_ownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner 0 on /sbin/autrace
file:
path: /sbin/autrace
owner: '0'
when: file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-20-010200
- configure_strategy
- file_ownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/auditd
stat:
path: /sbin/auditd
register: file_exists
tags:
- DISA-STIG-UBTU-20-010200
- configure_strategy
- file_ownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner 0 on /sbin/auditd
file:
path: /sbin/auditd
owner: '0'
when: file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-20-010200
- configure_strategy
- file_ownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/audispd
stat:
path: /sbin/audispd
register: file_exists
tags:
- DISA-STIG-UBTU-20-010200
- configure_strategy
- file_ownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner 0 on /sbin/audispd
file:
path: /sbin/audispd
owner: '0'
when: file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-20-010200
- configure_strategy
- file_ownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/augenrules
stat:
path: /sbin/augenrules
register: file_exists
tags:
- DISA-STIG-UBTU-20-010200
- configure_strategy
- file_ownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner 0 on /sbin/augenrules
file:
path: /sbin/augenrules
owner: '0'
when: file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-20-010200
- configure_strategy
- file_ownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
chown 0 /sbin/auditctl
chown 0 /sbin/aureport
chown 0 /sbin/ausearch
chown 0 /sbin/autrace
chown 0 /sbin/auditd
chown 0 /sbin/audispd
chown 0 /sbin/augenrules
|
Verify that System Executables Have Root Ownership
[ref]ruleSystem executables are stored in the following directories by default:
/bin
/sbin
/usr/bin
/usr/libexec
/usr/local/bin
/usr/local/sbin
/usr/sbin
All files in these directories should be owned by the root user.
If any file FILE in these directories is found
to be owned by a user other than root, correct its ownership with the
following command:
$ sudo chown root FILE Rationale:System binaries are executed by privileged users as well as system services,
and restrictive permissions are necessary to ensure that their
execution of these programs cannot be co-opted. References:
12, 13, 14, 15, 16, 18, 3, 5, APO01.06, DSS05.04, DSS05.07, DSS06.02, CCI-001499, 4.3.3.7.3, SR 2.1, SR 5.2, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2, CM-5(6), CM-5(6).1, CM-6(a), AC-6(1), PR.AC-4, PR.DS-5, SRG-OS-000259-GPOS-00100, UBTU-20-010457 |
Verify that Shared Library Files Have Root Ownership
[ref]ruleSystem-wide shared library files, which are linked to executables
during process load time or run time, are stored in the following directories
by default:
/lib
/lib64
/usr/lib
/usr/lib64
Kernel modules, which can be added to the kernel during runtime, are also
stored in /lib/modules . All files in these directories should be
owned by the root user. If the directory, or any file in these
directories, is found to be owned by a user other than root correct its
ownership with the following command:
$ sudo chown root FILE Rationale:Files from shared library directories are loaded into the address
space of processes (including privileged ones) or of the kernel itself at
runtime. Proper ownership is necessary to protect the integrity of the system. References:
12, 13, 14, 15, 16, 18, 3, 5, APO01.06, DSS05.04, DSS05.07, DSS06.02, CCI-001499, 4.3.3.7.3, SR 2.1, SR 5.2, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2, CM-5(6), CM-5(6).1, CM-6(a), AC-6(1), PR.AC-4, PR.DS-5, SRG-OS-000259-GPOS-00100, UBTU-20-010428 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Find /lib/ file(s) matching ^.*$ recursively
command: find -H /lib/ -type f ! -uid 0 -regex "^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-20-010428
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on /lib/ file(s) matching ^.*$
file:
path: '{{ item }}'
owner: '0'
state: file
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-20-010428
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /lib64/ file(s) matching ^.*$ recursively
command: find -H /lib64/ -type f ! -uid 0 -regex "^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-20-010428
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on /lib64/ file(s) matching ^.*$
file:
path: '{{ item }}'
owner: '0'
state: file
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-20-010428
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /usr/lib/ file(s) matching ^.*$ recursively
command: find -H /usr/lib/ -type f ! -uid 0 -regex "^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-20-010428
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on /usr/lib/ file(s) matching ^.*$
file:
path: '{{ item }}'
owner: '0'
state: file
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-20-010428
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /usr/lib64/ file(s) matching ^.*$ recursively
command: find -H /usr/lib64/ -type f ! -uid 0 -regex "^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-20-010428
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on /usr/lib64/ file(s) matching ^.*$
file:
path: '{{ item }}'
owner: '0'
state: file
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-20-010428
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
find /lib/ -type f ! -uid 0 -regex '^.*$' -exec chown 0 {} \;
find /lib64/ -type f ! -uid 0 -regex '^.*$' -exec chown 0 {} \;
find /usr/lib/ -type f ! -uid 0 -regex '^.*$' -exec chown 0 {} \;
find /usr/lib64/ -type f ! -uid 0 -regex '^.*$' -exec chown 0 {} \;
|
Verify that audit tools Have Mode 0755 or less
[ref]ruleThe Ubuntu 20.04 operating system audit tools must have the proper
permissions configured to protected against unauthorized access.
Verify it by running the following command:
$ stat -c "%n %a" /sbin/auditctl /sbin/aureport /sbin/ausearch /sbin/autrace /sbin/auditd /sbin/audispd /sbin/augenrules
/sbin/auditctl 755
/sbin/aureport 755
/sbin/ausearch 755
/sbin/autrace 755
/sbin/auditd 755
/sbin/audispd 755
/sbin/augenrules 755
Audit tools needed to successfully view and manipulate audit information
system activity and records. Audit tools include custom queries and report
generatorsRationale:Protecting audit information also includes identifying and protecting the
tools used to view and manipulate log data. Therefore, protecting audit
tools is necessary to prevent unauthorized operation on audit information.
Operating systems providing tools to interface with audit information
will leverage user permissions and roles identifying the user accessing the
tools and the corresponding rights the user enjoys to make access decisions
regarding the access to audit tools. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Test for existence /sbin/auditctl
stat:
path: /sbin/auditctl
register: file_exists
tags:
- DISA-STIG-UBTU-20-010199
- configure_strategy
- file_permissions_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure permission u-s,g-ws,o-wt on /sbin/auditctl
file:
path: /sbin/auditctl
mode: u-s,g-ws,o-wt
when: file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-20-010199
- configure_strategy
- file_permissions_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/aureport
stat:
path: /sbin/aureport
register: file_exists
tags:
- DISA-STIG-UBTU-20-010199
- configure_strategy
- file_permissions_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure permission u-s,g-ws,o-wt on /sbin/aureport
file:
path: /sbin/aureport
mode: u-s,g-ws,o-wt
when: file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-20-010199
- configure_strategy
- file_permissions_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/ausearch
stat:
path: /sbin/ausearch
register: file_exists
tags:
- DISA-STIG-UBTU-20-010199
- configure_strategy
- file_permissions_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure permission u-s,g-ws,o-wt on /sbin/ausearch
file:
path: /sbin/ausearch
mode: u-s,g-ws,o-wt
when: file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-20-010199
- configure_strategy
- file_permissions_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/autrace
stat:
path: /sbin/autrace
register: file_exists
tags:
- DISA-STIG-UBTU-20-010199
- configure_strategy
- file_permissions_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure permission u-s,g-ws,o-wt on /sbin/autrace
file:
path: /sbin/autrace
mode: u-s,g-ws,o-wt
when: file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-20-010199
- configure_strategy
- file_permissions_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/auditd
stat:
path: /sbin/auditd
register: file_exists
tags:
- DISA-STIG-UBTU-20-010199
- configure_strategy
- file_permissions_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure permission u-s,g-ws,o-wt on /sbin/auditd
file:
path: /sbin/auditd
mode: u-s,g-ws,o-wt
when: file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-20-010199
- configure_strategy
- file_permissions_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/audispd
stat:
path: /sbin/audispd
register: file_exists
tags:
- DISA-STIG-UBTU-20-010199
- configure_strategy
- file_permissions_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure permission u-s,g-ws,o-wt on /sbin/audispd
file:
path: /sbin/audispd
mode: u-s,g-ws,o-wt
when: file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-20-010199
- configure_strategy
- file_permissions_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/augenrules
stat:
path: /sbin/augenrules
register: file_exists
tags:
- DISA-STIG-UBTU-20-010199
- configure_strategy
- file_permissions_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure permission u-s,g-ws,o-wt on /sbin/augenrules
file:
path: /sbin/augenrules
mode: u-s,g-ws,o-wt
when: file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-20-010199
- configure_strategy
- file_permissions_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
chmod u-s,g-ws,o-wt /sbin/auditctl
chmod u-s,g-ws,o-wt /sbin/aureport
chmod u-s,g-ws,o-wt /sbin/ausearch
chmod u-s,g-ws,o-wt /sbin/autrace
chmod u-s,g-ws,o-wt /sbin/auditd
chmod u-s,g-ws,o-wt /sbin/audispd
chmod u-s,g-ws,o-wt /sbin/augenrules
|
Verify that System Executables Have Restrictive Permissions
[ref]ruleSystem executables are stored in the following directories by default:
/bin
/sbin
/usr/bin
/usr/libexec
/usr/local/bin
/usr/local/sbin
/usr/sbin
All files in these directories should not be group-writable or world-writable.
If any file FILE in these directories is found
to be group-writable or world-writable, correct its permission with the
following command:
$ sudo chmod go-w FILE Rationale:System binaries are executed by privileged users, as well as system services,
and restrictive permissions are necessary to ensure execution of these programs
cannot be co-opted. References:
12, 13, 14, 15, 16, 18, 3, 5, APO01.06, DSS05.04, DSS05.07, DSS06.02, CCI-001499, 4.3.3.7.3, SR 2.1, SR 5.2, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2, CM-5(6), CM-5(6).1, CM-6(a), AC-6(1), PR.AC-4, PR.DS-5, SRG-OS-000259-GPOS-00100, UBTU-20-010456 Remediation Shell script: (show)
DIRS="/bin /usr/bin /usr/local/bin /sbin /usr/sbin /usr/local/sbin /usr/libexec"
for dirPath in $DIRS; do
find "$dirPath" -perm /022 -exec chmod go-w '{}' \;
done
|
Verify that Shared Library Files Have Restrictive Permissions
[ref]ruleSystem-wide shared library files, which are linked to executables
during process load time or run time, are stored in the following directories
by default:
/lib
/lib64
/usr/lib
/usr/lib64
Kernel modules, which can be added to the kernel during runtime, are
stored in /lib/modules . All files in these directories
should not be group-writable or world-writable. If any file in these
directories is found to be group-writable or world-writable, correct
its permission with the following command:
$ sudo chmod go-w FILE Rationale:Files from shared library directories are loaded into the address
space of processes (including privileged ones) or of the kernel itself at
runtime. Restrictive permissions are necessary to protect the integrity of the system. References:
12, 13, 14, 15, 16, 18, 3, 5, APO01.06, DSS05.04, DSS05.07, DSS06.02, CCI-001499, 4.3.3.7.3, SR 2.1, SR 5.2, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2, CM-6(a), CM-5(6), CM-5(6).1, AC-6(1), PR.AC-4, PR.DS-5, SRG-OS-000259-GPOS-00100, UBTU-20-010426 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Find /lib/ file(s) recursively
command: find -H /lib/ -perm /g+w,o+w -type f -regex "^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-20-010426
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_permissions_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set permissions for /lib/ file(s)
file:
path: '{{ item }}'
mode: g-w,o-w
state: file
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-20-010426
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_permissions_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /lib64/ file(s) recursively
command: find -H /lib64/ -perm /g+w,o+w -type f -regex "^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-20-010426
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_permissions_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set permissions for /lib64/ file(s)
file:
path: '{{ item }}'
mode: g-w,o-w
state: file
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-20-010426
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_permissions_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /usr/lib/ file(s) recursively
command: find -H /usr/lib/ -perm /g+w,o+w -type f -regex "^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-20-010426
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_permissions_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set permissions for /usr/lib/ file(s)
file:
path: '{{ item }}'
mode: g-w,o-w
state: file
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-20-010426
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_permissions_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /usr/lib64/ file(s) recursively
command: find -H /usr/lib64/ -perm /g+w,o+w -type f -regex "^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-20-010426
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_permissions_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set permissions for /usr/lib64/ file(s)
file:
path: '{{ item }}'
mode: g-w,o-w
state: file
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-20-010426
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_permissions_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
find -H /lib/ -perm /g+w,o+w -type f -regex '^.*$' -exec chmod g-w,o-w {} \;
find -H /lib64/ -perm /g+w,o+w -type f -regex '^.*$' -exec chmod g-w,o-w {} \;
find -H /usr/lib/ -perm /g+w,o+w -type f -regex '^.*$' -exec chmod g-w,o-w {} \;
find -H /usr/lib64/ -perm /g+w,o+w -type f -regex '^.*$' -exec chmod g-w,o-w {} \;
|
Verify the system-wide library files in directories
"/lib", "/lib64", "/usr/lib/" and "/usr/lib64" are group-owned by root.
[ref]ruleSystem-wide library files are stored in the following directories
by default:
/lib
/lib64
/usr/lib
/usr/lib64
All system-wide shared library files should be protected from unauthorised
access. If any of these files is not group-owned by root, correct its group-owner with
the following command:
$ sudo chgrp root FILE Rationale:If the operating system were to allow any user to make changes to software libraries,
then those changes might be implemented without undergoing the appropriate testing and
approvals that are part of a robust change management process.
This requirement applies to operating systems with software libraries that are
accessible and configurable, as in the case of interpreted languages. Software libraries
also include privileged programs which execute with escalated privileges. Only qualified
and authorized individuals must be allowed to obtain access to information system components
for purposes of initiating changes, including upgrades and modifications. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Find /lib/ file(s) matching ^.*$ recursively
command: find -H /lib/ -type f ! -gid 0 -regex "^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-20-01430
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- root_permissions_syslibrary_files
- name: Ensure group owner on /lib/ file(s) matching ^.*$
file:
path: '{{ item }}'
group: '0'
state: file
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-20-01430
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- root_permissions_syslibrary_files
- name: Find /lib64/ file(s) matching ^.*$ recursively
command: find -H /lib64/ -type f ! -gid 0 -regex "^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-20-01430
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- root_permissions_syslibrary_files
- name: Ensure group owner on /lib64/ file(s) matching ^.*$
file:
path: '{{ item }}'
group: '0'
state: file
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-20-01430
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- root_permissions_syslibrary_files
- name: Find /usr/lib/ file(s) matching ^.*$ recursively
command: find -H /usr/lib/ -type f ! -gid 0 -regex "^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-20-01430
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- root_permissions_syslibrary_files
- name: Ensure group owner on /usr/lib/ file(s) matching ^.*$
file:
path: '{{ item }}'
group: '0'
state: file
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-20-01430
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- root_permissions_syslibrary_files
- name: Find /usr/lib64/ file(s) matching ^.*$ recursively
command: find -H /usr/lib64/ -type f ! -gid 0 -regex "^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-20-01430
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- root_permissions_syslibrary_files
- name: Ensure group owner on /usr/lib64/ file(s) matching ^.*$
file:
path: '{{ item }}'
group: '0'
state: file
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-20-01430
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- root_permissions_syslibrary_files
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
find /lib/ -type f ! -gid 0 -regex '^.*$' -exec chgrp 0 {} \;
find /lib64/ -type f ! -gid 0 -regex '^.*$' -exec chgrp 0 {} \;
find /usr/lib/ -type f ! -gid 0 -regex '^.*$' -exec chgrp 0 {} \;
find /usr/lib64/ -type f ! -gid 0 -regex '^.*$' -exec chgrp 0 {} \;
|
Verify that All World-Writable Directories Have Sticky Bits Set
[ref]ruleWhen the so-called 'sticky bit' is set on a directory,
only the owner of a given file may remove that file from the
directory. Without the sticky bit, any user with write access to a
directory may remove any file in the directory. Setting the sticky
bit prevents users from removing each other's files. In cases where
there is no reason for a directory to be world-writable, a better
solution is to remove that permission rather than to set the sticky
bit. However, if a directory is used by a particular application,
consult that application's documentation instead of blindly
changing modes.
To set the sticky bit on a world-writable directory DIR, run the
following command:
$ sudo chmod +t DIR Rationale:Failing to set the sticky bit on public directories allows unauthorized
users to delete files in the directory structure.
The only authorized public directories are those temporary directories
supplied with the system, or those designed to be temporary file
repositories. The setting is normally reserved for directories used by the
system, by users for temporary file storage (such as /tmp ), and
for directories requiring global read/write access. References:
BP28(R40), 1.1.22, 12, 13, 14, 15, 16, 18, 3, 5, APO01.06, DSS05.04, DSS05.07, DSS06.02, CCI-001090, 4.3.3.7.3, SR 2.1, SR 5.2, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2, CM-6(a), AC-6(1), PR.AC-4, PR.DS-5, SRG-OS-000138-GPOS-00069, UBTU-20-010411 Remediation Shell script: (show)
df --local -P | awk '{if (NR!=1) print $6}' \
| xargs -I '$6' find '$6' -xdev -type d \
\( -perm -0002 -a ! -perm -1000 \) 2>/dev/null \
-exec chmod a+t {} +
|
Verify Permissions on /etc/audit/auditd.conf
[ref]rule
To properly set the permissions of /etc/audit/auditd.conf , run the command:
$ sudo chmod 0640 /etc/audit/auditd.conf Rationale:Without the capability to restrict the roles and individuals that can select which events
are audited, unauthorized personnel may be able to prevent the auditing of critical
events. Misconfigured audits may degrade the system's performance by overwhelming
the audit log. Misconfigured audits may also make it more difficult to establish,
correlate, and investigate the events relating to an incident or identify
those responsible for one. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Test for existence /etc/audit/auditd.conf
stat:
path: /etc/audit/auditd.conf
register: file_exists
tags:
- DISA-STIG-UBTU-20-010133
- NIST-800-53-AU-12(b)
- configure_strategy
- file_permissions_etc_audit_auditd
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure permission u-xs,g-xws,o-xwrt on /etc/audit/auditd.conf
file:
path: /etc/audit/auditd.conf
mode: u-xs,g-xws,o-xwrt
when: file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-20-010133
- NIST-800-53-AU-12(b)
- configure_strategy
- file_permissions_etc_audit_auditd
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
chmod u-xs,g-xws,o-xwrt /etc/audit/auditd.conf
|
Verify Permissions on /etc/audit/rules.d/*.rules
[ref]rule
To properly set the permissions of /etc/audit/rules.d/*.rules , run the command:
$ sudo chmod 0640 /etc/audit/rules.d/*.rules Rationale:Without the capability to restrict the roles and individuals that can select which events
are audited, unauthorized personnel may be able to prevent the auditing of critical
events. Misconfigured audits may degrade the system's performance by overwhelming
the audit log. Misconfigured audits may also make it more difficult to establish,
correlate, and investigate the events relating to an incident or identify
those responsible for one. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Find /etc/audit/rules.d/ file(s)
command: find -H /etc/audit/rules.d/ -maxdepth 1 -perm /u+xs,g+xws,o+xwrt -type
f -regex "^.*rules$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-20-010133
- NIST-800-53-AU-12(b)
- configure_strategy
- file_permissions_etc_audit_rulesd
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set permissions for /etc/audit/rules.d/ file(s)
file:
path: '{{ item }}'
mode: u-xs,g-xws,o-xwrt
state: file
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-20-010133
- NIST-800-53-AU-12(b)
- configure_strategy
- file_permissions_etc_audit_rulesd
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
find -H /etc/audit/rules.d/ -maxdepth 1 -perm /u+xs,g+xws,o+xwrt -type f -regex '^.*rules$' -exec chmod u-xs,g-xws,o-xwrt {} \;
|
Verify permissions of log files
[ref]ruleAny operating system providing too much information in error messages
risks compromising the data and security of the structure, and content
of error messages needs to be carefully considered by the organization.
Organizations carefully consider the structure/content of error messages.
The extent to which information systems are able to identify and handle
error conditions is guided by organizational policy and operational
requirements. Information that could be exploited by adversaries includes,
for example, erroneous logon attempts with passwords entered by mistake
as the username, mission/business information that can be derived from
(if not stated explicitly by) information recorded, and personal
information, such as account numbers, social security numbers, and credit
card numbers. Rationale:The Ubuntu 20.04 must generate error messages that provide information
necessary for corrective actions without revealing information that could
be exploited by adversaries. Remediation Shell script: (show)
readarray -t files < <(find /var/log/)
for file in "${files[@]}"; do
if basename $file | grep -qE '^.*$'; then
chmod 0640 $file
fi
done
if grep -qE "^f \/var\/log\/(btmp|wtmp|lastlog)? " /usr/lib/tmpfiles.d/var.conf; then
sed -i --follow-symlinks "s/\(^f[[:space:]]\+\/var\/log\/btmp[[:space:]]\+\)\(\([[:digit:]]\+\)[^ $]*\)/\10640/" /usr/lib/tmpfiles.d/var.conf
sed -i --follow-symlinks "s/\(^f[[:space:]]\+\/var\/log\/wtmp[[:space:]]\+\)\(\([[:digit:]]\+\)[^ $]*\)/\10640/" /usr/lib/tmpfiles.d/var.conf
sed -i --follow-symlinks "s/\(^f[[:space:]]\+\/var\/log\/lastlog[[:space:]]\+\)\(\([[:digit:]]\+\)[^ $]*\)/\10640/" /usr/lib/tmpfiles.d/var.conf
fi
|
Restrict Programs from Dangerous Execution Patterns
[ref]groupThe recommendations in this section are designed to
ensure that the system's features to protect against potentially
dangerous program execution are activated.
These protections are applied at the system initialization or
kernel level, and defend against certain types of badly-configured
or compromised programs. |
contains 2 rules |
Enable ExecShield
[ref]groupExecShield describes kernel features that provide
protection against exploitation of memory corruption errors such as buffer
overflows. These features include random placement of the stack and other
memory regions, prevention of execution in memory that should only hold data,
and special handling of text buffers. These protections are enabled by default
on 32-bit systems and controlled through sysctl variables
kernel.exec-shield and kernel.randomize_va_space . On the latest
64-bit systems, kernel.exec-shield cannot be enabled or disabled with
sysctl . |
contains 1 rule |
Enable Randomized Layout of Virtual Address Space
[ref]ruleTo set the runtime status of the kernel.randomize_va_space kernel parameter, run the following command: $ sudo sysctl -w kernel.randomize_va_space=2
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d : kernel.randomize_va_space = 2 Rationale:Address space layout randomization (ASLR) makes it more difficult for an
attacker to predict the location of attack code they have introduced into a
process's address space during an attempt at exploitation. Additionally,
ASLR makes it more difficult for an attacker to know the location of
existing code in order to re-purpose it using return oriented programming
(ROP) techniques. References:
BP28(R23), 1.6.2, 3.1.7, CCI-000366, CCI-002824, 164.308(a)(1)(ii)(D), 164.308(a)(3), 164.308(a)(4), 164.310(b), 164.310(c), 164.312(a), 164.312(e), CIP-002-5 R1.1, CIP-002-5 R1.2, CIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 4.1, CIP-004-6 4.2, CIP-004-6 R2.2.3, CIP-004-6 R2.2.4, CIP-004-6 R2.3, CIP-004-6 R4, CIP-005-6 R1, CIP-005-6 R1.1, CIP-005-6 R1.2, CIP-007-3 R3, CIP-007-3 R3.1, CIP-007-3 R5.1, CIP-007-3 R5.1.2, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3, CIP-007-3 R8.4, CIP-009-6 R.1.1, CIP-009-6 R4, SC-30, SC-30(2), CM-6(a), Req-2.2.1, SRG-OS-000433-GPOS-00193, SRG-OS-000480-GPOS-00227, UBTU-20-010448 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: List /etc/sysctl.d/*.conf files
find:
paths:
- /etc/sysctl.d/
- /run/sysctl.d/
- /usr/local/lib/sysctl.d/
- /usr/lib/sysctl.d/
contains: ^[\s]*kernel.randomize_va_space.*$
patterns: '*.conf'
file_type: any
register: find_sysctl_d
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010448
- NIST-800-171-3.1.7
- NIST-800-53-CM-6(a)
- NIST-800-53-SC-30
- NIST-800-53-SC-30(2)
- PCI-DSS-Req-2.2.1
- disable_strategy
- low_complexity
- medium_disruption
- medium_severity
- reboot_required
- sysctl_kernel_randomize_va_space
- name: Comment out any occurrences of kernel.randomize_va_space from config files
replace:
path: '{{ item.path }}'
regexp: ^[\s]*kernel.randomize_va_space
replace: '#kernel.randomize_va_space'
loop: '{{ find_sysctl_d.files }}'
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010448
- NIST-800-171-3.1.7
- NIST-800-53-CM-6(a)
- NIST-800-53-SC-30
- NIST-800-53-SC-30(2)
- PCI-DSS-Req-2.2.1
- disable_strategy
- low_complexity
- medium_disruption
- medium_severity
- reboot_required
- sysctl_kernel_randomize_va_space
- name: Ensure sysctl kernel.randomize_va_space is set to 2
sysctl:
name: kernel.randomize_va_space
value: '2'
state: present
reload: true
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010448
- NIST-800-171-3.1.7
- NIST-800-53-CM-6(a)
- NIST-800-53-SC-30
- NIST-800-53-SC-30(2)
- PCI-DSS-Req-2.2.1
- disable_strategy
- low_complexity
- medium_disruption
- medium_severity
- reboot_required
- sysctl_kernel_randomize_va_space
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
# Comment out any occurrences of kernel.randomize_va_space from /etc/sysctl.d/*.conf files
for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf /usr/lib/sysctl.d/*.conf; do
matching_list=$(grep -P '^(?!#).*[\s]*kernel.randomize_va_space.*$' $f | uniq )
if ! test -z "$matching_list"; then
while IFS= read -r entry; do
escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
# comment out "kernel.randomize_va_space" matches to preserve user data
sed -i "s/^${escaped_entry}$/# &/g" $f
done <<< "$matching_list"
fi
done
#
# Set runtime for kernel.randomize_va_space
#
/sbin/sysctl -q -n -w kernel.randomize_va_space="2"
#
# If kernel.randomize_va_space present in /etc/sysctl.conf, change value to "2"
# else, add "kernel.randomize_va_space = 2" to /etc/sysctl.conf
#
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "/etc/sysctl.conf"; then
sed_command+=('--follow-symlinks')
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^kernel.randomize_va_space")
# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "2"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^kernel.randomize_va_space\\>" "/etc/sysctl.conf"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
"${sed_command[@]}" "s/^kernel.randomize_va_space\\>.*/$escaped_formatted_output/gi" "/etc/sysctl.conf"
else
# \n is precaution for case where file ends without trailing newline
printf '%s\n' "$formatted_output" >> "/etc/sysctl.conf"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Enable Execute Disable (XD) or No Execute (NX) Support on
x86 Systems
[ref]groupRecent processors in the x86 family support the
ability to prevent code execution on a per memory page basis.
Generically and on AMD processors, this ability is called No
Execute (NX), while on Intel processors it is called Execute
Disable (XD). This ability can help prevent exploitation of buffer
overflow vulnerabilities and should be activated whenever possible.
Extra steps must be taken to ensure that this protection is
enabled, particularly on 32-bit x86 systems. Other processors, such
as Itanium and POWER, have included such support since inception
and the standard kernel for those platforms supports the
feature. This is enabled by default on the latest Oracle Linux, Red Hat and
Fedora systems if supported by the hardware. |
contains 1 rule |
Enable NX or XD Support in the BIOS
[ref]ruleReboot the system and enter the BIOS or Setup configuration menu.
Navigate the BIOS configuration menu and make sure that the option is enabled. The setting may be located
under a Security section. Look for Execute Disable (XD) on Intel-based systems and No Execute (NX)
on AMD-based systems. Rationale:Computers with the ability to prevent this type of code execution frequently put an option in the BIOS that will
allow users to turn the feature on or off at will. References:
BP28(R9), 11, 3, 9, BAI10.01, BAI10.02, BAI10.03, BAI10.05, 3.1.7, CCI-002824, 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, SC-39, CM-6(a), PR.IP-1, Req-2.2.1, SRG-OS-000433-GPOS-00192, UBTU-20-010447 |
Services
[ref]groupThe best protection against vulnerable software is running less software. This section describes how to review
the software which Ubuntu 20.04 installs on a system and disable software which is not needed. It
then enumerates the software packages installed on a default Ubuntu 20.04 system and provides guidance about which
ones can be safely disabled.
Ubuntu 20.04 provides a convenient minimal install option that essentially installs the bare necessities for a functional
system. When building Ubuntu 20.04 systems, it is highly recommended to select the minimal packages and then build up
the system from there. |
contains 21 rules |
APT service configuration
[ref]groupThe apt service manage the package management and update of the whole system. Its configuration need to be properly defined to ensure efficient security updates, packages and repository authentication and proper lifecycle management. |
contains 1 rule |
Disable unauthenticated repositories in APT configuration
[ref]ruleUnauthenticated repositories should not be used for updates. Rationale:Repositories hosts all packages that will be intsalled on the system during update.
If a repository is not authenticated, the associated packages can't be trusted,
and then should not be installed localy. |
Base Services
[ref]groupThis section addresses the base services that are installed on a
Ubuntu 20.04 default installation which are not covered in other
sections. Some of these services listen on the network and
should be treated with particular discretion. Other services are local
system utilities that may or may not be extraneous. In general, system services
should be disabled if not required. |
contains 1 rule |
Disable KDump Kernel Crash Analyzer (kdump)
[ref]ruleThe kdump service provides a kernel crash dump analyzer. It uses the kexec
system call to boot a secondary kernel ("capture" kernel) following a system
crash, which can load information from the crashed kernel for analysis.
The kdump service can be disabled with the following command:
$ sudo systemctl mask --now kdump.service Rationale:Kernel core dumps may contain the full contents of system memory at the
time of the crash. Kernel core dumps consume a considerable amount of disk
space and may result in denial of service by exhausting the available space
on the target file system partition. Unless the system is used for kernel
development or testing, there is little need to run the kdump service. References:
11, 12, 14, 15, 3, 8, 9, APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.04, DSS05.02, DSS05.03, DSS05.05, DSS06.06, CCI-000366, CCI-001665, 164.308(a)(1)(ii)(D), 164.308(a)(3), 164.308(a)(4), 164.310(b), 164.310(c), 164.312(a), 164.312(e), 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 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.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3, SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, 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.11.2.6, A.12.1.2, A.12.5.1, A.12.6.2, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.6.2.1, A.6.2.2, A.9.1.2, CM-7(a), CM-7(b), CM-6(a), PR.AC-3, PR.IP-1, PR.PT-3, PR.PT-4, FMT_SMF_EXT.1.1, SRG-OS-000269-GPOS-00103, SRG-OS-000480-GPOS-00227, UBTU-20-010413 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | disable |
---|
- name: Disable service kdump
block:
- name: Disable service kdump
systemd:
name: kdump.service
enabled: 'no'
state: stopped
masked: 'yes'
ignore_errors: 'yes'
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010413
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- disable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- service_kdump_disabled
- name: Unit Socket Exists - kdump.socket
command: systemctl list-unit-files kdump.socket
register: socket_file_exists
changed_when: false
ignore_errors: true
check_mode: false
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010413
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- disable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- service_kdump_disabled
- name: Disable socket kdump
systemd:
name: kdump.socket
enabled: 'no'
state: stopped
masked: 'yes'
when:
- ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
- '"kdump.socket" in socket_file_exists.stdout_lines[1]'
tags:
- DISA-STIG-UBTU-20-010413
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- disable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- service_kdump_disabled
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | disable |
---|
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
SYSTEMCTL_EXEC='/usr/bin/systemctl'
"$SYSTEMCTL_EXEC" stop 'kdump.service'
"$SYSTEMCTL_EXEC" disable 'kdump.service'
"$SYSTEMCTL_EXEC" mask 'kdump.service'
# Disable socket activation if we have a unit file for it
if "$SYSTEMCTL_EXEC" -q list-unit-files kdump.socket; then
"$SYSTEMCTL_EXEC" stop 'kdump.socket'
"$SYSTEMCTL_EXEC" mask 'kdump.socket'
fi
# The service may not be running because it has been started and failed,
# so let's reset the state so OVAL checks pass.
# Service should be 'inactive', not 'failed' after reboot though.
"$SYSTEMCTL_EXEC" reset-failed 'kdump.service' || true
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
config:
ignition:
version: 3.1.0
systemd:
units:
- name: kdump.service
enabled: false
mask: true
- name: kdump.socket
enabled: false
mask: true
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include disable_kdump
class disable_kdump {
service {'kdump':
enable => false,
ensure => 'stopped',
}
}
Remediation script: (show)
[customizations.services]
disabled = ["kdump"]
|
Deprecated services
[ref]groupSome deprecated software services impact the overall system security due to their behavior (leak of
confidentiality in network exchange, usage as uncontrolled communication channel, risk associated with the service due to its old age, etc. |
contains 1 rule |
Uninstall the telnet server
[ref]ruleThe telnet daemon should be uninstalled. Rationale:telnet allows clear text communications, and does not protect
any data transmission between client and server. Any confidential data
can be listened and no integrity checking is made.'
References:
BP28(R1), NT007(R03), 11, 12, 14, 15, 3, 8, 9, APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.04, DSS05.02, DSS05.03, DSS05.05, DSS06.06, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 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.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3, SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, 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.11.2.6, A.12.1.2, A.12.5.1, A.12.6.2, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.6.2.1, A.6.2.2, A.9.1.2, CM-7(a), CM-7(b), CM-6(a), PR.AC-3, PR.IP-1, PR.PT-3, PR.PT-4, UBTU-20-010405 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | disable |
---|
- name: Ensure telnetd is removed
package:
name: telnetd
state: absent
tags:
- DISA-STIG-UBTU-20-010405
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- disable_strategy
- high_severity
- low_complexity
- low_disruption
- no_reboot_needed
- package_telnetd_removed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | disable |
---|
# CAUTION: This remediation script will remove telnetd
# from the system, and may remove any packages
# that depend on telnetd. Execute this
# remediation AFTER testing on a non-production
# system!
DEBIAN_FRONTEND=noninteractive apt-get remove -y "telnetd"
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | disable |
---|
include remove_telnetd
class remove_telnetd {
package { 'telnetd':
ensure => 'purged',
}
}
|
Network Time Protocol
[ref]groupThe Network Time Protocol is used to manage the system
clock over a network. Computer clocks are not very accurate, so
time will drift unpredictably on unmanaged systems. Central time
protocols can be used both to ensure that time is consistent among
a network of systems, and that their time is consistent with the
outside world.
If every system on a network reliably reports the same time, then it is much
easier to correlate log messages in case of an attack. In addition, a number of
cryptographic protocols (such as Kerberos) use timestamps to prevent certain
types of attacks. If your network does not have synchronized time, these
protocols may be unreliable or even unusable.
Depending on the specifics of the network, global time accuracy may be just as
important as local synchronization, or not very important at all. If your
network is connected to the Internet, using a public timeserver (or one
provided by your enterprise) provides globally accurate timestamps which may be
essential in investigating or responding to an attack which originated outside
of your network.
A typical network setup involves a small number of internal systems operating
as NTP servers, and the remainder obtaining time information from those
internal servers.
There is a choice between the daemons ntpd and chronyd , which
are available from the repositories in the ntp and chrony
packages respectively.
The default chronyd daemon can work well when external time references
are only intermittently accesible, can perform well even when the network is
congested for longer periods of time, can usually synchronize the clock faster
and with better time accuracy, and quickly adapts to sudden changes in the rate
of the clock, for example, due to changes in the temperature of the crystal
oscillator. Chronyd should be considered for all systems which are
frequently suspended or otherwise intermittently disconnected and reconnected
to a network. Mobile and virtual systems for example.
The ntpd NTP daemon fully supports NTP protocol version 4 (RFC 5905),
including broadcast, multicast, manycast clients and servers, and the orphan
mode. It also supports extra authentication schemes based on public-key
cryptography (RFC 5906). The NTP daemon (ntpd ) should be considered
for systems which are normally kept permanently on. Systems which are required
to use broadcast or multicast IP, or to perform authentication of packets with
the Autokey protocol, should consider using ntpd .
Refer to
https://help.ubuntu.com/lts/serverguide/NTP.html
for more detailed comparison of features of chronyd
and ntpd daemon features respectively, and for further guidance how to
choose between the two NTP daemons.
The upstream manual pages at
http://chrony.tuxfamily.org/manual.html for
chronyd and
http://www.ntp.org for ntpd provide additional
information on the capabilities and configuration of each of the NTP daemons. |
contains 3 rules |
The Chrony package is installed
[ref]ruleSystem time should be synchronized between all systems in an environment. This is
typically done by establishing an authoritative time server or set of servers and having all
systems synchronize their clocks to them.
The chrony package can be installed with the following command:
$ apt-get install chrony Rationale:Time synchronization is important to support time sensitive security mechanisms like
Kerberos and also ensures log files have consistent time records across the enterprise,
which aids in forensic investigations. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Ensure chrony is installed
package:
name: chrony
state: present
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010435
- PCI-DSS-Req-10.6.1
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- package_chrony_installed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
DEBIAN_FRONTEND=noninteractive apt-get install -y "chrony"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include install_chrony
class install_chrony {
package { 'chrony':
ensure => 'installed',
}
}
Remediation script: (show)
[[packages]]
name = "chrony"
version = "*"
|
Configure Time Service Maxpoll Interval
[ref]ruleThe maxpoll should be configured to
17 in /etc/ntp.conf or
/etc/chrony/chrony.conf to continuously poll time servers. To configure
maxpoll in /etc/ntp.conf or /etc/chrony/chrony.conf
add the following after each `server`, `pool` or `peer` entry:
maxpoll 17
to server directives. If using chrony any pool directives
should be configured too.
If no server or pool directives are configured, the rule evaluates
to pass.Rationale:Inaccurate time stamps make it more difficult to correlate events and can lead to an inaccurate analysis. Determining the correct time a particular event occurred on a system is critical when conducting forensic analysis and investigating system events. Sources outside the configured acceptable allowance (drift) may be inaccurate.
Synchronizing internal information system clocks provides uniformity of time stamps for information systems with multiple system clocks and systems connected over a network.
Organizations should consider endpoints that may not have regular access to the authoritative time server (e.g., mobile, teleworking, and tactical endpoints). References:
1, 14, 15, 16, 3, 5, 6, APO11.04, BAI03.05, DSS05.04, DSS05.07, MEA02.01, CCI-001891, CCI-002046, 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, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, CM-6(a), AU-8(1)(b), AU-12(1), PR.PT-1, SRG-OS-000355-GPOS-00143, SRG-OS-000356-GPOS-00144, SRG-OS-000359-GPOS-00146, UBTU-20-010435 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && { ( dpkg-query --show --showformat='${db:Status-Status}\n' 'chrony' 2>/dev/null | grep -q installed || dpkg-query --show --showformat='${db:Status-Status}\n' 'ntp' 2>/dev/null | grep -q installed ); }; then
var_time_service_set_maxpoll='17'
pof="/bin/pidof"
CONFIG_FILES="/etc/ntp.conf"
$pof ntpd || {
CHRONY_NAME=/etc/chrony/chrony.conf
CHRONY_PATH=${CHRONY_NAME%%.*}
CONFIG_FILES=$(find ${CHRONY_PATH}.* -type f -name '*.conf')
}
# get list of ntp files
for config_file in $CONFIG_FILES; do
# Set maxpoll values to var_time_service_set_maxpoll
sed -i "s/^\(\(server\|pool\|peer\).*maxpoll\) [0-9][0-9]*\(.*\)$/\1 $var_time_service_set_maxpoll \3/" "$config_file"
done
for config_file in $CONFIG_FILES; do
# Add maxpoll to server, pool or peer entries without maxpoll
grep "^\(server\|pool\|peer\)" "$config_file" | grep -v maxpoll | while read -r line ; do
sed -i "s/$line/& maxpoll $var_time_service_set_maxpoll/" "$config_file"
done
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Synchronize internal information system clocks
[ref]ruleSynchronizing internal information system clocks provides uniformity
of time stamps for information systems with multiple system clocks and
systems connected over a network. Rationale:Inaccurate time stamps make it more difficult to correlate events and
can lead to an inaccurate analysis. Determining the correct time a
particular event occurred on a system is critical when conducting
forensic analysis and investigating system events. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Synchronize internal information system clocks
lineinfile:
path: /etc/chrony/chrony.conf
create: true
line: makestep 1 -1
state: present
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010436
- chronyd_sync_clock
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
if [ -e "/etc/chrony/chrony.conf" ] ; then
LC_ALL=C sed -i "/^\s*makestep 1 \-1/Id" "/etc/chrony/chrony.conf"
else
touch "/etc/chrony/chrony.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/chrony/chrony.conf"
cp "/etc/chrony/chrony.conf" "/etc/chrony/chrony.conf.bak"
# Insert at the end of the file
printf '%s\n' "makestep 1 -1" >> "/etc/chrony/chrony.conf"
# Clean up after ourselves.
rm "/etc/chrony/chrony.conf.bak"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Obsolete Services
[ref]groupThis section discusses a number of network-visible
services which have historically caused problems for system
security, and for which disabling or severely limiting the service
has been the best available guidance for some time. As a result of
this, many of these services are not installed as part of Ubuntu 20.04
by default.
Organizations which are running these services should
switch to more secure equivalents as soon as possible.
If it remains absolutely necessary to run one of
these services for legacy reasons, care should be taken to restrict
the service as much as possible, for instance by configuring host
firewall software such as iptables to restrict access to the
vulnerable service to only those remote hosts which have a known
need to use it. |
contains 1 rule |
Rlogin, Rsh, and Rexec
[ref]groupThe Berkeley r-commands are legacy services which
allow cleartext remote access and have an insecure trust
model. |
contains 1 rule |
Uninstall rsh-server Package
[ref]ruleThe rsh-server package can be removed with the following command:
$ apt-get remove rsh-server Rationale:The rsh-server service provides unencrypted remote access service which does not
provide for the confidentiality and integrity of user passwords or the remote session and has very weak
authentication. If a privileged user were to login using this service, the privileged user password
could be compromised. The rsh-server package provides several obsolete and insecure
network services. Removing it decreases the risk of those services' accidental (or intentional)
activation. References:
BP28(R1), 11, 12, 14, 15, 3, 8, 9, APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.04, DSS05.02, DSS05.03, DSS05.05, DSS06.06, CCI-000381, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 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.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3, SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, 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.11.2.6, A.12.1.2, A.12.5.1, A.12.6.2, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.6.2.1, A.6.2.2, A.9.1.2, CM-7(a), CM-7(b), CM-6(a), IA-5(1)(c), PR.AC-3, PR.IP-1, PR.PT-3, PR.PT-4, SRG-OS-000095-GPOS-00049, UBTU-20-010406 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | disable |
---|
- name: Ensure rsh-server is removed
package:
name: rsh-server
state: absent
tags:
- DISA-STIG-UBTU-20-010406
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- NIST-800-53-IA-5(1)(c)
- disable_strategy
- high_severity
- low_complexity
- low_disruption
- no_reboot_needed
- package_rsh-server_removed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | disable |
---|
# CAUTION: This remediation script will remove rsh-server
# from the system, and may remove any packages
# that depend on rsh-server. Execute this
# remediation AFTER testing on a non-production
# system!
DEBIAN_FRONTEND=noninteractive apt-get remove -y "rsh-server"
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | disable |
---|
include remove_rsh-server
class remove_rsh-server {
package { 'rsh-server':
ensure => 'purged',
}
}
|
SSH Server
[ref]groupThe SSH protocol is recommended for remote login and
remote file transfer. SSH provides confidentiality and integrity
for data exchanged between two systems, as well as server
authentication, through the use of public key cryptography. The
implementation included with the system is called OpenSSH, and more
detailed documentation is available from its website,
https://www.openssh.com.
Its server program is called sshd and provided by the RPM package
openssh-server . |
contains 13 rules |
Configure OpenSSH Server if Necessary
[ref]groupIf the system needs to act as an SSH server, then
certain changes should be made to the OpenSSH daemon configuration
file /etc/ssh/sshd_config . The following recommendations can be
applied to this file. See the sshd_config(5) man page for more
detailed information. |
contains 11 rules |
Set SSH Client Alive Count Max
[ref]ruleThe SSH server sends at most ClientAliveCountMax messages
during a SSH session and waits for a response from the SSH client.
The option ClientAliveInterval configures timeout after
each ClientAliveCountMax message. If the SSH server does not
receive a response from the client, then the connection is considered unresponsive
and terminated.
For SSH earlier than v8.2, a ClientAliveCountMax value of 0
causes a timeout precisely when the ClientAliveInterval is set.
Starting with v8.2, a value of 0 disables the timeout functionality
completely. If the option is set to a number greater than 0 , then
the session will be disconnected after
ClientAliveInterval * ClientAliveCountMax seconds without receiving
a keep alive message. Rationale:This ensures a user login will be terminated as soon as the ClientAliveInterval
is reached. References:
BP28(R29), 5.2.15, 1, 12, 13, 14, 15, 16, 18, 3, 5, 7, 8, 5.5.6, APO13.01, BAI03.01, BAI03.02, BAI03.03, DSS01.03, DSS03.05, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, 3.1.11, CCI-000879, CCI-001133, CCI-002361, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), 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.3, 4.3.3.7.4, 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 6.2, A.12.4.1, A.12.4.3, A.14.1.1, A.14.2.1, A.14.2.5, A.18.1.4, A.6.1.2, A.6.1.5, A.7.1.1, A.9.1.2, 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.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5, 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-2(5), AC-12, AC-17(a), SC-10, CM-6(a), DE.CM-1, DE.CM-3, PR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.IP-2, Req-8.1.8, SRG-OS-000163-GPOS-00072, SRG-OS-000279-GPOS-00109, UBTU-20-010036, SRG-OS-000480-VMM-002000 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: XCCDF Value var_sshd_set_keepalive # promote to variable
set_fact:
var_sshd_set_keepalive: !!str 1
tags:
- always
- name: Set SSH Client Alive Count Max
block:
- name: Check for duplicate values
lineinfile:
path: /etc/ssh/sshd_config
create: false
regexp: (?i)^\s*ClientAliveCountMax\s+
state: absent
check_mode: true
changed_when: false
register: dupes
- name: Deduplicate values from /etc/ssh/sshd_config
lineinfile:
path: /etc/ssh/sshd_config
create: false
regexp: (?i)^\s*ClientAliveCountMax\s+
state: absent
when: dupes.found is defined and dupes.found > 1
- name: Insert correct line to /etc/ssh/sshd_config
lineinfile:
path: /etc/ssh/sshd_config
create: true
regexp: (?i)^\s*ClientAliveCountMax\s+
line: ClientAliveCountMax {{ var_sshd_set_keepalive }}
state: present
insertbefore: ^[#\s]*Match
validate: /usr/sbin/sshd -t -f %s
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.5.6
- DISA-STIG-UBTU-20-010036
- NIST-800-171-3.1.11
- NIST-800-53-AC-12
- NIST-800-53-AC-17(a)
- NIST-800-53-AC-2(5)
- NIST-800-53-CM-6(a)
- NIST-800-53-SC-10
- PCI-DSS-Req-8.1.8
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_set_keepalive
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
var_sshd_set_keepalive='1'
if [ -e "/etc/ssh/sshd_config" ] ; then
LC_ALL=C sed -i "/^\s*ClientAliveCountMax\s\+/Id" "/etc/ssh/sshd_config"
else
touch "/etc/ssh/sshd_config"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config"
cp "/etc/ssh/sshd_config" "/etc/ssh/sshd_config.bak"
# Insert before the line matching the regex '^Match'.
line_number="$(LC_ALL=C grep -n "^Match" "/etc/ssh/sshd_config.bak" | LC_ALL=C sed 's/:.*//g')"
if [ -z "$line_number" ]; then
# There was no match of '^Match', insert at
# the end of the file.
printf '%s\n' "ClientAliveCountMax $var_sshd_set_keepalive" >> "/etc/ssh/sshd_config"
else
head -n "$(( line_number - 1 ))" "/etc/ssh/sshd_config.bak" > "/etc/ssh/sshd_config"
printf '%s\n' "ClientAliveCountMax $var_sshd_set_keepalive" >> "/etc/ssh/sshd_config"
tail -n "+$(( line_number ))" "/etc/ssh/sshd_config.bak" >> "/etc/ssh/sshd_config"
fi
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.bak"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Set SSH Client Alive Interval
[ref]ruleSSH allows administrators to set a network responsiveness timeout interval.
After this interval has passed, the unresponsive client will be automatically logged out.
To set this timeout interval, edit the following line in /etc/ssh/sshd_config as
follows:
ClientAliveInterval 600
The timeout interval is given in seconds. For example, have a timeout
of 10 minutes, set interval to 600.
If a shorter timeout has already been set for the login shell, that value will
preempt any SSH setting made in /etc/ssh/sshd_config . Keep in mind that
some processes may stop SSH from correctly detecting that the user is idle.Warning:
SSH disconnecting unresponsive clients will not have desired effect without also
configuring ClientAliveCountMax in the SSH service configuration. Warning:
Following conditions may prevent the SSH session to time out:
- Remote processes on the remote machine generates output. As the output has to be transferred over the network to the client, the timeout is reset every time such transfer happens.
- Any
scp or sftp activity by the same user to the host resets the timeout.
Rationale:Terminating an idle ssh 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 let unattended. References:
BP28(R29), 5.2.15, 1, 12, 13, 14, 15, 16, 18, 3, 5, 7, 8, 5.5.6, APO13.01, BAI03.01, BAI03.02, BAI03.03, DSS01.03, DSS03.05, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, 3.1.11, CCI-000879, CCI-001133, CCI-002361, 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.3, 4.3.3.7.4, 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 6.2, A.12.4.1, A.12.4.3, A.14.1.1, A.14.2.1, A.14.2.5, A.18.1.4, A.6.1.2, A.6.1.5, A.7.1.1, A.9.1.2, 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.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5, 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, CM-6(a), AC-17(a), AC-2(5), AC-12, AC-17(a), SC-10, CM-6(a), DE.CM-1, DE.CM-3, PR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.IP-2, Req-8.1.8, SRG-OS-000126-GPOS-00066, SRG-OS-000163-GPOS-00072, SRG-OS-000279-GPOS-00109, SRG-OS-000395-GPOS-00175, UBTU-20-010037, SRG-OS-000480-VMM-002000 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
sshd_idle_timeout_value='600'
if [ -e "/etc/ssh/sshd_config" ] ; then
LC_ALL=C sed -i "/^\s*ClientAliveInterval\s\+/Id" "/etc/ssh/sshd_config"
else
touch "/etc/ssh/sshd_config"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config"
cp "/etc/ssh/sshd_config" "/etc/ssh/sshd_config.bak"
# Insert before the line matching the regex '^Match'.
line_number="$(LC_ALL=C grep -n "^Match" "/etc/ssh/sshd_config.bak" | LC_ALL=C sed 's/:.*//g')"
if [ -z "$line_number" ]; then
# There was no match of '^Match', insert at
# the end of the file.
printf '%s\n' "ClientAliveInterval $sshd_idle_timeout_value" >> "/etc/ssh/sshd_config"
else
head -n "$(( line_number - 1 ))" "/etc/ssh/sshd_config.bak" > "/etc/ssh/sshd_config"
printf '%s\n' "ClientAliveInterval $sshd_idle_timeout_value" >> "/etc/ssh/sshd_config"
tail -n "+$(( line_number ))" "/etc/ssh/sshd_config.bak" >> "/etc/ssh/sshd_config"
fi
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.bak"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Disable SSH Access via Empty Passwords
[ref]ruleDisallow SSH login with empty passwords.
The default SSH configuration disables logins with empty passwords. The appropriate
configuration is used if no value is set for PermitEmptyPasswords .
To explicitly disallow SSH login from accounts with empty passwords,
add or correct the following line in
/etc/ssh/sshd_config :
PermitEmptyPasswords no
Any accounts with empty passwords should be disabled immediately, and PAM configuration
should prevent users from being able to assign themselves empty passwords.Rationale:Configuring this setting for the SSH daemon provides additional assurance
that remote login via SSH will require a password, even in the event of
misconfiguration elsewhere. References:
NT007(R17), 5.2.10, 11, 12, 13, 14, 15, 16, 18, 3, 5, 9, 5.5.6, APO01.06, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.02, DSS06.03, DSS06.06, 3.1.1, 3.1.5, CCI-000366, CCI-000766, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 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.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3, SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 5.2, SR 7.6, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.1.2, A.12.5.1, A.12.6.2, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.1, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, AC-17(a), CM-7(a), CM-7(b), CM-6(a), PR.AC-4, PR.AC-6, PR.DS-5, PR.IP-1, PR.PT-3, FIA_UAU.1, Req-2.2.6, SRG-OS-000106-GPOS-00053, SRG-OS-000480-GPOS-00229, SRG-OS-000480-GPOS-00227, UBTU-20-010047, SRG-OS-000480-VMM-002000 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Disable SSH Access via Empty Passwords
block:
- name: Check for duplicate values
lineinfile:
path: /etc/ssh/sshd_config
create: false
regexp: (?i)^\s*PermitEmptyPasswords\s+
state: absent
check_mode: true
changed_when: false
register: dupes
- name: Deduplicate values from /etc/ssh/sshd_config
lineinfile:
path: /etc/ssh/sshd_config
create: false
regexp: (?i)^\s*PermitEmptyPasswords\s+
state: absent
when: dupes.found is defined and dupes.found > 1
- name: Insert correct line to /etc/ssh/sshd_config
lineinfile:
path: /etc/ssh/sshd_config
create: true
regexp: (?i)^\s*PermitEmptyPasswords\s+
line: PermitEmptyPasswords no
state: present
insertbefore: ^[#\s]*Match
validate: /usr/sbin/sshd -t -f %s
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.5.6
- DISA-STIG-UBTU-20-010047
- NIST-800-171-3.1.1
- NIST-800-171-3.1.5
- NIST-800-53-AC-17(a)
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- PCI-DSS-Req-2.2.6
- high_severity
- low_complexity
- low_disruption
- no_reboot_needed
- restrict_strategy
- sshd_disable_empty_passwords
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
if [ -e "/etc/ssh/sshd_config" ] ; then
LC_ALL=C sed -i "/^\s*PermitEmptyPasswords\s\+/Id" "/etc/ssh/sshd_config"
else
touch "/etc/ssh/sshd_config"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config"
cp "/etc/ssh/sshd_config" "/etc/ssh/sshd_config.bak"
# Insert before the line matching the regex '^Match'.
line_number="$(LC_ALL=C grep -n "^Match" "/etc/ssh/sshd_config.bak" | LC_ALL=C sed 's/:.*//g')"
if [ -z "$line_number" ]; then
# There was no match of '^Match', insert at
# the end of the file.
printf '%s\n' "PermitEmptyPasswords no" >> "/etc/ssh/sshd_config"
else
head -n "$(( line_number - 1 ))" "/etc/ssh/sshd_config.bak" > "/etc/ssh/sshd_config"
printf '%s\n' "PermitEmptyPasswords no" >> "/etc/ssh/sshd_config"
tail -n "+$(( line_number ))" "/etc/ssh/sshd_config.bak" >> "/etc/ssh/sshd_config"
fi
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.bak"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Disable X11 Forwarding
[ref]ruleThe X11Forwarding parameter provides the ability to tunnel X11 traffic
through the connection to enable remote graphic connections.
SSH has the capability to encrypt remote X11 connections when SSH's
X11Forwarding option is enabled.
The default SSH configuration disables X11Forwarding. The appropriate
configuration is used if no value is set for X11Forwarding .
To explicitly disable X11 Forwarding, add or correct the following line in
/etc/ssh/sshd_config :
X11Forwarding no Rationale:Disable X11 forwarding unless there is an operational requirement to use X11
applications directly. There is a small risk that the remote X11 servers of
users who are logged in via SSH with X11 forwarding could be compromised by
other users on the X11 server. Note that even if X11 forwarding is disabled,
users can always install their own forwarders. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Disable X11 Forwarding
block:
- name: Check for duplicate values
lineinfile:
path: /etc/ssh/sshd_config
create: false
regexp: (?i)^\s*X11Forwarding\s+
state: absent
check_mode: true
changed_when: false
register: dupes
- name: Deduplicate values from /etc/ssh/sshd_config
lineinfile:
path: /etc/ssh/sshd_config
create: false
regexp: (?i)^\s*X11Forwarding\s+
state: absent
when: dupes.found is defined and dupes.found > 1
- name: Insert correct line to /etc/ssh/sshd_config
lineinfile:
path: /etc/ssh/sshd_config
create: true
regexp: (?i)^\s*X11Forwarding\s+
line: X11Forwarding no
state: present
insertbefore: ^[#\s]*Match
validate: /usr/sbin/sshd -t -f %s
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010048
- NIST-800-53-CM-6(b)
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_disable_x11_forwarding
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
if [ -e "/etc/ssh/sshd_config" ] ; then
LC_ALL=C sed -i "/^\s*X11Forwarding\s\+/Id" "/etc/ssh/sshd_config"
else
touch "/etc/ssh/sshd_config"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config"
cp "/etc/ssh/sshd_config" "/etc/ssh/sshd_config.bak"
# Insert before the line matching the regex '^Match'.
line_number="$(LC_ALL=C grep -n "^Match" "/etc/ssh/sshd_config.bak" | LC_ALL=C sed 's/:.*//g')"
if [ -z "$line_number" ]; then
# There was no match of '^Match', insert at
# the end of the file.
printf '%s\n' "X11Forwarding no" >> "/etc/ssh/sshd_config"
else
head -n "$(( line_number - 1 ))" "/etc/ssh/sshd_config.bak" > "/etc/ssh/sshd_config"
printf '%s\n' "X11Forwarding no" >> "/etc/ssh/sshd_config"
tail -n "+$(( line_number ))" "/etc/ssh/sshd_config.bak" >> "/etc/ssh/sshd_config"
fi
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.bak"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Do Not Allow SSH Environment Options
[ref]ruleEnsure that users are not able to override environment variables of the SSH daemon.
The default SSH configuration disables environment processing. The appropriate
configuration is used if no value is set for PermitUserEnvironment .
To explicitly disable Environment options, add or correct the following
/etc/ssh/sshd_config :
PermitUserEnvironment no Rationale:SSH environment options potentially allow users to bypass
access restriction in some configurations. References:
5.2.11, 11, 3, 9, 5.5.6, BAI10.01, BAI10.02, BAI10.03, BAI10.05, 3.1.12, CCI-000366, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), 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-17(a), CM-7(a), CM-7(b), CM-6(a), PR.IP-1, Req-2.2.6, SRG-OS-000480-GPOS-00229, UBTU-20-010047, SRG-OS-000480-VMM-002000 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Do Not Allow SSH Environment Options
block:
- name: Check for duplicate values
lineinfile:
path: /etc/ssh/sshd_config
create: false
regexp: (?i)^\s*PermitUserEnvironment\s+
state: absent
check_mode: true
changed_when: false
register: dupes
- name: Deduplicate values from /etc/ssh/sshd_config
lineinfile:
path: /etc/ssh/sshd_config
create: false
regexp: (?i)^\s*PermitUserEnvironment\s+
state: absent
when: dupes.found is defined and dupes.found > 1
- name: Insert correct line to /etc/ssh/sshd_config
lineinfile:
path: /etc/ssh/sshd_config
create: true
regexp: (?i)^\s*PermitUserEnvironment\s+
line: PermitUserEnvironment no
state: present
insertbefore: ^[#\s]*Match
validate: /usr/sbin/sshd -t -f %s
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.5.6
- DISA-STIG-UBTU-20-010047
- NIST-800-171-3.1.12
- NIST-800-53-AC-17(a)
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- PCI-DSS-Req-2.2.6
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_do_not_permit_user_env
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
if [ -e "/etc/ssh/sshd_config" ] ; then
LC_ALL=C sed -i "/^\s*PermitUserEnvironment\s\+/Id" "/etc/ssh/sshd_config"
else
touch "/etc/ssh/sshd_config"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config"
cp "/etc/ssh/sshd_config" "/etc/ssh/sshd_config.bak"
# Insert before the line matching the regex '^Match'.
line_number="$(LC_ALL=C grep -n "^Match" "/etc/ssh/sshd_config.bak" | LC_ALL=C sed 's/:.*//g')"
if [ -z "$line_number" ]; then
# There was no match of '^Match', insert at
# the end of the file.
printf '%s\n' "PermitUserEnvironment no" >> "/etc/ssh/sshd_config"
else
head -n "$(( line_number - 1 ))" "/etc/ssh/sshd_config.bak" > "/etc/ssh/sshd_config"
printf '%s\n' "PermitUserEnvironment no" >> "/etc/ssh/sshd_config"
tail -n "+$(( line_number ))" "/etc/ssh/sshd_config.bak" >> "/etc/ssh/sshd_config"
fi
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.bak"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Enable PAM
[ref]ruleUsePAM Enables the Pluggable Authentication Module interface. If set to “yes” this will
enable PAM authentication using ChallengeResponseAuthentication and
PasswordAuthentication in addition to PAM account and session module processing for all
authentication types.
To enable PAM authentication, add or correct the following line in
/etc/ssh/sshd_config :
UsePAM yes Rationale:When UsePAM is set to yes, PAM runs through account and session types properly. This is
important if you want to restrict access to services based off of IP, time or other factors of
the account. Additionally, you can make sure users inherit certain environment variables
on login or disallow access to the server. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Enable PAM
block:
- name: Check for duplicate values
lineinfile:
path: /etc/ssh/sshd_config
create: false
regexp: (?i)^\s*UsePAM\s+
state: absent
check_mode: true
changed_when: false
register: dupes
- name: Deduplicate values from /etc/ssh/sshd_config
lineinfile:
path: /etc/ssh/sshd_config
create: false
regexp: (?i)^\s*UsePAM\s+
state: absent
when: dupes.found is defined and dupes.found > 1
- name: Insert correct line to /etc/ssh/sshd_config
lineinfile:
path: /etc/ssh/sshd_config
create: true
regexp: (?i)^\s*UsePAM\s+
line: UsePAM yes
state: present
insertbefore: ^[#\s]*Match
validate: /usr/sbin/sshd -t -f %s
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010035
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_enable_pam
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
if [ -e "/etc/ssh/sshd_config" ] ; then
LC_ALL=C sed -i "/^\s*UsePAM\s\+/Id" "/etc/ssh/sshd_config"
else
touch "/etc/ssh/sshd_config"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config"
cp "/etc/ssh/sshd_config" "/etc/ssh/sshd_config.bak"
# Insert before the line matching the regex '^Match'.
line_number="$(LC_ALL=C grep -n "^Match" "/etc/ssh/sshd_config.bak" | LC_ALL=C sed 's/:.*//g')"
if [ -z "$line_number" ]; then
# There was no match of '^Match', insert at
# the end of the file.
printf '%s\n' "UsePAM yes" >> "/etc/ssh/sshd_config"
else
head -n "$(( line_number - 1 ))" "/etc/ssh/sshd_config.bak" > "/etc/ssh/sshd_config"
printf '%s\n' "UsePAM yes" >> "/etc/ssh/sshd_config"
tail -n "+$(( line_number ))" "/etc/ssh/sshd_config.bak" >> "/etc/ssh/sshd_config"
fi
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.bak"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Enable Public Key Authentication
[ref]ruleEnable SSH login with public keys.
The default SSH configuration enables authentication based on public keys. The appropriate
configuration is used if no value is set for PubkeyAuthentication .
To explicitly enable Public Key Authentication, add or correct the following
/etc/ssh/sshd_config :
PubkeyAuthentication yes Rationale:Without the use of multifactor authentication, the ease of access to
privileged functions is greatly increased. Multifactor authentication
requires using two or more factors to achieve authentication.
A privileged account is defined as an information system account with
authorizations of a privileged user.
The DoD CAC with DoD-approved PKI is an example of multifactor
authentication. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Enable Public Key Authentication
block:
- name: Check for duplicate values
lineinfile:
path: /etc/ssh/sshd_config
create: false
regexp: (?i)^\s*PubkeyAuthentication\s+
state: absent
check_mode: true
changed_when: false
register: dupes
- name: Deduplicate values from /etc/ssh/sshd_config
lineinfile:
path: /etc/ssh/sshd_config
create: false
regexp: (?i)^\s*PubkeyAuthentication\s+
state: absent
when: dupes.found is defined and dupes.found > 1
- name: Insert correct line to /etc/ssh/sshd_config
lineinfile:
path: /etc/ssh/sshd_config
create: true
regexp: (?i)^\s*PubkeyAuthentication\s+
line: PubkeyAuthentication yes
state: present
insertbefore: ^[#\s]*Match
validate: /usr/sbin/sshd -t -f %s
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010033
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_enable_pubkey_auth
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
if [ -e "/etc/ssh/sshd_config" ] ; then
LC_ALL=C sed -i "/^\s*PubkeyAuthentication\s\+/Id" "/etc/ssh/sshd_config"
else
touch "/etc/ssh/sshd_config"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config"
cp "/etc/ssh/sshd_config" "/etc/ssh/sshd_config.bak"
# Insert before the line matching the regex '^Match'.
line_number="$(LC_ALL=C grep -n "^Match" "/etc/ssh/sshd_config.bak" | LC_ALL=C sed 's/:.*//g')"
if [ -z "$line_number" ]; then
# There was no match of '^Match', insert at
# the end of the file.
printf '%s\n' "PubkeyAuthentication yes" >> "/etc/ssh/sshd_config"
else
head -n "$(( line_number - 1 ))" "/etc/ssh/sshd_config.bak" > "/etc/ssh/sshd_config"
printf '%s\n' "PubkeyAuthentication yes" >> "/etc/ssh/sshd_config"
tail -n "+$(( line_number ))" "/etc/ssh/sshd_config.bak" >> "/etc/ssh/sshd_config"
fi
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.bak"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Enable SSH Warning Banner
[ref]ruleTo enable the warning banner and ensure it is consistent
across the system, add or correct the following line in
/etc/ssh/sshd_config :
Banner /etc/issue.net
Another section contains information on how to create an
appropriate system-wide warning banner.Rationale:The warning message reinforces policy awareness during the logon process and
facilitates possible legal action against attackers. Alternatively, systems
whose ownership should not be obvious should ensure usage of a banner that does
not provide easy attribution. References:
5.2.18, 5.5.6, DSS05.04, DSS05.10, DSS06.10, 3.1.9, CCI-000048, CCI-000050, CCI-001384, CCI-001385, CCI-001386, CCI-001387, CCI-001388, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), 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, AC-8(a), AC-8(c), AC-17(a), CM-6(a), PR.AC-7, FTA_TAB.1, SRG-OS-000023-GPOS-00006, SRG-OS-000228-GPOS-00088, UBTU-20-010038, SRG-OS-000023-VMM-000060, SRG-OS-000024-VMM-000070 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Enable SSH Warning Banner
block:
- name: Check for duplicate values
lineinfile:
path: /etc/ssh/sshd_config
create: false
regexp: (?i)^\s*Banner\s+
state: absent
check_mode: true
changed_when: false
register: dupes
- name: Deduplicate values from /etc/ssh/sshd_config
lineinfile:
path: /etc/ssh/sshd_config
create: false
regexp: (?i)^\s*Banner\s+
state: absent
when: dupes.found is defined and dupes.found > 1
- name: Insert correct line to /etc/ssh/sshd_config
lineinfile:
path: /etc/ssh/sshd_config
create: true
regexp: (?i)^\s*Banner\s+
line: Banner /etc/issue.net
state: present
insertbefore: ^[#\s]*Match
validate: /usr/sbin/sshd -t -f %s
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CJIS-5.5.6
- DISA-STIG-UBTU-20-010038
- NIST-800-171-3.1.9
- NIST-800-53-AC-17(a)
- NIST-800-53-AC-8(a)
- NIST-800-53-AC-8(c)
- NIST-800-53-CM-6(a)
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_enable_warning_banner_net
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
if [ -e "/etc/ssh/sshd_config" ] ; then
LC_ALL=C sed -i "/^\s*Banner\s\+/Id" "/etc/ssh/sshd_config"
else
touch "/etc/ssh/sshd_config"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config"
cp "/etc/ssh/sshd_config" "/etc/ssh/sshd_config.bak"
# Insert before the line matching the regex '^Match'.
line_number="$(LC_ALL=C grep -n "^Match" "/etc/ssh/sshd_config.bak" | LC_ALL=C sed 's/:.*//g')"
if [ -z "$line_number" ]; then
# There was no match of '^Match', insert at
# the end of the file.
printf '%s\n' "Banner /etc/issue.net" >> "/etc/ssh/sshd_config"
else
head -n "$(( line_number - 1 ))" "/etc/ssh/sshd_config.bak" > "/etc/ssh/sshd_config"
printf '%s\n' "Banner /etc/issue.net" >> "/etc/ssh/sshd_config"
tail -n "+$(( line_number ))" "/etc/ssh/sshd_config.bak" >> "/etc/ssh/sshd_config"
fi
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.bak"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Use Only FIPS 140-2 Validated Ciphers
[ref]ruleLimit the ciphers to those algorithms which are FIPS-approved.
The following line in /etc/ssh/sshd_config
demonstrates use of FIPS-approved ciphers:
Ciphers aes256-ctr,aes192-ctr,aes128-ctr
This rule ensures that there are configured ciphers mentioned
above (or their subset), keeping the given order of algorithms.Warning:
The system needs to be rebooted for these changes to take effect. Warning:
System Crypto Modules must be provided by a vendor that undergoes
FIPS-140 certifications.
FIPS-140 is applicable to all Federal agencies that use
cryptographic-based security systems to protect sensitive information
in computer and telecommunication systems (including voice systems) as
defined in Section 5131 of the Information Technology Management Reform
Act of 1996, Public Law 104-106. This standard shall be used in
designing and implementing cryptographic modules that Federal
departments and agencies operate or are operated for them under
contract. See https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.140-2.pdf
To meet this, the system has to have cryptographic software provided by
a vendor that has undergone this certification. This means providing
documentation, test results, design information, and independent third
party review by an accredited lab. While open source software is
capable of meeting this, it does not meet FIPS-140 unless the vendor
submits to this process. Rationale:Unapproved mechanisms that are used for authentication to the cryptographic module are not verified and therefore
cannot be relied upon to provide confidentiality or integrity, and system data may be compromised.
Operating systems utilizing encryption are required to use FIPS-compliant mechanisms for authenticating to
cryptographic modules.
FIPS 140-2 is the current standard for validating that mechanisms used to access cryptographic modules
utilize authentication that meets industry and government requirements. For government systems, this allows
Security Levels 1, 2, 3, or 4 for use on Ubuntu 20.04. References:
CCI-000068, CCI-000366, CCI-000803, CCI-000877, CCI-002890, CCI-003123, SRG-OS-000033-GPOS-00014, SRG-OS-000120-GPOS-00061, SRG-OS-000125-GPOS-00065, SRG-OS-000250-GPOS-00093, SRG-OS-000393-GPOS-00173, SRG-OS-000394-GPOS-00174, UBTU-20-010044 Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
if grep -q -P '^\s*[Cc]iphers\s+' /etc/ssh/sshd_config; then
sed -i 's/^\s*[Cc]iphers.*/Ciphers aes256-ctr,aes192-ctr,aes128-ctr/' /etc/ssh/sshd_config
else
echo "Ciphers aes256-ctr,aes192-ctr,aes128-ctr" >> /etc/ssh/sshd_config
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Use Only FIPS 140-2 Validated MACs
[ref]ruleLimit the MACs to those hash algorithms which are FIPS-approved.
The following line in /etc/ssh/sshd_config
demonstrates use of FIPS-approved MACs:
MACs hmac-sha2-512,hmac-sha2-256
This rule ensures that there are configured MACs mentioned
above (or their subset), keeping the given order of algorithms.Warning:
The system needs to be rebooted for these changes to take effect. Warning:
System Crypto Modules must be provided by a vendor that undergoes
FIPS-140 certifications.
FIPS-140 is applicable to all Federal agencies that use
cryptographic-based security systems to protect sensitive information
in computer and telecommunication systems (including voice systems) as
defined in Section 5131 of the Information Technology Management Reform
Act of 1996, Public Law 104-106. This standard shall be used in
designing and implementing cryptographic modules that Federal
departments and agencies operate or are operated for them under
contract. See https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.140-2.pdf
To meet this, the system has to have cryptographic software provided by
a vendor that has undergone this certification. This means providing
documentation, test results, design information, and independent third
party review by an accredited lab. While open source software is
capable of meeting this, it does not meet FIPS-140 unless the vendor
submits to this process. Rationale:DoD Information Systems are required to use FIPS-approved cryptographic hash
functions. The only SSHv2 hash algorithms meeting this requirement is SHA2. Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
if grep -q -P '^\s*MACs\s+' /etc/ssh/sshd_config; then
sed -i 's/^\s*MACs.*/MACs hmac-sha2-512,hmac-sha2-256/' /etc/ssh/sshd_config
else
echo "MACs hmac-sha2-512,hmac-sha2-256" >> /etc/ssh/sshd_config
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Prevent remote hosts from connecting to the proxy display
[ref]ruleThe SSH daemon should prevent remote hosts from connecting to the proxy
display.
The default SSH configuration for X11UseLocalhost is yes ,
which prevents remote hosts from connecting to the proxy display.
To explicitly prevent remote connections to the proxy display, add or correct
the following line in
/etc/ssh/sshd_config :
X11UseLocalhost yes Rationale:When X11 forwarding is enabled, there may be additional exposure to the
server and client displays if the sshd proxy display is configured to listen
on the wildcard address. By default, sshd binds the forwarding server to the
loopback address and sets the hostname part of the DISPLAY
environment variable to localhost. This prevents remote hosts from
connecting to the proxy display. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Prevent remote hosts from connecting to the proxy display
block:
- name: Check for duplicate values
lineinfile:
path: /etc/ssh/sshd_config
create: false
regexp: (?i)^\s*X11UseLocalhost\s+
state: absent
check_mode: true
changed_when: false
register: dupes
- name: Deduplicate values from /etc/ssh/sshd_config
lineinfile:
path: /etc/ssh/sshd_config
create: false
regexp: (?i)^\s*X11UseLocalhost\s+
state: absent
when: dupes.found is defined and dupes.found > 1
- name: Insert correct line to /etc/ssh/sshd_config
lineinfile:
path: /etc/ssh/sshd_config
create: true
regexp: (?i)^\s*X11UseLocalhost\s+
line: X11UseLocalhost yes
state: present
insertbefore: ^[#\s]*Match
validate: /usr/sbin/sshd -t -f %s
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010049
- NIST-800-53-CM-6(b)
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_x11_use_localhost
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
if [ -e "/etc/ssh/sshd_config" ] ; then
LC_ALL=C sed -i "/^\s*X11UseLocalhost\s\+/Id" "/etc/ssh/sshd_config"
else
touch "/etc/ssh/sshd_config"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config"
cp "/etc/ssh/sshd_config" "/etc/ssh/sshd_config.bak"
# Insert before the line matching the regex '^Match'.
line_number="$(LC_ALL=C grep -n "^Match" "/etc/ssh/sshd_config.bak" | LC_ALL=C sed 's/:.*//g')"
if [ -z "$line_number" ]; then
# There was no match of '^Match', insert at
# the end of the file.
printf '%s\n' "X11UseLocalhost yes" >> "/etc/ssh/sshd_config"
else
head -n "$(( line_number - 1 ))" "/etc/ssh/sshd_config.bak" > "/etc/ssh/sshd_config"
printf '%s\n' "X11UseLocalhost yes" >> "/etc/ssh/sshd_config"
tail -n "+$(( line_number ))" "/etc/ssh/sshd_config.bak" >> "/etc/ssh/sshd_config"
fi
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.bak"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Install the OpenSSH Server Package
[ref]ruleThe openssh-server package should be installed.
The openssh-server package can be installed with the following command:
$ apt-get install openssh-server Rationale:Without protection of the transmitted information, confidentiality, and
integrity may be compromised because unprotected communications can be
intercepted and either read or altered. References:
13, 14, APO01.06, DSS05.02, DSS05.04, DSS05.07, DSS06.02, DSS06.06, CCI-002418, CCI-002420, CCI-002421, CCI-002422, SR 3.1, SR 3.8, SR 4.1, SR 4.2, SR 5.2, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CM-6(a), PR.DS-2, PR.DS-5, FIA_UAU.5, FTP_ITC_EXT.1, FCS_SSH_EXT.1, FCS_SSHS_EXT.1, SRG-OS-000423-GPOS-00187, SRG-OS-000424-GPOS-00188, SRG-OS-000425-GPOS-00189, SRG-OS-000426-GPOS-00190, UBTU-20-010042 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Ensure openssh-server is installed
package:
name: openssh-server
state: present
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010042
- NIST-800-53-CM-6(a)
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- package_openssh-server_installed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
DEBIAN_FRONTEND=noninteractive apt-get install -y "openssh-server"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include install_openssh-server
class install_openssh-server {
package { 'openssh-server':
ensure => 'installed',
}
}
Remediation script: (show)
[[packages]]
name = "openssh-server"
version = "*"
|
Enable the OpenSSH Service
[ref]ruleThe SSH server service, sshd, is commonly needed.
The sshd service can be enabled with the following command:
$ sudo systemctl enable sshd.service Rationale:Without protection of the transmitted information, confidentiality, and
integrity may be compromised because unprotected communications can be
intercepted and either read or altered.
This checklist item applies to both internal and external networks and all types
of information system components from which information can be transmitted (e.g., servers,
mobile devices, notebook computers, printers, copiers, scanners, etc). Communication paths
outside the physical protection of a controlled boundary are exposed to the possibility
of interception and modification. References:
13, 14, APO01.06, DSS05.02, DSS05.04, DSS05.07, DSS06.02, DSS06.06, 3.1.13, 3.5.4, 3.13.8, CCI-002418, CCI-002420, CCI-002421, CCI-002422, SR 3.1, SR 3.8, SR 4.1, SR 4.2, SR 5.2, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CM-6(a), SC-8, SC-8(1), SC-8(2), SC-8(3), SC-8(4), PR.DS-2, PR.DS-5, SRG-OS-000423-GPOS-00187, SRG-OS-000424-GPOS-00188, SRG-OS-000425-GPOS-00189, SRG-OS-000426-GPOS-00190, UBTU-20-010042 Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Enable service ssh
block:
- name: Gather the package facts
package_facts:
manager: auto
- name: Enable service ssh
service:
name: ssh
enabled: 'yes'
state: started
masked: 'no'
when:
- '"openssh-server" in ansible_facts.packages'
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-20-010042
- NIST-800-171-3.1.13
- NIST-800-171-3.13.8
- NIST-800-171-3.5.4
- NIST-800-53-CM-6(a)
- NIST-800-53-SC-8
- NIST-800-53-SC-8(1)
- NIST-800-53-SC-8(2)
- NIST-800-53-SC-8(3)
- NIST-800-53-SC-8(4)
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- service_sshd_enabled
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
SYSTEMCTL_EXEC='/usr/bin/systemctl'
"$SYSTEMCTL_EXEC" unmask 'ssh.service'
"$SYSTEMCTL_EXEC" start 'ssh.service'
"$SYSTEMCTL_EXEC" enable 'ssh.service'
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include enable_ssh
class enable_ssh {
service {'ssh':
enable => true,
ensure => 'running',
}
}
Remediation script: (show)
[customizations.services]
enabled = ["ssh"]
|
System Security Services Daemon
[ref]groupThe System Security Services Daemon (SSSD) is a system daemon that provides access
to different identity and authentication providers such as Red Hat's IdM, Microsoft's AD,
openLDAP, MIT Kerberos, etc. It uses a common framework that can provide caching and offline
support to systems utilizing SSSD. SSSD using caching to reduce load on authentication
servers permit offline authentication as well as store extended user data.
For more information, see |
contains 1 rule |
Configure SSSD to Expire Offline Credentials
[ref]ruleSSSD should be configured to expire offline credentials after 1 day.
To configure SSSD to expire offline credentials, set
offline_credentials_expiration to 1 under the [pam]
section in /etc/sssd/sssd.conf . For example:
[pam]
offline_credentials_expiration = 1
Rationale:If cached authentication information is out-of-date, the validity of the
authentication information may be questionable. References:
1, 12, 15, 16, 5, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, CCI-002007, 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, 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), IA-5(13), PR.AC-1, PR.AC-6, PR.AC-7, SRG-OS-000383-GPOS-00166, UBTU-20-010441, SRG-OS-000383-VMM-001570 |