## Guide to the Secure Configuration of Red Hat Enterprise Linux 6

> with profile PCI-DSS v3 Control Baseline for Red Hat Enterprise Linux 6
> This is a *draft* profile for PCI-DSS v3.

The SCAP Security Guide Project
https://www.open-scap.org/security-policies/scap-security-guide
This guide presents a catalog of security-relevant configuration settings for Red Hat Enterprise Linux 6. It is a rendering of content structured in the eXtensible Configuration Checklist Description Format (XCCDF) in order to support security automation. The SCAP content is is available in the config_item="scap-security-guide" package which is developed at https://www.open-scap.org/security-policies/scap-security-guide.
Providing system administrators with such guidance informs them how to securely configure systems under their control in a variety of network roles. Policy makers and baseline creators can use this catalog of settings, with its associated references to higher-level security control catalogs, in order to assist them in security baseline creation. This guide is a italics="catalog, not a checklist," and satisfaction of every item is not likely to be possible or sensible in many operational scenarios. However, the XCCDF format enables granular selection and adjustment of settings, and their association with OVAL and OCIL content provides an automated checking capability. Transformations of this document, and its associated automated checking content, are capable of providing baselines that meet a diverse set of policy objectives. Some example XCCDF italics="Profiles", which are selections of items that form checklists and can be used as baselines, are available with this guide. They can be processed, in an automated fashion, with tools that support the Security Content Automation Protocol (SCAP). The DISA STIG for Red Hat Enterprise Linux 6, which provides required settings for US Department of Defense systems, is one example of a baseline created from this guidance.

This benchmark is a direct port of a SCAP Security Guide benchmark developed for Red Hat Enterprise Linux. It has been modified through an automated process to remove specific dependencies on Red Hat Enterprise Linux and to function with Scientifc Linux. The result is a generally useful SCAP Security Guide benchmark with the following caveats:

- Scientifc Linux is not an exact copy of Red Hat Enterprise Linux. Scientific Linux is a Linux distribution produced by Fermi National Accelerator Laboratory. It is a free and open source operating system based on Red Hat Enterprise Linux and aims to be "as close to the commercial enterprise distribution as we can get it." There may be configuration differences that produce false positives and/or false negatives. If this occurs please file a bug report.
- Scientifc Linux is derived from the free and open source software made available by Red Hat, but it is not produced, maintained or supported by Red Hat. Scientifc Linux has its own build system, compiler options, patchsets, and is a community supported, non-commercial operating system. Scientifc Linux does not inherit certifications or evaluations from Red Hat Enterprise Linux. As such, some configuration rules (such as those requiring FIPS 140-2 encryption) will continue to fail on Scientifc Linux.

Members of the Scientifc Linux community are invited to participate in OpenSCAP and SCAP Security Guide development. Bug reports and patches can be sent to GitHub: https://github.com/OpenSCAP/scap-security-guide. The mailing list is at https://fedorahosted.org/mailman/listinfo/scap-security-guide.

Do not attempt to implement any of the settings in this guide without first testing them in a non-operational environment. The creators of this guidance assume no responsibility whatsoever for its use by other parties, and makes no guarantees, expressed or implied, about its quality, reliability, or any other characteristic.

Profile Title | PCI-DSS v3 Control Baseline for Red Hat Enterprise Linux 6
Profile ID | xccdf_org.ssgproject.content_profile_pci-dss

## Revision History

Current version: 0.1.39

- draft (as of 2018-07-26)

## Platforms

- cpe:/o:redhat:enterprise_linux:6
- cpe:/o:scientificlinux:scientificlinux:6
- cpe:/o:redhat:enterprise_linux:6::client
- cpe:/o:redhat:enterprise_linux:6::computenode

## Table of Contents

1. Services

1. Network Time Protocol
2. SSH Server

2. System Settings

1. Configure Syslog
2. Network Configuration and Firewalls
3. Account and Access Control
4. File Permissions and Masks
5. System Accounting with <tt>auditd</tt>
6. Installing and Maintaining Software

## Checklist

contains 94 rules

### Services [ref]group

The best protection against vulnerable software is running less software. This section describes how to review the software which Red Hat Enterprise Linux 6 installs on a system and disable software which is not needed. It then enumerates the software packages installed on a default Red Hat Enterprise Linux 6 system and provides guidance about which ones can be safely disabled.
Red Hat Enterprise Linux 6 provides a convenient minimal install option that essentially installs the bare necessities for a functional system. When building Red Hat Enterprise Linux 6 systems, it is highly recommended to select the minimal packages and then build up the system from there.

contains 4 rules

### Network Time Protocol [ref]group

The 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.
More information on how to configure the NTP server software, including configuration of cryptographic authentication for time data, is available at http://www.ntp.org.

contains 3 rules

#### Enable the NTP Daemon [ref]rule

The `ntpd` service can be enabled with the following command:

    $ sudo chkconfig --level 2345 ntpd on

Rationale:

Enabling the `ntpd` service ensures that the `ntpd` service will be running and that the system will synchronize its time to any servers specified. This is important whether the system is configured to be a client (and synchronize only its own clock) or it is also acting as an NTP server to other systems. Synchronizing time is essential for authentication services such as Kerberos, but it is also important for maintaining accurate logs and auditing possible security breaches.
The NTP daemon offers all of the functionality of `ntpdate`, which is now deprecated. Additional information on this is available at http://support.ntp.org/bin/view/Dev/DeprecatingNtpdate

Severity: medium

References: CCI-000160, AU-8(1), Req-10.4, SRG-OS-000056, SV-50421r1_rule

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Strategy: | enable

    # Function to enable/disable and start/stop services on RHEL and Fedora systems.
    #
    # Example Call(s):
    #
    #     service_command enable bluetooth
    #     service_command disable bluetooth.service
    #
    #     Using xinetd:
    #     service_command disable rsh.socket xinetd=rsh
    #
    function service_command {

    # Load function arguments into local variables
    local service_state=$1
    local service=$2
    local xinetd=$(echo $3 | cut -d'=' -f2)

    # Check sanity of the input
    if [ $# -lt "2" ]
    then
      echo "Usage: service_command 'enable/disable' 'service_name.service'"
      echo
      echo "To enable or disable xinetd services add \'xinetd=service_name\'"
      echo "as the last argument"
      echo "Aborting."
      exit 1
    fi

    # If systemctl is installed, use systemctl command; otherwise, use the service/chkconfig commands
    if [ -f "/usr/bin/systemctl" ] ; then
      service_util="/usr/bin/systemctl"
    else
      service_util="/sbin/service"
      chkconfig_util="/sbin/chkconfig"
    fi

    # If disable is not specified in arg1, set variables to enable services.
    # Otherwise, variables are to be set to disable services.
    if [ "$service_state" != 'disable' ] ; then
      service_state="enable"
      service_operation="start"
      chkconfig_state="on"
    else
      service_state="disable"
      service_operation="stop"
      chkconfig_state="off"
    fi

    # If chkconfig_util is not empty, use chkconfig/service commands.
    if [ "x$chkconfig_util" != x ] ; then
      $service_util $service $service_operation
      $chkconfig_util --level 0123456 $service $chkconfig_state
    else
      $service_util $service_operation $service
      $service_util $service_state $service
      # 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.
      $service_util reset-failed $service
    fi

    # Test if local variable xinetd is empty using non-bashism.
    # If empty, then xinetd is not being used.
    if [ "x$xinetd" != x ] ; then
      grep -qi disable /etc/xinetd.d/$xinetd && \

      if [ "$service_operation" = 'disable' ] ; then
        sed -i "s/disable.*/disable         = no/gI" /etc/xinetd.d/$xinetd
      else
        sed -i "s/disable.*/disable         = yes/gI" /etc/xinetd.d/$xinetd
      fi
    fi

    }

    service_command enable ntpd

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | enable

    - name: Enable service ntpd
      service:
        name="{{item}}"
        enabled="yes"
        state="started"
      with_items:
        - ntpd
      tags:
        - service_ntpd_enabled
        - medium_severity
        - enable_strategy
        - low_complexity
        - low_disruption
        - CCE-27093-4
        - NIST-800-53-AU-8(1)
        - PCI-DSS-Req-10.4
        - DISA-STIG-RHEL-06-000247

#### Specify Additional Remote NTP Servers [ref]rule

Additional NTP servers can be specified for time synchronization in the file `/etc/ntp.conf`. To do so, add additional lines of the following form, substituting the IP address or hostname of a remote NTP server for ntpserver:

    server ntpserver

Rationale:

Specifying additional NTP servers increases the availability of accurate time data, in the event that one of the specified servers becomes unavailable. This is typical for a system acting as an NTP server for other systems.

Severity: unknown

References: AU-8(1), Req-10.4.3

#### Specify a Remote NTP Server [ref]rule

To specify a remote NTP server for time synchronization, edit the file `/etc/ntp.conf`. Add or correct the following lines, substituting the IP or hostname of a remote NTP server for ntpserver:

    server ntpserver

This instructs the NTP software to contact that remote server to obtain time data.Rationale:

Synchronizing with an NTP server makes it possible to collate system logs from multiple sources or correlate computer events with real time events.

Severity: medium

References: CCI-000160, AU-8(1), Req-10.4.1, Req-10.4.3, SRG-OS-000056, SV-50422r1_rule

### SSH Server [ref]group

The 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, http://www.openssh.org. Its server program is called `sshd` and provided by the RPM package `openssh-server`.

contains 1 rule

### Configure OpenSSH Server if Necessary [ref]group

If 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 1 rule

#### Set SSH Idle Timeout Interval [ref]rule

SSH allows administrators to set an idle timeout interval. After this interval has passed, the idle user will be automatically logged out.
To set an idle timeout interval, edit the following line in `/etc/ssh/sshd_config` as follows:

    ClientAliveInterval 900

The timeout interval is given in seconds. To have a timeout of 15 minutes, set interval to 900.
If a shorter timeout has already been set for the login shell, that value will preempt any SSH setting made here. Keep in mind that some processes may stop SSH from correctly detecting that the user is idle.Rationale:

Causing idle users to be automatically logged out guards against compromises one system leading trivially to compromises on another.

Severity: unknown

References: CCI-000879, CCI-001133, AC-2(5), SA-8, Req-8.1.8, SRG-OS-000163, SV-50409r1_rule

Remediation Shell script: (show)

    sshd_idle_timeout_value="900"

    grep -q ^ClientAliveInterval /etc/ssh/sshd_config && \
      sed -i "s/ClientAliveInterval.*/ClientAliveInterval $sshd_idle_timeout_value/g" /etc/ssh/sshd_config
    if ! [ $? -eq 0 ]; then
        echo "ClientAliveInterval $sshd_idle_timeout_value" >> /etc/ssh/sshd_config
    fi

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | restrict

    - name: XCCDF Value sshd_idle_timeout_value # promote to variable
      set_fact:
        sshd_idle_timeout_value: 900
      tags:
        - always

    - name: Set SSH Idle Timeout Interval
      lineinfile:
        create: yes
        dest: /etc/ssh/sshd_config
        regexp: ^ClientAliveInterval
        line: "ClientAliveInterval {{ sshd_idle_timeout_value }}"
        validate: sshd -t -f %s
      #notify: restart sshd
      tags:
        - sshd_set_idle_timeout
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-26919-1
        - NIST-800-53-AC-2(5)
        - NIST-800-53-SA-8
        - PCI-DSS-Req-8.1.8
        - DISA-STIG-RHEL-06-000230

### System Settings [ref]group

Contains rules that check correct system settings.

contains 90 rules

### Configure Syslog [ref]group

The 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 Red Hat Enterprise Linux 6, 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 4 rules

### Ensure Proper Configuration of Log Files [ref]group

The 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 Red Hat Enterprise Linux 6 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 3 rules

#### Ensure System Log Files Have Correct Permissions [ref]rule

The file permissions for all log files written by `rsyslog` should be set to 600, or more restrictive. These log files are determined by the second part of each Rule line in `/etc/rsyslog.conf` and typically all appear in `/var/log`. For each log file LOGFILE referenced in `/etc/rsyslog.conf`, run the following command to inspect the file's permissions:

    $ ls -l LOGFILE

If the permissions are not 600 or more restrictive, run the following command to correct this:

    $ sudo chmod 0600 LOGFILE

Rationale:

Log files can contain valuable information regarding system configuration. If the system log files are not protected unauthorized users could change the logged data, eliminating their forensic value.

Severity: medium

References: CCI-001314, SI-11, Req-10.5.1, Req-10.5.2, SRG-OS-000206, SV-50424r2_rule

Remediation Shell script: (show)

    # List of log file paths to be inspected for correct permissions
    # * Primarily inspect log file paths listed in /etc/rsyslog.conf
    RSYSLOG_ETC_CONFIG="/etc/rsyslog.conf"
    # * And also the log file paths listed after rsyslog's $IncludeConfig directive
    #   (store the result into array for the case there's shell glob used as value of IncludeConfig)
    RSYSLOG_INCLUDE_CONFIG=($(grep -e "\$IncludeConfig[[:space:]]\+[^[:space:];]\+" /etc/rsyslog.conf | cut -d ' ' -f 2))
    # Declare an array to hold the final list of different log file paths
    declare -a LOG_FILE_PATHS

    # Browse each file selected above as containing paths of log files
    # ('/etc/rsyslog.conf' and '/etc/rsyslog.d/*.conf' in the default configuration)
    for LOG_FILE in "${RSYSLOG_ETC_CONFIG}" "${RSYSLOG_INCLUDE_CONFIG[@]}"
    do
    	# From each of these files extract just particular log file path(s), thus:
    	# * Ignore lines starting with space (' '), comment ('#"), or variable syntax ('$') characters,
    	# * Ignore empty lines,
    	# * From the remaining valid rows select only fields constituting a log file path
    	# Text file column is understood to represent a log file path if and only if all of the following are met:
    	# * it contains at least one slash '/' character,
    	# * it doesn't contain space (' '), colon (':'), and semicolon (';') characters
    	# Search log file for path(s) only in case it exists!
    	if [[ -f "${LOG_FILE}" ]]
    	then
    		MATCHED_ITEMS=$(sed -e "/^[[:space:]|#|$]/d ; s/[^\/]*[[:space:]]*\([^:;[:space:]]*\)/\1/g ; /^$/d" "${LOG_FILE}")
    		# Since above sed command might return more than one item (delimited by newline), split the particular
    		# matches entries into new array specific for this log file
    		readarray -t ARRAY_FOR_LOG_FILE <<< "$MATCHED_ITEMS"
    		# Concatenate the two arrays - previous content of $LOG_FILE_PATHS array with
    		# items from newly created array for this log file
    		LOG_FILE_PATHS=("${LOG_FILE_PATHS[@]}" "${ARRAY_FOR_LOG_FILE[@]}")
    		# Delete the temporary array
    		unset ARRAY_FOR_LOG_FILE
    	fi
    done

    for PATH in "${LOG_FILE_PATHS[@]}"
    do
    	# Sanity check - if particular $PATH is empty string, skip it from further processing
    	if [ -z "$PATH" ]
    	then
    		continue
    	fi
    	# Per https://access.redhat.com/solutions/66805 '/var/log/boot.log' log file needs special care => perform it
    	if [ "$PATH" == "/var/log/boot.log" ]
    	then
    		# Ensure permissions of /var/log/boot.log are configured to be updated in /etc/rc.local
    		if ! /bin/grep -q "boot.log" "/etc/rc.local"
    		then
    			echo "/bin/chmod 600 /var/log/boot.log" >> /etc/rc.local
    		fi
    		# Ensure /etc/rc.d/rc.local has user-executable permission
    		# (in order to be actually executed during boot)
    		if [ "$(/usr/bin/stat -c %a /etc/rc.d/rc.local)" -ne 744 ]
    		then
    			/bin/chmod u+x /etc/rc.d/rc.local
    		fi
    	fi
    	# Also for each log file check if its permissions differ from 600. If so, correct them
    	if [ "$(/usr/bin/stat -c %a "$PATH")" -ne 600 ]
    	then
    		/bin/chmod 600 "$PATH"
    	fi
    done

#### Ensure Log Files Are Owned By Appropriate User [ref]rule

The owner of all log files written by `rsyslog` should be root. These log files are determined by the second part of each Rule line in `/etc/rsyslog.conf` and typically all appear in `/var/log`. For each log file LOGFILE referenced in `/etc/rsyslog.conf`, run the following command to inspect the file's owner:

    $ ls -l LOGFILE

If the owner is not `root`, run the following command to correct this:

    $ sudo chown root LOGFILE

Rationale:

The log files generated by rsyslog contain valuable information regarding system configuration, user authentication, and other such information. Log files should be protected from unauthorized access.

Severity: medium

References: CCI-001314, AC-6, SI-11, Req-10.5.1, Req-10.5.2, SRG-OS-000206, SV-50319r2_rule

#### Ensure Log Files Are Owned By Appropriate Group [ref]rule

The group-owner of all log files written by `rsyslog` should be root. These log files are determined by the second part of each Rule line in `/etc/rsyslog.conf` and typically all appear in `/var/log`. For each log file LOGFILE referenced in `/etc/rsyslog.conf`, run the following command to inspect the file's group owner:

    $ ls -l LOGFILE

If the owner is not `root`, run the following command to correct this:

    $ sudo chgrp root LOGFILE

Rationale:

The log files generated by rsyslog contain valuable information regarding system configuration, user authentication, and other such information. Log files should be protected from unauthorized access.

Severity: medium

References: CCI-001314, AC-6, SI-11, Req-10.5.1, Req-10.5.2, SRG-OS-000206, SV-50320r2_rule

### Ensure All Logs are Rotated by <tt>logrotate</tt> [ref]group

Edit the file `/etc/logrotate.d/syslog`. Find the first line, which should look like this (wrapped for clarity):

    /var/log/messages /var/log/secure /var/log/maillog /var/log/spooler \
      /var/log/boot.log /var/log/cron {

Edit this line so that it contains a one-space-separated listing of each log file referenced in `/etc/rsyslog.conf`.
All logs in use on a system must be rotated regularly, or the log files will consume disk space over time, eventually interfering with system operation. The file `/etc/logrotate.d/syslog` is the configuration file used by the `logrotate` program to maintain all log files written by `syslog`. By default, it rotates logs weekly and stores four archival copies of each log. These settings can be modified by editing `/etc/logrotate.conf`, but the defaults are sufficient for purposes of this guide.
Note that `logrotate` is run nightly by the cron job `/etc/cron.daily/logrotate`. If particularly active logs need to be rotated more often than once a day, some other mechanism must be used.

contains 1 rule

#### Ensure Logrotate Runs Periodically [ref]rule

The `logrotate` utility allows for the automatic rotation of log files. The frequency of rotation is specified in `/etc/logrotate.conf`, which triggers a cron task. To configure logrotate to run daily, add or correct the following line in `/etc/logrotate.conf`:

    # rotate log files frequency
    daily

Rationale:

Log files that are not properly rotated run the risk of growing so large that they fill up the /var/log partition. Valuable logging information could be lost if the /var/log partition becomes full.

Severity: unknown

References: CCI-000366, AU-9, Req-10.7, SRG-OS-999999, SV-50425r1_rule

Remediation Shell script: (show)

    LOGROTATE_CONF_FILE="/etc/logrotate.conf"
    CRON_DAILY_LOGROTATE_FILE="/etc/cron.daily/logrotate"

    # daily rotation is configured
    grep -q "^daily$" $LOGROTATE_CONF_FILE|| echo "daily" >> $LOGROTATE_CONF_FILE

    # remove any line configuring weekly, monthly or yearly rotation
    sed -i -r "/^(weekly|monthly|yearly)$/d" $LOGROTATE_CONF_FILE

    # configure cron.daily if not already
    if ! grep -q "^[[:space:]]*/usr/sbin/logrotate[[:alnum:][:blank:][:punct:]]*$LOGROTATE_CONF_FILE$" $CRON_DAILY_LOGROTATE_FILE; then
    	echo "#!/bin/sh" > $CRON_DAILY_LOGROTATE_FILE
    	echo "/usr/sbin/logrotate $LOGROTATE_CONF_FILE" >> $CRON_DAILY_LOGROTATE_FILE
    fi

### Network Configuration and Firewalls [ref]group

Most machines 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 1 rule

### IPSec Support [ref]group

Support for Internet Protocol Security (IPsec) is provided in Red Hat Enterprise Linux 6 with openswan and libreswan packages respectively.

contains 1 rule

#### Install openswan or libreswan Package [ref]rule

The openswan and libreswan packages provide an implementation of IPsec and IKE, which permits the creation of secure tunnels over untrusted networks. The `openswan` package can be installed with the following command:

    $ sudo yum install openswan

The `libreswan` package can be installed with the following command:

    $ sudo yum install libreswan

Rationale:

Providing the ability for remote users or systems to initiate a secure VPN connection protects information when it is transmitted over a wide area network.

Severity: unknown

References: CCI-001130, CCI-001131, AC-17, MA-4, SC-8, Req-4.1, SRG-OS-000160, SV-50488r3_rule

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Strategy: | enable

    # Function to install packages on RHEL, Fedora, Debian, and possibly other systems.
    #
    # Example Call(s):
    #
    #     package_install aide
    #
    function package_install {

    # Load function arguments into local variables
    local package="$1"

    # Check sanity of the input
    if [ $# -ne "1" ]
    then
      echo "Usage: package_install 'package_name'"
      echo "Aborting."
      exit 1
    fi

    if which dnf ; then
      if ! rpm -q --quiet "$package"; then
        dnf install -y "$package"
      fi
    elif which yum ; then
      if ! rpm -q --quiet "$package"; then
        yum install -y "$package"
      fi
    elif which apt-get ; then
      apt-get install -y "$package"
    else
      echo "Failed to detect available packaging system, tried dnf, yum and apt-get!"
      echo "Aborting."
      exit 1
    fi

    }

    package_install openswan

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | enable

    - name: Ensure openswan is installed
      package:
        name="{{item}}"
        state=present
      with_items:
        - openswan
      tags:
        - package_openswan_installed
        - unknown_severity
        - enable_strategy
        - low_complexity
        - low_disruption
        - CCE-27626-1
        - NIST-800-53-AC-17
        - NIST-800-53-MA-4
        - NIST-800-53-SC-8
        - PCI-DSS-Req-4.1
        - DISA-STIG-RHEL-06-000321

Remediation Puppet snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | enable

    include install_openswan

    class install_openswan {
      package { 'openswan':
        ensure => 'installed',
      }
    }

Remediation Anaconda snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | enable

    package --add=openswan

### Account and Access Control [ref]group

In traditional Unix security, if an attacker gains shell access to a certain login account, they can perform any action or access any file to which that account has access. Therefore, making it more difficult for unauthorized people to gain shell access to accounts, particularly to privileged accounts, is a necessary part of securing a system. This section introduces mechanisms for restricting access to accounts under Red Hat Enterprise Linux 6.

contains 20 rules

### Protect Accounts by Restricting Password-Based Login [ref]group

Conventionally, Unix shell accounts are accessed by providing a username and password to a login program, which tests these values for correctness using the `/etc/passwd` and `/etc/shadow` files. Password-based login is vulnerable to guessing of weak passwords, and to sniffing and man-in-the-middle attacks against passwords entered over a network or at an insecure console. Therefore, mechanisms for accessing accounts by entering usernames and passwords should be restricted to those which are operationally necessary.

contains 6 rules

### Verify Proper Storage and Existence of Password Hashes [ref]group

By 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 3 rules

#### Prevent Log In to Accounts With Empty Password [ref]rule

If an account is configured for password authentication but does not have an assigned password, it may be possible to log onto the account without authentication. Remove any instances of the `nullok` option in `/etc/pam.d/system-auth` to prevent logins with empty passwords.

Rationale:

If an account has an empty password, anyone could log in and run commands with the privileges of that account. Accounts with empty passwords should never be used in operational environments.

Severity: high

References: IA-5(b), IA-5(c), IA-5(1)(a), Req-8.2.3, SRG-OS-999999, SV-50298r2_rule

Remediation Shell script: (show)

    sed --follow-symlinks -i 's/\<nullok\>//g' /etc/pam.d/system-auth
    sed --follow-symlinks -i 's/\<nullok\>//g' /etc/pam.d/password-auth

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | medium
Strategy: | configure

    - name: "Prevent Log In to Accounts With Empty Password - system-auth"
      replace:
        dest: /etc/pam.d/system-auth
        follow: yes
        regexp: 'nullok'
      tags:
        - no_empty_passwords
        - high_severity
        - configure_strategy
        - low_complexity
        - medium_disruption
        - CCE-27038-9
        - NIST-800-53-IA-5(b)
        - NIST-800-53-IA-5(c)
        - NIST-800-53-IA-5(1)(a)
        - PCI-DSS-Req-8.2.3
        - DISA-STIG-RHEL-06-000030

    - name: "Prevent Log In to Accounts With Empty Password - password-auth"
      replace:
        dest: /etc/pam.d/password-auth
        follow: yes
        regexp: 'nullok'
      tags:
        - no_empty_passwords
        - high_severity
        - configure_strategy
        - low_complexity
        - medium_disruption
        - CCE-27038-9
        - NIST-800-53-IA-5(b)
        - NIST-800-53-IA-5(c)
        - NIST-800-53-IA-5(1)(a)
        - PCI-DSS-Req-8.2.3
        - DISA-STIG-RHEL-06-000030

#### Verify All Account Password Hashes are Shadowed [ref]rule

If any password hashes are stored in `/etc/passwd` (in the second field, instead of an `x`), the cause of this misconfiguration should be investigated. The account should have its password reset and the hash should be properly stored, or the account should be deleted entirely.

Rationale:

The hashes for all user account passwords should be stored in the file `/etc/shadow` and never in `/etc/passwd`, which is readable by all users.

Severity: medium

References: CCI-000201, IA-5(h), Req-8.2.1, SRG-OS-999999, SV-50300r1_rule

#### All GIDs referenced in /etc/passwd must be defined in /etc/group [ref]rule

Add a group to the system for each GID referenced without a corresponding group.

Rationale:

Inconsistency in GIDs between `/etc/passwd` and `/etc/group` could lead to a user having unintended rights.

Severity: unknown

References: CCI-000366, Req-8.5.a, SRG-OS-999999, SV-50482r2_rule

### Set Password Expiration Parameters [ref]group

The file `/etc/login.defs` controls several password-related settings. Programs such as `passwd`, `su`, and `login` consult `/etc/login.defs` to determine behavior with regard to password aging, expiration warnings, and length. See the man page `login.defs(5)` for more information.
Users should be forced to change their passwords, in order to decrease the utility of compromised passwords. However, the need to change passwords often should be balanced against the risk that users will reuse or write down passwords if forced to change them too often. Forcing password changes every 90-360 days, depending on the environment, is recommended. Set the appropriate value as `PASS_MAX_DAYS` and apply it to existing accounts with the `-M` flag.
The `PASS_MIN_DAYS` (`-m`) setting prevents password changes for 7 days after the first change, to discourage password cycling. If you use this setting, train users to contact an administrator for an emergency password change in case a new password becomes compromised. The `PASS_WARN_AGE` (`-W`) setting gives users 7 days of warnings at login time that their passwords are about to expire.
For example, for each existing human user USER, expiration parameters could be adjusted to a 180 day maximum password age, 7 day minimum password age, and 7 day warning period with the following command:

    $ sudo chage -M 180 -m 7 -W 7 USER

contains 1 rule

#### Set Password Maximum Age [ref]rule

To specify password maximum age for new accounts, edit the file `/etc/login.defs` and add or correct the following line:

    PASS_MAX_DAYS 90

A value of 180 days is sufficient for many environments. The DoD requirement is 60.Rationale:

Setting the password maximum age ensures users are required to periodically change their passwords. This could possibly decrease the utility of a stolen password. Requiring shorter password lifetimes increases the risk of users writing down the password in a convenient location subject to physical compromise.

Severity: medium

References: CCI-000180, CCI-000199, IA-5(f), IA-5(g), IA-5(1)(d), Req-8.2.4, SRG-OS-000076, SV-50279r1_rule

Remediation Shell script: (show)

    var_accounts_maximum_age_login_defs="90"

    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

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | restrict

    - name: XCCDF Value var_accounts_maximum_age_login_defs # promote to variable
      set_fact:
        var_accounts_maximum_age_login_defs: 90
      tags:
        - always

    - name: Set Password Maximum Age
      lineinfile:
        create: yes
        dest: /etc/login.defs
        regexp: ^#?PASS_MAX_DAYS
        line: "PASS_MAX_DAYS {{ var_accounts_maximum_age_login_defs }}"
      tags:
        - accounts_maximum_age_login_defs
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-26985-2
        - NIST-800-53-IA-5(f)
        - NIST-800-53-IA-5(g)
        - NIST-800-53-IA-5(1)(d)
        - PCI-DSS-Req-8.2.4
        - DISA-STIG-RHEL-06-000053

### Set Account Expiration Parameters [ref]group

Accounts 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.

contains 2 rules

#### Ensure All Accounts on the System Have Unique Names [ref]rule

Change usernames, or delete accounts, so each has a unique name.

Rationale:

Unique usernames allow for accountability on the system.

Severity: unknown

References: CCI-000770, CCI-000804, Req-8.1.1, SRG-OS-000121, SV-50484r1_rule

#### Set Account Expiration Following Inactivity [ref]rule

To specify the number of days after a password expires (which signifies inactivity) until an account is permanently disabled, add or correct the following lines in `/etc/default/useradd`, substituting `NUM_DAYS` appropriately:

    INACTIVE=(N/A)

A value of 35 is recommended. If a password is currently on the verge of expiration, then 35 days remain until the account is automatically disabled. However, if the password will not expire for another 60 days, then 95 days could elapse until the account would be automatically disabled. See the `useradd` man page for more information. Determining the inactivity timeout must be done with careful consideration of the length of a "normal" period of inactivity for users in the particular environment. Setting the timeout too low incurs support costs and also has the potential to impact availability of the system to legitimate users.Rationale:

Disabling inactive accounts ensures that accounts which may not have been responsibly removed are not available to attackers who may have compromised their credentials.

Severity: unknown

References: CCI-000016, CCI-000017, CCI-000795, AC-2(2), AC-2(3), Req-8.1.4, SRG-OS-000118, SV-50493r1_rule

Remediation Shell script: (show)

    var_account_disable_post_pw_expiration="90"

    grep -q ^INACTIVE /etc/default/useradd && \
      sed -i "s/INACTIVE.*/INACTIVE=$var_account_disable_post_pw_expiration/g" /etc/default/useradd
    if ! [ $? -eq 0 ]; then
        echo "INACTIVE=$var_account_disable_post_pw_expiration" >> /etc/default/useradd
    fi

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | restrict

    - name: XCCDF Value var_account_disable_post_pw_expiration # promote to variable
      set_fact:
        var_account_disable_post_pw_expiration: 90
      tags:
        - always

    - name: Set Account Expiration Following Inactivity
      lineinfile:
        create: yes
        dest: /etc/default/useradd
        regexp: ^INACTIVE
        line: "INACTIVE={{ var_account_disable_post_pw_expiration }}"
      tags:
        - account_disable_post_pw_expiration
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27283-1
        - NIST-800-53-AC-2(2)
        - NIST-800-53-AC-2(3)
        - PCI-DSS-Req-8.1.4
        - DISA-STIG-RHEL-06-000334

### Protect Physical Console Access [ref]group

It 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 3 rules

### Configure Screen Locking [ref]group

When 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 1 rule

### Hardware Tokens for Authentication [ref]group

The 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 1 rule

#### Enable Smart Card Login [ref]rule

To enable smart card authentication, consult the documentation at:

- https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Managing_Smart_Cards/enabling-smart-card-login.html

For guidance on enabling SSH to authenticate against a Common Access Card (CAC), consult documentation at:

- https://access.redhat.com/solutions/82273

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.

Severity: medium

References: CCI-000765, CCI-000766, CCI-000767, CCI-000768, CCI-000771, CCI-000772, CCI-000884, Req-8.3, SRG-OS-000105, SV-50396r3_rule

Remediation Shell script: (show)

    # Install required packages
    yum -y install esc
    yum -y install pam_pkcs11

    # Enable pcscd service
    # Function to enable/disable and start/stop services on RHEL and Fedora systems.
    #
    # Example Call(s):
    #
    #     service_command enable bluetooth
    #     service_command disable bluetooth.service
    #
    #     Using xinetd:
    #     service_command disable rsh.socket xinetd=rsh
    #
    function service_command {

    # Load function arguments into local variables
    local service_state=$1
    local service=$2
    local xinetd=$(echo $3 | cut -d'=' -f2)

    # Check sanity of the input
    if [ $# -lt "2" ]
    then
      echo "Usage: service_command 'enable/disable' 'service_name.service'"
      echo
      echo "To enable or disable xinetd services add \'xinetd=service_name\'"
      echo "as the last argument"
      echo "Aborting."
      exit 1
    fi

    # If systemctl is installed, use systemctl command; otherwise, use the service/chkconfig commands
    if [ -f "/usr/bin/systemctl" ] ; then
      service_util="/usr/bin/systemctl"
    else
      service_util="/sbin/service"
      chkconfig_util="/sbin/chkconfig"
    fi

    # If disable is not specified in arg1, set variables to enable services.
    # Otherwise, variables are to be set to disable services.
    if [ "$service_state" != 'disable' ] ; then
      service_state="enable"
      service_operation="start"
      chkconfig_state="on"
    else
      service_state="disable"
      service_operation="stop"
      chkconfig_state="off"
    fi

    # If chkconfig_util is not empty, use chkconfig/service commands.
    if [ "x$chkconfig_util" != x ] ; then
      $service_util $service $service_operation
      $chkconfig_util --level 0123456 $service $chkconfig_state
    else
      $service_util $service_operation $service
      $service_util $service_state $service
      # 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.
      $service_util reset-failed $service
    fi

    # Test if local variable xinetd is empty using non-bashism.
    # If empty, then xinetd is not being used.
    if [ "x$xinetd" != x ] ; then
      grep -qi disable /etc/xinetd.d/$xinetd && \

      if [ "$service_operation" = 'disable' ] ; then
        sed -i "s/disable.*/disable         = no/gI" /etc/xinetd.d/$xinetd
      else
        sed -i "s/disable.*/disable         = yes/gI" /etc/xinetd.d/$xinetd
      fi
    fi

    }

    service_command enable pcscd

    # Configure the expected /etc/pam.d/system-auth{,-ac} settings directly
    #
    # The code below will configure system authentication in the way smart card
    # logins will be enabled, but also user login(s) via other method to be allowed
    #
    # NOTE: In contrast to Red Hat Enterprise Linux 7 version of this remediation
    #       script (based on the testing) it does NOT seem to be possible to use
    #       the 'authconfig' command to perform the remediation for us. Because:
    #
    #       * calling '/usr/sbin/authconfig --enablesmartcard --update'
    #	  does not update all the necessary files, while
    #
    #	* calling '/usr/sbin/authconfig --enablesmartcard --updateall'
    #	  discards the necessary changes on /etc/pam_pkcs11/pam_pkcs11.conf
    #	  performed subsequently below
    #
    #	Therefore we configure /etc/pam.d/system-auth{,-ac} settings directly.
    #

    # Define system-auth config location
    SYSTEM_AUTH_CONF="/etc/pam.d/system-auth"
    # Define expected 'pam_env.so' row in $SYSTEM_AUTH_CONF
    PAM_ENV_SO="auth.*required.*pam_env.so"

    # Define 'pam_succeed_if.so' row to be appended past $PAM_ENV_SO row into $SYSTEM_AUTH_CONF
    SYSTEM_AUTH_PAM_SUCCEED="\
    auth        \[success=1 default=ignore\] pam_succeed_if.so service notin \
    login:gdm:xdm:kdm:xscreensaver:gnome-screensaver:kscreensaver quiet use_uid"
    # Define 'pam_pkcs11.so' row to be appended past $SYSTEM_AUTH_PAM_SUCCEED
    # row into SYSTEM_AUTH_CONF file
    SYSTEM_AUTH_PAM_PKCS11="\
    auth        \[success=done authinfo_unavail=ignore ignore=ignore default=die\] \
    pam_pkcs11.so card_only"

    # Define smartcard-auth config location
    SMARTCARD_AUTH_CONF="/etc/pam.d/smartcard-auth"
    # Define 'pam_pkcs11.so' auth section to be appended past $PAM_ENV_SO into $SMARTCARD_AUTH_CONF
    SMARTCARD_AUTH_SECTION="\
    auth        [success=done ignore=ignore default=die] pam_pkcs11.so wait_for_card card_only"
    # Define expected 'pam_permit.so' row in $SMARTCARD_AUTH_CONF
    PAM_PERMIT_SO="account.*required.*pam_permit.so"
    # Define 'pam_pkcs11.so' password section
    SMARTCARD_PASSWORD_SECTION="\
    password    required      pam_pkcs11.so"

    # First Correct the SYSTEM_AUTH_CONF configuration
    if ! grep -q 'pam_pkcs11.so' "$SYSTEM_AUTH_CONF"
    then
    	# Append (expected) pam_succeed_if.so row past the pam_env.so into SYSTEM_AUTH_CONF file
    	sed -i --follow-symlinks -e '/^'"$PAM_ENV_SO"'/a '"$SYSTEM_AUTH_PAM_SUCCEED" "$SYSTEM_AUTH_CONF"
    	# Append (expected) pam_pkcs11.so row past the pam_succeed_if.so into SYSTEM_AUTH_CONF file
    	sed -i --follow-symlinks -e '/^'"$SYSTEM_AUTH_PAM_SUCCEED"'/a '"$SYSTEM_AUTH_PAM_PKCS11" "$SYSTEM_AUTH_CONF"
    fi

    # Then also correct the SMARTCARD_AUTH_CONF
    if ! grep -q 'pam_pkcs11.so' "$SMARTCARD_AUTH_CONF"
    then
    	# Append (expected) SMARTCARD_AUTH_SECTION row past the pam_env.so into SMARTCARD_AUTH_CONF file
    	sed -i --follow-symlinks -e '/^'"$PAM_ENV_SO"'/a '"$SMARTCARD_AUTH_SECTION" "$SMARTCARD_AUTH_CONF"
    	# Append (expected) SMARTCARD_PASSWORD_SECTION row past the pam_permit.so into SMARTCARD_AUTH_CONF file
    	sed -i --follow-symlinks -e '/^'"$PAM_PERMIT_SO"'/a '"$SMARTCARD_PASSWORD_SECTION" "$SMARTCARD_AUTH_CONF"
    fi

    # Perform /etc/pam_pkcs11/pam_pkcs11.conf settings below
    # Define selected constants for later reuse
    SP="[:space:]"
    PAM_PKCS11_CONF="/etc/pam_pkcs11/pam_pkcs11.conf"

    # Ensure OCSP is turned on in $PAM_PKCS11_CONF
    # 1) First replace any occurrence of 'none' value of 'cert_policy' key setting with the correct configuration
    # On Red Hat Enterprise Linux 6 a space isn't required between 'cert_policy' key and value assignment !!!
    sed -i "s/^[$SP]*cert_policy=none;/    cert_policy=ca, ocsp_on, signature;/g" "$PAM_PKCS11_CONF"

    # 2) Then append 'ocsp_on' value setting to each 'cert_policy' key in $PAM_PKCS11_CONF configuration line,
    # which does not contain it yet
    # On Red Hat Enterprise Linux 6 a space isn't required between 'cert_policy' key and value assignment !!!
    sed -i "/ocsp_on/! s/^[$SP]*cert_policy=\(.*\);/    cert_policy=\1, ocsp_on;/" "$PAM_PKCS11_CONF"

Remediation Anaconda snippet: (show)

    package --add=pam_pkcs11 --add=esc

### Set Boot Loader Password [ref]group

During 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 Red Hat Enterprise Linux boot loader for x86 systems is called GRUB. 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

#### Verify /etc/grub.conf Group Ownership [ref]rule

The file `/etc/grub.conf` should be group-owned by the `root` group to prevent destruction or modification of the file. To properly set the group owner of `/etc/grub.conf`, run the command:

    $ sudo chgrp root /etc/grub.conf

Rationale:

The `root` group is a highly-privileged group. Furthermore, the group-owner of this file should not have any access privileges anyway.

Severity: medium

References: CCI-000225, AC-6(7), Req-7.1, SRG-OS-999999, SV-50382r2_rule

Remediation Shell script: (show)

    chgrp root /etc/grub.conf

#### Verify /etc/grub.conf User Ownership [ref]rule

The file `/etc/grub.conf` should be owned by the `root` user to prevent destruction or modification of the file. To properly set the owner of `/etc/grub.conf`, run the command:

    $ sudo chown root /etc/grub.conf

Rationale:

Only root should be able to modify important boot parameters.

Severity: medium

References: CCI-000225, AC-6(7), Req-7.1, SRG-OS-999999, SV-50380r2_rule

Remediation Shell script: (show)

    chown root /etc/grub.conf

### Protect Accounts by Configuring PAM [ref]group

PAM, or Pluggable Authentication Modules, is a system which implements modular authentication for Linux programs. PAM provides a flexible and configurable architecture for authentication, and it should be configured to minimize exposure to unnecessary risk. This section contains guidance on how to accomplish that.
PAM is implemented as a set of shared objects which are loaded and invoked whenever an application wishes to authenticate a user. Typically, the application must be running as root in order to take advantage of PAM, because PAM's modules often need to be able to access sensitive stores of account information, such as /etc/shadow. Traditional privileged network listeners (e.g. sshd) or SUID programs (e.g. sudo) already meet this requirement. An SUID root application, userhelper, is provided so that programs which are not SUID or privileged themselves can still take advantage of PAM.
PAM looks in the directory `/etc/pam.d` for application-specific configuration information. For instance, if the program login attempts to authenticate a user, then PAM's libraries follow the instructions in the file `/etc/pam.d/login` to determine what actions should be taken.
One very important file in `/etc/pam.d` is `/etc/pam.d/system-auth`. This file, which is included by many other PAM configuration files, defines 'default' system authentication measures. Modifying this file is a good way to make far-reaching authentication changes, for instance when implementing a centralized authentication service.

contains 11 rules

### Set Password Hashing Algorithm [ref]group

The system's default algorithm for storing password hashes in `/etc/shadow` is SHA-512. This can be configured in several locations.

contains 3 rules

#### Set Password Hashing Algorithm in /etc/login.defs [ref]rule

In `/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:

Using a stronger hashing algorithm makes password cracking attacks more difficult.

Severity: medium

References: CCI-000803, IA-5(b), IA-5(c), IA-5(1)(c), IA-7, Req-8.2.1, SRG-OS-000120, SV-50377r1_rule

Remediation Shell script: (show)

    if grep --silent ^ENCRYPT_METHOD /etc/login.defs ; then
    	sed -i 's/^ENCRYPT_METHOD.*/ENCRYPT_METHOD SHA512/g' /etc/login.defs
    else
    	echo "" >> /etc/login.defs
    	echo "ENCRYPT_METHOD SHA512" >> /etc/login.defs
    fi

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | restrict

    - name: Set Password Hashing Algorithm in /etc/login.defs
      lineinfile:
          dest: /etc/login.defs
          regexp: ^#?ENCRYPT_METHOD
          line: ENCRYPT_METHOD SHA512
          state: present
      tags:
        - set_password_hashing_algorithm_logindefs
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27228-6
        - NIST-800-53-IA-5(b)
        - NIST-800-53-IA-5(c)
        - NIST-800-53-IA-5(1)(c)
        - NIST-800-53-IA-7
        - PCI-DSS-Req-8.2.1
        - DISA-STIG-RHEL-06-000063

#### Set Password Hashing Algorithm in /etc/pam.d/system-auth [ref]rule

In `/etc/pam.d/system-auth`, the `password` section of the file controls which PAM modules execute during a password change. Set the `pam_unix.so` module in the `password` section to include the argument `sha512`, as shown below:

    password    sufficient    pam_unix.so sha512 other arguments...

This will help ensure when local users change their passwords, hashes for the new passwords will be generated using the SHA-512 algorithm. This is the default.Rationale:

Using a stronger hashing algorithm makes password cracking attacks more difficult.

Severity: medium

References: CCI-000803, IA-5(b), IA-5(c), IA-5(1)(c), IA-7, Req-8.2.1, SRG-OS-000120, SV-50375r3_rule

Remediation Shell script: (show)

    AUTH_FILES[0]="/etc/pam.d/system-auth"
    AUTH_FILES[1]="/etc/pam.d/password-auth"

    for pamFile in "${AUTH_FILES[@]}"
    do
    	if ! grep -q "^password.*sufficient.*pam_unix.so.*sha512" $pamFile; then
    		sed -i --follow-symlinks "/^password.*sufficient.*pam_unix.so/ s/$/ sha512/" $pamFile
    	fi
    done

#### Set Password Hashing Algorithm in /etc/libuser.conf [ref]rule

In `/etc/libuser.conf`, add or correct the following line in its `[defaults]` section to ensure the system will use the SHA-512 algorithm for password hashing:

    crypt_style = sha512

Rationale:

Using a stronger hashing algorithm makes password cracking attacks more difficult.

Severity: medium

References: CCI-000803, IA-5(b), IA-5(c), IA-5(1)(c), IA-7, Req-8.2.1, SRG-OS-000120, SV-50378r1_rule

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | restrict

    - name: Set Password Hashing Algorithm in /etc/libuser.conf
      lineinfile:
        dest: /etc/libuser.conf
        insertafter: "^.default]"
        regexp: ^#?crypt_style
        line: crypt_style = sha512
        state: present
      tags:
        - set_password_hashing_algorithm_libuserconf
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27229-4
        - NIST-800-53-IA-5(b)
        - NIST-800-53-IA-5(c)
        - NIST-800-53-IA-5(1)(c)
        - NIST-800-53-IA-7
        - PCI-DSS-Req-8.2.1
        - DISA-STIG-RHEL-06-000064

### Set Lockouts for Failed Password Attempts [ref]group

The `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`.

contains 3 rules

#### Set Lockout Time For Failed Password Attempts [ref]rule

To configure the system to lock out accounts after a number of incorrect login attempts and require an administrator to unlock the account using `pam_faillock.so`, modify the content of both `/etc/pam.d/system-auth` and `/etc/pam.d/password-auth` as follows:

- Add the following line immediately `before` the `pam_unix.so` statement in the `AUTH` section:

      auth required pam_faillock.so preauth silent deny=6 unlock_time=1800 fail_interval=(N/A)

- Add the following line immediately `after` the `pam_unix.so` statement in the `AUTH` section:

      auth [default=die] pam_faillock.so authfail deny=6 unlock_time=1800 fail_interval=(N/A)

- Add the following line immediately `before` the `pam_unix.so` statement in the `ACCOUNT` section:

      account required pam_faillock.so

Rationale:

Locking out user accounts after a number of incorrect attempts prevents direct password guessing attacks. Ensuring that an administrator is involved in unlocking locked accounts draws appropriate attention to such situations.

Severity: medium

References: Req-8.1.7, CCI-000047, AC-7(b), SRG-OS-000022, SV-50393r4_rule

Remediation Shell script: (show)

    var_accounts_passwords_pam_faillock_unlock_time="1800"

    # Invoke the function without args, so its body is substituded right here.
    function set_faillock_option_to_value_in_pam_file {
    	# If invoked with no arguments, exit. This is an intentional behavior.
    	[ $# -gt 1 ] || return 0
    	[ $# -ge 3 ] || die "$0 requires exactly zero, three, or four arguments"
    	[ $# -le 4 ] || die "$0 requires exactly zero, three, or four arguments"
    	local _pamFile="$1" _option="$2" _value="$3" _insert_lines_callback="$4"
    	# pam_faillock.so already present?
    	if grep -q "^auth.*pam_faillock.so.*" "$_pamFile"; then

    		# pam_faillock.so present, is the option present?
    		if grep -q "^auth.*[default=die].*pam_faillock.so.*authfail.*$_option=" "$_pamFile"; then

    			# both pam_faillock.so & option present, just correct option to the right value
    			sed -i --follow-symlinks "s/\(^auth.*required.*pam_faillock.so.*preauth.*silent.*\)\($_option *= *\).*/\1\2$_value/" "$_pamFile"
    			sed -i --follow-symlinks "s/\(^auth.*[default=die].*pam_faillock.so.*authfail.*\)\($_option *= *\).*/\1\2$_value/" "$_pamFile"

    		# pam_faillock.so present, but the option not yet
    		else

    			# append correct option value to appropriate places
    			sed -i --follow-symlinks "/^auth.*required.*pam_faillock.so.*preauth.*silent.*/ s/$/ $_option=$_value/" "$_pamFile"
    			sed -i --follow-symlinks "/^auth.*[default=die].*pam_faillock.so.*authfail.*/ s/$/ $_option=$_value/" "$_pamFile"
    		fi

    	# pam_faillock.so not present yet
    	else
    		test -z "$_insert_lines_callback" || "$_insert_lines_callback" "$_option" "$_value" "$_pamFile"
    		# insert pam_faillock.so preauth & authfail rows with proper value of the option in question
    	fi
    }

    set_faillock_option_to_value_in_pam_file

    AUTH_FILES[0]="/etc/pam.d/system-auth"
    AUTH_FILES[1]="/etc/pam.d/password-auth"

    function insert_lines_if_pam_faillock_so_not_present {
    	local _option="$1" _value="$2" _pamFile="$3"
    	sed -i --follow-symlinks "/^auth.*sufficient.*pam_unix.so.*/i auth        required      pam_faillock.so preauth silent $_option=$_value" "$_pamFile"
    	sed -i --follow-symlinks "/^auth.*sufficient.*pam_unix.so.*/a auth        [default=die] pam_faillock.so authfail $_option=$_value" "$_pamFile"
    	sed -i --follow-symlinks "/^account.*required.*pam_unix.so/i account     required      pam_faillock.so" "$_pamFile"
    }

    for pamFile in "${AUTH_FILES[@]}"
    do
    	# 'true &&' has to be there due to build system limitation
    	true && set_faillock_option_to_value_in_pam_file "$pamFile" unlock_time "$var_accounts_passwords_pam_faillock_unlock_time" insert_lines_if_pam_faillock_so_not_present
    done

#### Limit Password Reuse [ref]rule

Do 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. In the file `/etc/pam.d/system-auth`, append `remember=4` to the line which refers to the `pam_unix.so` or `pam_pwhistory.so`module, as shown below:

- for the `pam_unix.so` case:

      password sufficient pam_unix.so existing_options remember=4

- for the `pam_pwhistory.so` case:

      password requisite pam_pwhistory.so existing_options remember=4

The DoD STIG requirement is 5 passwords.Rationale:

Preventing re-use of previous passwords helps ensure that a compromised password is not re-used by a user.

Severity: medium

References: CCI-000200, IA-5(f), IA-5(1)(e), Req-8.2.5, SRG-OS-000077, SV-50459r5_rule

Remediation Shell script: (show)

    var_password_pam_unix_remember="4"

    AUTH_FILES[0]="/etc/pam.d/system-auth"
    AUTH_FILES[1]="/etc/pam.d/password-auth"

    for pamFile in "${AUTH_FILES[@]}"
    do
    	if grep -q "remember=" $pamFile; then
    		sed -i --follow-symlinks "s/\(^password.*sufficient.*pam_unix.so.*\)\(\(remember *= *\)[^ $]*\)/\1remember=$var_password_pam_unix_remember/" $pamFile
    	else
    		sed -i --follow-symlinks "/^password[[:space:]]\+sufficient[[:space:]]\+pam_unix.so/ s/$/ remember=$var_password_pam_unix_remember/" $pamFile
    	fi
    done

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | medium
Strategy: | configure

    - name: XCCDF Value var_password_pam_unix_remember # promote to variable
      set_fact:
        var_password_pam_unix_remember: 4
      tags:
        - always

    - name: "Do not allow users to reuse recent passwords - system-auth (change)"
      replace:
        dest: /etc/pam.d/system-auth
        follow: yes
        regexp: '^(password\s+sufficient\s+pam_unix\.so\s.*remember\s*=\s*)(\S+)(.*)$'
        replace: '\g<1>{{ var_password_pam_unix_remember }}\g<3>'
      tags:
        - accounts_password_pam_unix_remember
        - medium_severity
        - configure_strategy
        - low_complexity
        - medium_disruption
        - CCE-26741-9
        - NIST-800-53-IA-5(f)
        - NIST-800-53-IA-5(1)(e)
        - PCI-DSS-Req-8.2.5
        - DISA-STIG-RHEL-06-000274

    - name: "Do not allow users to reuse recent passwords - system-auth (add)"
      replace:
        dest: /etc/pam.d/system-auth
        follow: yes
        regexp: '^password\s+sufficient\s+pam_unix\.so\s(?!.*remember\s*=\s*).*$'
        replace: '\g<0> remember={{ var_password_pam_unix_remember }}'
      tags:
        - accounts_password_pam_unix_remember
        - medium_severity
        - configure_strategy
        - low_complexity
        - medium_disruption
        - CCE-26741-9
        - NIST-800-53-IA-5(f)
        - NIST-800-53-IA-5(1)(e)
        - PCI-DSS-Req-8.2.5
        - DISA-STIG-RHEL-06-000274

#### Set Deny For Failed Password Attempts [ref]rule

To configure the system to lock out accounts after a number of incorrect login attempts using `pam_faillock.so`, modify the content of both `/etc/pam.d/system-auth` and `/etc/pam.d/password-auth` as follows:

- Add the following line immediately `before` the `pam_unix.so` statement in the `AUTH` section:

      auth required pam_faillock.so preauth silent deny=6 unlock_time=1800 fail_interval=(N/A)

- Add the following line immediately `after` the `pam_unix.so` statement in the `AUTH` section:

      auth [default=die] pam_faillock.so authfail deny=6 unlock_time=1800 fail_interval=(N/A)

- Add the following line immediately `before` the `pam_unix.so` statement in the `ACCOUNT` section:

      account required pam_faillock.so

Rationale:

Locking out user accounts after a number of incorrect attempts prevents direct password guessing attacks.

Severity: medium

References: CCI-000044, AC-7(a), Req-8.1.6, SRG-OS-000021, SV-50374r4_rule

Remediation Shell script: (show)

    var_accounts_passwords_pam_faillock_deny="6"

    AUTH_FILES[0]="/etc/pam.d/system-auth"
    AUTH_FILES[1]="/etc/pam.d/password-auth"

    # This script fixes absence of pam_faillock.so in PAM stack or the
    # absense of deny=[0-9]+ in pam_faillock.so arguments
    # When inserting auth pam_faillock.so entries,
    # the entry with preauth argument will be added before pam_unix.so module
    # and entry with authfail argument will be added before pam_deny.so module.

    # The placement of pam_faillock.so entries will not be changed
    # if they are already present

    # Invoke the function without args, so its body is substituded right here.
    function set_faillock_option_to_value_in_pam_file {
    	# If invoked with no arguments, exit. This is an intentional behavior.
    	[ $# -gt 1 ] || return 0
    	[ $# -ge 3 ] || die "$0 requires exactly zero, three, or four arguments"
    	[ $# -le 4 ] || die "$0 requires exactly zero, three, or four arguments"
    	local _pamFile="$1" _option="$2" _value="$3" _insert_lines_callback="$4"
    	# pam_faillock.so already present?
    	if grep -q "^auth.*pam_faillock.so.*" "$_pamFile"; then

    		# pam_faillock.so present, is the option present?
    		if grep -q "^auth.*[default=die].*pam_faillock.so.*authfail.*$_option=" "$_pamFile"; then

    			# both pam_faillock.so & option present, just correct option to the right value
    			sed -i --follow-symlinks "s/\(^auth.*required.*pam_faillock.so.*preauth.*silent.*\)\($_option *= *\).*/\1\2$_value/" "$_pamFile"
    			sed -i --follow-symlinks "s/\(^auth.*[default=die].*pam_faillock.so.*authfail.*\)\($_option *= *\).*/\1\2$_value/" "$_pamFile"

    		# pam_faillock.so present, but the option not yet
    		else

    			# append correct option value to appropriate places
    			sed -i --follow-symlinks "/^auth.*required.*pam_faillock.so.*preauth.*silent.*/ s/$/ $_option=$_value/" "$_pamFile"
    			sed -i --follow-symlinks "/^auth.*[default=die].*pam_faillock.so.*authfail.*/ s/$/ $_option=$_value/" "$_pamFile"
    		fi

    	# pam_faillock.so not present yet
    	else
    		test -z "$_insert_lines_callback" || "$_insert_lines_callback" "$_option" "$_value" "$_pamFile"
    		# insert pam_faillock.so preauth & authfail rows with proper value of the option in question
    	fi
    }

    set_faillock_option_to_value_in_pam_file

    function insert_lines_if_pam_faillock_so_not_present {
    	# insert pam_faillock.so preauth row with proper value of the 'deny' option before pam_unix.so
    	sed -i --follow-symlinks "/^auth.*pam_unix.so.*/i auth        required      pam_faillock.so preauth silent $_option=$_value" $_pamFile
    	# insert pam_faillock.so authfail row with proper value of the 'deny' option before pam_deny.so, after all modules which determine authentication outcome.
    	sed -i --follow-symlinks "/^auth.*pam_deny.so.*/i auth        [default=die] pam_faillock.so authfail $_option=$_value" $_pamFile
    }

    for pamFile in "${AUTH_FILES[@]}"
    do
    	# 'true &&' has to be there due to build system limitation
    	true && set_faillock_option_to_value_in_pam_file "$pamFile" deny "$var_accounts_passwords_pam_faillock_deny" insert_lines_if_pam_faillock_so_not_present

    	# add pam_faillock.so into account phase
    	if ! grep -q "^account.*required.*pam_faillock.so" $pamFile; then
    		sed -i --follow-symlinks "/^account.*required.*pam_unix.so/i account     required      pam_faillock.so" $pamFile
    	fi
    done

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | restrict

    - name: XCCDF Value var_accounts_passwords_pam_faillock_deny # promote to variable
      set_fact:
        var_accounts_passwords_pam_faillock_deny: 6
      tags:
        - always
    - name: XCCDF Value var_accounts_passwords_pam_faillock_unlock_time # promote to variable
      set_fact:
        var_accounts_passwords_pam_faillock_unlock_time: 1800
      tags:
        - always
    - name: XCCDF Value var_accounts_passwords_pam_faillock_fail_interval # promote to variable
      set_fact:
        var_accounts_passwords_pam_faillock_fail_interval: (N/A)
      tags:
        - always

    - name: set auth pam_faillock before pam_unix.so
      pamd:
        name: system-auth
        type: auth
        control: sufficient
        module_path: pam_unix.so
        new_type: auth
        new_control: required
        new_module_path: pam_faillock.so
        module_arguments: 'preauth
            silent
            deny={{ var_accounts_passwords_pam_faillock_deny }}
            unlock_time={{ var_accounts_passwords_pam_faillock_unlock_time }}
            fail_interval={{ var_accounts_passwords_pam_faillock_fail_interval }}'
        state: before
      tags:
        - accounts_passwords_pam_faillock_deny
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-26844-1
        - NIST-800-53-AC-7(a)
        - PCI-DSS-Req-8.1.6
        - DISA-STIG-RHEL-06-000061

    - name: set auth pam_faillock after pam_unix.so
      pamd:
        name: system-auth
        type: auth
        control: sufficient
        module_path: pam_unix.so
        new_type: auth
        new_control: '[default=die]'
        new_module_path: pam_faillock.so
        module_arguments: 'preauth
            silent
            deny={{ var_accounts_passwords_pam_faillock_deny }}
            unlock_time={{ var_accounts_passwords_pam_faillock_unlock_time }}
            fail_interval={{ var_accounts_passwords_pam_faillock_fail_interval }}'
        state: after
      tags:
        - accounts_passwords_pam_faillock_deny
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-26844-1
        - NIST-800-53-AC-7(a)
        - PCI-DSS-Req-8.1.6
        - DISA-STIG-RHEL-06-000061

    - name: set account pam_faillock before pam_unix.so
      pamd:
        name: system-auth
        type: account
        control: required
        module_path: pam_unix.so
        new_type: account
        new_control: required
        new_module_path: pam_faillock.so
        state: before
      tags:
        - accounts_passwords_pam_faillock_deny
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-26844-1
        - NIST-800-53-AC-7(a)
        - PCI-DSS-Req-8.1.6
        - DISA-STIG-RHEL-06-000061

### Set Password Quality Requirements [ref]group

The default `pam_cracklib` 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 man page `pam_cracklib(8)` provides information on the capabilities and configuration of each.

contains 4 rules

### Set Password Quality Requirements, if using pam_cracklib [ref]group

The `pam_cracklib` PAM module can be configured to meet requirements for a variety of policies.
For example, to configure `pam_cracklib` to require at least one uppercase character, lowercase character, digit, and other (special) character, locate the following line in `/etc/pam.d/system-auth`:

    password requisite pam_cracklib.so try_first_pass retry=3

and then alter it to read:

    password required pam_cracklib.so try_first_pass retry=3 maxrepeat=3 minlen=14 dcredit=-1 ucredit=-1 ocredit=-1 lcredit=-1 difok=4

If no such line exists, add one as the first line of the password section in `/etc/pam.d/system-auth`. The arguments can be modified to ensure compliance with your organization's security policy. Discussion of each parameter follows.

contains 4 rules

#### Set Password Minimum Length [ref]rule

The pam_cracklib module's `minlen` parameter controls requirements for minimum characters required in a password. Add `minlen=7` after pam_pwquality to set minimum password length requirements.

Rationale:

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.

Severity: unknown

References: CCI-000205, IA-5(1)(a), Req-8.2.3

Remediation Shell script: (show)

    var_password_pam_minlen="7"

    if grep -q "minlen=" /etc/pam.d/system-auth
    then
    	sed -i --follow-symlinks "s/\(minlen *= *\).*/\1$var_password_pam_minlen/" /etc/pam.d/system-auth
    else
    	sed -i --follow-symlinks "/pam_cracklib.so/ s/$/ minlen=$var_password_pam_minlen/" /etc/pam.d/system-auth
    fi

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | restrict

    - name: XCCDF Value var_password_pam_minlen # promote to variable
      set_fact:
        var_password_pam_minlen: 7
      tags:
        - always

    - name: Set Password Minimum Length - /etc/security/pwquality.conf
      lineinfile:
        dest: /etc/security/pwquality.conf
        regexp: ^minlen =
        state: present
        line: "minlen = {{ var_password_pam_minlen }}"
      tags:
        - accounts_password_pam_minlen
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-26615-5
        - NIST-800-53-IA-5(1)(a)
        - PCI-DSS-Req-8.2.3

#### Set Password Strength Minimum Digit Characters [ref]rule

The pam_cracklib 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_cracklib will grant +1 additional length credit for each digit. Add `dcredit=-1` after pam_cracklib.so to require use of a digit in passwords.

Rationale:

Requiring digits makes password guessing attacks more difficult by ensuring a larger search space.

Severity: unknown

References: CCI-000194, IA-5(b), IA-5(c), Req-8.2.3, SRG-OS-000071, SV-50282r1_rule

Remediation Shell script: (show)

    var_password_pam_dcredit="(N/A)"

    if grep -q "dcredit=" /etc/pam.d/system-auth; then
    	sed -i --follow-symlinks "s/\(dcredit *= *\).*/\1$var_password_pam_dcredit/" /etc/pam.d/system-auth
    else
    	sed -i --follow-symlinks "/pam_cracklib.so/ s/$/ dcredit=$var_password_pam_dcredit/" /etc/pam.d/system-auth
    fi

#### Set Password Strength Minimum Lowercase Characters [ref]rule

The pam_cracklib 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_cracklib will grant +1 additional length credit for each lowercase character. Add `lcredit=-1` after pam_cracklib.so to require use of a lowercase character in passwords.

Rationale:

Requiring a minimum number of lowercase characters makes password guessing attacks more difficult by ensuring a larger search space.

Severity: unknown

References: CCI-000193, IA-5(b), IA-5(c), IA-5(1)(a), Req-8.2.3, SRG-OS-000070, SV-50372r2_rule

Remediation Shell script: (show)

    var_password_pam_lcredit="(N/A)"

    if grep -q "lcredit=" /etc/pam.d/system-auth; then
    	sed -i --follow-symlinks "s/\(lcredit *= *\).*/\1$var_password_pam_lcredit/" /etc/pam.d/system-auth
    else
    	sed -i --follow-symlinks "/pam_cracklib.so/ s/$/ lcredit=$var_password_pam_lcredit/" /etc/pam.d/system-auth
    fi

#### Set Password Strength Minimum Uppercase Characters [ref]rule

The pam_cracklib 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_cracklib will grant +1 additional length credit for each uppercase character. Add `ucredit=-1` after pam_cracklib.so to require use of an upper case character in passwords.

Rationale:

Requiring a minimum number of uppercase characters makes password guessing attacks more difficult by ensuring a larger search space.

Severity: unknown

References: 3.5.7, CCI-000192, IA-5(b), IA-5(c), IA-5(1)(a), Req-8.2.3, SRG-OS-000069, SV-50370r1_rule

Remediation Shell script: (show)

    var_password_pam_ucredit="(N/A)"

    if grep -q "ucredit=" /etc/pam.d/system-auth; then
    	sed -i --follow-symlinks "s/\(ucredit *= *\).*/\1$var_password_pam_ucredit/" /etc/pam.d/system-auth
    else
    	sed -i --follow-symlinks "/pam_cracklib.so/ s/$/ ucredit=$var_password_pam_ucredit/" /etc/pam.d/system-auth
    fi

#### Set Last Login/Access Notification [ref]rule

To configure the system to notify users of last login/access using `pam_lastlog`, add the following line immediately after `session required pam_limits.so`:

    session       required     pam_lastlog.so showfailed

Rationale:

Users need to be aware of activity that occurs regarding their account. Providing users with information regarding the number of unsuccessful attempts that were made to login to their account allows the user to determine if any unauthorized activity has occurred and gives them an opportunity to notify administrators.

Severity: unknown

References: CCI-000366, Req-10.2.4, SRG-OS-999999, SV-66089r1_rule

Remediation Shell script: (show)

    sed -i --follow-symlinks '/pam_limits.so/a session\t    required\t  pam_lastlog.so showfailed' /etc/pam.d/system-auth

### File Permissions and Masks [ref]group

Traditional 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 Red Hat Enterprise Linux 7 installations:

    $ mount -t xfs | awk '{print $3}'

For any systems that use a different local filesystem type, modify this command as appropriate.

contains 9 rules

### Verify Permissions on Important Files and Directories [ref]group

Permissions 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 9 rules

### Verify Permissions on Files with Local Account Information and Credentials [ref]group

The default restrictive permissions for files which act as important security databases such as `passwd`, `shadow`, `group`, and `gshadow` files must be maintained. Many utilities need read access to the `passwd` file in order to function properly, but read access to the `shadow` file allows malicious attacks against system passwords, and should never be enabled.

contains 9 rules

#### Verify Permissions on shadow File [ref]rule

To properly set the permissions of `/etc/shadow`, run the command:

    $ sudo chmod 0000 /etc/shadow

Rationale:

The `/etc/shadow` file contains the list of local system accounts and stores password hashes. Protection of this file is critical for system security. Failure to give ownership of this file to root provides the designated owner with access to sensitive information which could weaken the system security posture.

Severity: medium

References: CCI-000225, AC-6, Req-8.7.c, SRG-OS-999999, SV-50305r1_rule

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Strategy: | configure

    chmod 0000 /etc/shadow

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | configure

    - name: Ensure permission 0000 on /etc/shadow
      file:
        path="{{item}}"
        mode=0000
      with_items:
        - /etc/shadow
      tags:
        - file_permissions_etc_shadow
        - medium_severity
        - configure_strategy
        - low_complexity
        - low_disruption
        - CCE-26992-8
        - NIST-800-53-AC-6
        - PCI-DSS-Req-8.7.c
        - DISA-STIG-RHEL-06-000035

#### Verify Group Who Owns shadow File [ref]rule

To properly set the group owner of `/etc/shadow`, run the command:

    $ sudo chgrp root /etc/shadow

Rationale:

The `/etc/shadow` file stores password hashes. Protection of this file is critical for system security.

Severity: medium

References: CCI-000225, AC-6, Req-8.7.c, SRG-OS-999999, SV-50304r1_rule

Remediation Shell script: (show)

    chgrp root /etc/shadow

#### Verify User Who Owns group File [ref]rule

To properly set the owner of `/etc/group`, run the command:

    $ sudo chown root /etc/group

Rationale:

The `/etc/group` file contains information regarding groups that are configured on the system. Protection of this file is important for system security.

Severity: medium

References: AC-6, Req-8.7.c, SRG-OS-999999, SV-50258r1_rule

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Strategy: | configure

    chown root /etc/group

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | configure

    - name: Find /etc/group file(s)
      find:
        paths: "{{ '/etc/group' | dirname }}"
        patterns: "{{ '/etc/group' | basename }}"
      register: files_found
      tags:
        - file_owner_etc_group
        - medium_severity
        - configure_strategy
        - low_complexity
        - low_disruption
        - CCE-26822-7
        - NIST-800-53-AC-6
        - PCI-DSS-Req-8.7.c
        - DISA-STIG-RHEL-06-000042

    - name: Set user ownership to root
      file:
        path: "{{ item.path }}"
        owner: root
      with_items:
        - "{{ files_found.files }}"
      tags:
        - file_owner_etc_group
        - medium_severity
        - configure_strategy
        - low_complexity
        - low_disruption
        - CCE-26822-7
        - NIST-800-53-AC-6
        - PCI-DSS-Req-8.7.c
        - DISA-STIG-RHEL-06-000042

#### Verify Permissions on group File [ref]rule

To properly set the permissions of `/etc/group`, run the command:

    $ sudo chmod 644 /etc/group

Rationale:

The `/etc/group` file contains information regarding groups that are configured on the system. Protection of this file is important for system security.

Severity: medium

References: CCI-000225, AC-6, Req-8.7.c, SRG-OS-999999, SV-50261r1_rule

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Strategy: | configure

    chmod 0644 /etc/group

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | configure

    - name: Find /etc/group file(s)
      find:
        paths: "{{ '/etc/group' | dirname }}"
        patterns: "{{ '/etc/group' | basename }}"
      register: files_found
      tags:
        - file_permissions_etc_group
        - medium_severity
        - configure_strategy
        - low_complexity
        - low_disruption
        - CCE-26954-8
        - NIST-800-53-AC-6
        - PCI-DSS-Req-8.7.c
        - DISA-STIG-RHEL-06-000044

    - name: Set permissions
      file:
        path: "{{ item.path }}"
        mode: 0644
      with_items:
        - "{{ files_found.files }}"
      tags:
        - file_permissions_etc_group
        - medium_severity
        - configure_strategy
        - low_complexity
        - low_disruption
        - CCE-26954-8
        - NIST-800-53-AC-6
        - PCI-DSS-Req-8.7.c
        - DISA-STIG-RHEL-06-000044

#### Verify User Who Owns passwd File [ref]rule

To properly set the owner of `/etc/passwd`, run the command:

    $ sudo chown root /etc/passwd

Rationale:

The `/etc/passwd` file contains information about the users that are configured on the system. Protection of this file is critical for system security.

Severity: medium

References: CCI-000225, AC-6, Req-8.7.c, SRG-OS-999999, SV-50250r1_rule

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Strategy: | configure

    chown root /etc/passwd

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | configure

    - name: Find /etc/passwd file(s)
      find:
        paths: "{{ '/etc/passwd' | dirname }}"
        patterns: "{{ '/etc/passwd' | basename }}"
      register: files_found
      tags:
        - file_owner_etc_passwd
        - medium_severity
        - configure_strategy
        - low_complexity
        - low_disruption
        - CCE-26953-0
        - NIST-800-53-AC-6
        - PCI-DSS-Req-8.7.c
        - DISA-STIG-RHEL-06-000039

    - name: Set user ownership to root
      file:
        path: "{{ item.path }}"
        owner: root
      with_items:
        - "{{ files_found.files }}"
      tags:
        - file_owner_etc_passwd
        - medium_severity
        - configure_strategy
        - low_complexity
        - low_disruption
        - CCE-26953-0
        - NIST-800-53-AC-6
        - PCI-DSS-Req-8.7.c
        - DISA-STIG-RHEL-06-000039

#### Verify Group Who Owns passwd File [ref]rule

To properly set the group owner of `/etc/passwd`, run the command:

    $ sudo chgrp root /etc/passwd

Rationale:

The `/etc/passwd` file contains information about the users that are configured on the system. Protection of this file is critical for system security.

Severity: medium

References: CCI-000225, AC-6, Req-8.7.c, SRG-OS-999999, SV-50251r1_rule

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Strategy: | configure

    chgrp root /etc/passwd

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | configure

    - name: Find /etc/passwd file(s)
      find:
        paths: "{{ '/etc/passwd' | dirname }}"
        patterns: "{{ '/etc/passwd' | basename }}"
      register: files_found
      tags:
        - file_groupowner_etc_passwd
        - medium_severity
        - configure_strategy
        - low_complexity
        - low_disruption
        - CCE-26856-5
        - NIST-800-53-AC-6
        - PCI-DSS-Req-8.7.c
        - DISA-STIG-RHEL-06-000040

    - name: Set group ownership to root
      file:
        path: "{{ item.path }}"
        group: root
      with_items:
        - "{{ files_found.files }}"
      tags:
        - file_groupowner_etc_passwd
        - medium_severity
        - configure_strategy
        - low_complexity
        - low_disruption
        - CCE-26856-5
        - NIST-800-53-AC-6
        - PCI-DSS-Req-8.7.c
        - DISA-STIG-RHEL-06-000040

#### Verify Group Who Owns group File [ref]rule

To properly set the group owner of `/etc/group`, run the command:

    $ sudo chgrp root /etc/group

Rationale:

The `/etc/group` file contains information regarding groups that are configured on the system. Protection of this file is important for system security.

Severity: medium

References: CCI-000225, AC-6, Req-8.7.c, SRG-OS-999999, SV-50259r1_rule

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Strategy: | configure

    chgrp root /etc/group

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | configure

    - name: Find /etc/group file(s)
      find:
        paths: "{{ '/etc/group' | dirname }}"
        patterns: "{{ '/etc/group' | basename }}"
      register: files_found
      tags:
        - file_groupowner_etc_group
        - medium_severity
        - configure_strategy
        - low_complexity
        - low_disruption
        - CCE-26930-8
        - NIST-800-53-AC-6
        - PCI-DSS-Req-8.7.c
        - DISA-STIG-RHEL-06-000043

    - name: Set group ownership to root
      file:
        path: "{{ item.path }}"
        group: root
      with_items:
        - "{{ files_found.files }}"
      tags:
        - file_groupowner_etc_group
        - medium_severity
        - configure_strategy
        - low_complexity
        - low_disruption
        - CCE-26930-8
        - NIST-800-53-AC-6
        - PCI-DSS-Req-8.7.c
        - DISA-STIG-RHEL-06-000043

#### Verify User Who Owns shadow File [ref]rule

To properly set the owner of `/etc/shadow`, run the command:

    $ sudo chown root /etc/shadow

Rationale:

The `/etc/shadow` file contains the list of local system accounts and stores password hashes. Protection of this file is critical for system security. Failure to give ownership of this file to root provides the designated owner with access to sensitive information which could weaken the system security posture.

Severity: medium

References: CCI-000225, AC-6, Req-8.7.c, SRG-OS-999999, SV-50303r1_rule

Remediation Shell script: (show)

    chown root /etc/shadow

#### Verify Permissions on passwd File [ref]rule

To properly set the permissions of `/etc/passwd`, run the command:

    $ sudo chmod 0644 /etc/passwd

Rationale:

If the `/etc/passwd` file is writable by a group-owner or the world the risk of its compromise is increased. The file contains the list of accounts on the system and associated information, and protection of this file is critical for system security.

Severity: medium

References: CCI-000225, AC-6, Req-8.7.c, SRG-OS-999999, SV-50257r1_rule

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Strategy: | configure

    chmod 0644 /etc/passwd

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | configure

    - name: Ensure permission 0644 on /etc/passwd
      file:
        path="{{item}}"
        mode=0644
      with_items:
        - /etc/passwd
      tags:
        - file_permissions_etc_passwd
        - medium_severity
        - configure_strategy
        - low_complexity
        - low_disruption
        - CCE-26868-0
        - NIST-800-53-AC-6
        - PCI-DSS-Req-8.7.c
        - DISA-STIG-RHEL-06-000041

### System Accounting with <tt>auditd</tt> [ref]group

The audit service provides substantial capabilities for recording system activities. By default, the service audits about SELinux AVC denials and certain types of security-relevant events such as system logins, account modifications, and authentication events performed by programs such as sudo. Under its default configuration, `auditd` has modest disk space requirements, and should not noticeably impact system performance.
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 Fedora Documentation available at `http://docs.fedoraproject.org/en-US/Fedora/13/html/Security-Enhanced_Linux/sect-Security-Enhanced_Linux-Fixing_Problems-Raw_Audit_Messages.html` 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 41 rules

### Configure <tt>auditd</tt> Data Retention [ref]group

The 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 auditd to use audispd's syslog plugin [ref]rule

To configure the `auditd` service to use the `syslog` plug-in of the `audispd` audit event multiplexor, set the `active` line in `/etc/audisp/plugins.d/syslog.conf` to `yes`. Restart the `auditd` service:

    $ sudo service auditd restart

Rationale:

The auditd service does not include the ability to send audit records to a centralized server for management directly. It does, however, include a plug-in for audit event multiplexor (audispd) to pass audit records to the local syslog server

Severity: low

References: CCI-000136, AU-1(b), AU-3(2), IR-5, Req-10.5.3, SRG-OS-000043, SV-50271r1_rule

Remediation Shell script: (show)

    grep -q ^active /etc/audisp/plugins.d/syslog.conf && \
      sed -i "s/active.*/active = yes/g" /etc/audisp/plugins.d/syslog.conf
    if ! [ $? -eq 0 ]; then
        echo "active = yes" >> /etc/audisp/plugins.d/syslog.conf
    fi

#### Configure auditd Max Log File Size [ref]rule

Determine the amount of audit data (in megabytes) which should be retained in each log file. Edit the file `/etc/audit/auditd.conf`. Add or modify the following line, substituting the correct value of (N/A) for STOREMB:

    max_log_file = STOREMB

Set the value to `6` (MB) or higher for general-purpose systems. Larger values, of course, support retention of even more audit data.Rationale:

The total storage for audit log files must be large enough to retain log information over the period required. This is a function of the maximum log file size and the number of logs retained.

Severity: medium

References: AU-1(b), AU-11, IR-5, Req-10.7, SRG-OS-999999, SV-50434r1_rule

Remediation Shell script: (show)

    var_auditd_max_log_file="(N/A)"

    AUDITCONFIG=/etc/audit/auditd.conf

    grep -q ^max_log_file $AUDITCONFIG && \
      sed -i 's/^max_log_file.*/max_log_file = '"$var_auditd_max_log_file"'/g' $AUDITCONFIG
    if ! [ $? -eq 0 ]; then
      echo "max_log_file = $var_auditd_max_log_file" >> $AUDITCONFIG
    fi

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | restrict

    - name: XCCDF Value var_auditd_max_log_file # promote to variable
      set_fact:
        var_auditd_max_log_file: (N/A)
      tags:
        - always

    - name: Configure auditd Max Log File Size
      lineinfile:
        dest: /etc/audit/auditd.conf
        line: "max_log_file {{ var_auditd_max_log_file }}"
        state: present
      #notify: reload auditd
      tags:
        - auditd_data_retention_max_log_file
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27550-3
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-11
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.7
        - DISA-STIG-RHEL-06-000160

#### Configure auditd mail_acct Action on Low Disk Space [ref]rule

The `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 = (N/A)

Rationale:

Email sent to the root account is typically aliased to the administrators of the system, who can take appropriate action.

Severity: medium

References: CCI-000139, CCI-000144, AU-1(b), AU-4, AU-5(a), IR-5, Req-10.7.a, SRG-OS-000046, SV-50481r1_rule

Remediation Shell script: (show)

    var_auditd_action_mail_acct="(N/A)"

    AUDITCONFIG=/etc/audit/auditd.conf

    grep -q ^action_mail_acct $AUDITCONFIG && \
      sed -i 's/^action_mail_acct.*/action_mail_acct = '"$var_auditd_action_mail_acct"'/g' $AUDITCONFIG
    if ! [ $? -eq 0 ]; then
      echo "action_mail_acct = $var_auditd_action_mail_acct" >> $AUDITCONFIG
    fi

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | restrict

    - name: XCCDF Value var_auditd_action_mail_acct # promote to variable
      set_fact:
        var_auditd_action_mail_acct: (N/A)
      tags:
        - always

    - name: Configure auditd mail_acct Action on Low Disk Space
      lineinfile:
        dest: /etc/audit/auditd.conf
        line: "action_mail_acct = {{ var_auditd_action_mail_acct }}"
        state: present
      #notify: reload auditd
      tags:
        - auditd_data_retention_action_mail_acct
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27241-9
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-4
        - NIST-800-53-AU-5(a)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.7.a
        - DISA-STIG-RHEL-06-000313

#### Configure auditd admin_space_left Action on Low Disk Space [ref]rule

The `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:

    admin_space_left_action = ACTION

Set this value to `single` to cause the system to switch to single-user mode for corrective action. Acceptable values also include `suspend` 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:

Administrators should be made aware of an inability to record audit records. If a separate partition or logical volume of adequate size is used, running low on space for audit records should never occur.

Severity: medium

References: CCI-000140, CCI-001343, AU-1(b), AU-4, AU-5(b), IR-5, Req-10.7, SRG-OS-999999, SV-68627r3_rule

Remediation Shell script: (show)

    var_auditd_admin_space_left_action="(N/A)"

    grep -q ^admin_space_left_action /etc/audit/auditd.conf && \
      sed -i "s/admin_space_left_action.*/admin_space_left_action = $var_auditd_admin_space_left_action/g" /etc/audit/auditd.conf
    if ! [ $? -eq 0 ]; then
        echo "admin_space_left_action = $var_auditd_admin_space_left_action" >> /etc/audit/auditd.conf
    fi

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | restrict

    - name: XCCDF Value var_auditd_admin_space_left_action # promote to variable
      set_fact:
        var_auditd_admin_space_left_action: (N/A)
      tags:
        - always

    - name: Configure auditd admin_space_left Action on Low Disk Space
      lineinfile:
        dest: /etc/audit/auditd.conf
        line: "admin_space_left_action = {{ var_auditd_admin_space_left_action }}"
        regexp: "^admin_space_left_action*"
      #notify: reload auditd
      tags:
        - auditd_data_retention_admin_space_left_action
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27239-3
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-4
        - NIST-800-53-AU-5(b)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.7
        - DISA-STIG-RHEL-06-000163

#### Configure auditd space_left Action on Low Disk Space [ref]rule

The `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:

- `ignore`
- `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.

Severity: medium

References: CCI-000140, CCI-000143, AU-1(b), AU-4, AU-5(b), IR-5, Req-10.7, SRG-OS-000045, SV-50270r2_rule

Remediation Shell script: (show)

    var_auditd_space_left_action="(N/A)"

    #
    # 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
    #

    if grep --silent ^space_left_action /etc/audit/auditd.conf ; then
            sed -i 's/^space_left_action.*/space_left_action = '"$var_auditd_space_left_action"'/g' /etc/audit/auditd.conf
    else
            echo -e "\n# Set space_left_action to $var_auditd_space_left_action per security requirements" >> /etc/audit/auditd.conf
            echo "space_left_action = $var_auditd_space_left_action" >> /etc/audit/auditd.conf
    fi

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | restrict

    - name: XCCDF Value var_auditd_space_left_action # promote to variable
      set_fact:
        var_auditd_space_left_action: (N/A)
      tags:
        - always

    - name: Configure auditd space_left Action on Low Disk Space
      lineinfile:
        dest: /etc/audit/auditd.conf
        line: "space_left_action = {{ var_auditd_space_left_action }}"
        regexp: ^space_left_action*
      #notify: reload auditd
      tags:
        - auditd_data_retention_space_left_action
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27238-5
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-4
        - NIST-800-53-AU-5(b)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.7
        - DISA-STIG-RHEL-06-000005

#### Configure auditd Number of Logs Retained [ref]rule

Determine how many log files `auditd` should retain when it rotates logs. Edit the file `/etc/audit/auditd.conf`. Add or modify the following line, substituting NUMLOGS with the correct value of (N/A):

    num_logs = NUMLOGS

Set the value to 5 for general-purpose systems. Note that values less than 2 result in no log rotation.Rationale:

The total storage for audit log files must be large enough to retain log information over the period required. This is a function of the maximum log file size and the number of logs retained.

Severity: medium

References: AU-1(b), AU-11, IR-5, Req-10.7, SRG-OS-999999, SV-50437r1_rule

Remediation Shell script: (show)

    var_auditd_num_logs="(N/A)"

    AUDITCONFIG=/etc/audit/auditd.conf

    grep -q ^num_logs $AUDITCONFIG && \
      sed -i 's/^num_logs.*/num_logs = '"$var_auditd_num_logs"'/g' $AUDITCONFIG
    if ! [ $? -eq 0 ]; then
      echo "num_logs = $var_auditd_num_logs" >> $AUDITCONFIG
    fi

#### Configure auditd max_log_file_action Upon Reaching Maximum Log Size [ref]rule

The default action to take when the logs reach their maximum size is to rotate the log files, discarding the oldest one. To configure the action taken by `auditd`, add or correct the line in `/etc/audit/auditd.conf`:

    max_log_file_action = ACTION

Possible values for ACTION are described in the `auditd.conf` man page. These include:

- `ignore`
- `syslog`
- `suspend`
- `rotate`
- `keep_logs`

Set the `ACTION` to `rotate` to ensure log rotation occurs. This is the default. The setting is case-insensitive.Rationale:

Automatically rotating logs (by setting this to `rotate`) minimizes the chances of the system unexpectedly running out of disk space by being overwhelmed with log data. However, for systems that must never discard log data, or which use external processes to transfer it and reclaim space, `keep_logs` can be employed.

Severity: medium

References: AU-1(b), AU-4, AU-11, IR-5, Req-10.7, SRG-OS-999999, SV-50435r2_rule

Remediation Shell script: (show)

    var_auditd_max_log_file_action="(N/A)"

    AUDITCONFIG=/etc/audit/auditd.conf

    grep -q ^max_log_file_action $AUDITCONFIG && \
      sed -i 's/^max_log_file_action.*/max_log_file_action = '"$var_auditd_max_log_file_action"'/g' $AUDITCONFIG
    if ! [ $? -eq 0 ]; then
      echo "max_log_file_action = $var_auditd_max_log_file_action" >> $AUDITCONFIG
    fi

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | restrict

    - name: XCCDF Value var_auditd_max_log_file_action # promote to variable
      set_fact:
        var_auditd_max_log_file_action: (N/A)
      tags:
        - always

    - name: Configure auditd max_log_file_action Upon Reaching Maximum Log Size
      lineinfile:
        dest: /etc/audit/auditd.conf
        line: "max_log_file_action {{ var_auditd_max_log_file_action }}"
        state: present
      #notify: reload auditd
      tags:
        - auditd_data_retention_max_log_file_action
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27237-7
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-4
        - NIST-800-53-AU-11
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.7
        - DISA-STIG-RHEL-06-000161

### Configure <tt>auditd</tt> Rules for Comprehensive Auditing [ref]group

The `auditd` program can perform comprehensive monitoring of system activity. This section describes recommended configuration settings for comprehensive auditing, but a full description of the auditing system's capabilities is beyond the scope of this guide. The mailing list linux-audit@redhat.com exists to facilitate community discussion of the auditing system.
The audit subsystem supports extensive collection of events, including:

- Tracing of arbitrary system calls (identified by name or number) on entry or exit.
- Filtering by PID, UID, call success, system call argument (with some limitations), etc.
- Monitoring of specific files for modifications to the file's contents or metadata.

Auditing rules at startup are controlled by the file `/etc/audit/audit.rules`. Add rules to it to meet the auditing requirements for your organization. Each line in `/etc/audit/audit.rules` represents a series of arguments that can be passed to `auditctl` and can be individually tested during runtime. See documentation in `/usr/share/doc/audit-VERSION` and in the related man pages for more details.
If copying any example audit rulesets from `/usr/share/doc/audit-VERSION`, be sure to comment out the lines containing `arch=` which are not appropriate for your system's architecture. Then review and understand the following rules, ensuring rules are activated as needed for the appropriate architecture.
After reviewing all the rules, reading the following sections, and editing as needed, the new rules can be activated as follows:

    $ sudo service auditd restart

contains 32 rules

### Record Events that Modify the System's Discretionary Access Controls [ref]group

At a minimum, the audit system should collect file permission changes for all users and root. Note that the "-F arch=b32" lines should be present even on a 64 bit system. These commands identify system calls for auditing. Even if the system is 64 bit it can still execute 32 bit system calls. Additionally, these rules can be configured in a number of ways while still achieving the desired effect. An example of this is that the "-S" calls could be split up and placed on separate lines, however, this is less efficient. Add the following to `/etc/audit/audit.rules`:

    -a always,exit -F arch=b32 -S chmod -S fchmod -S fchmodat -F auid>=500 -F auid!=4294967295 -k perm_mod
        -a always,exit -F arch=b32 -S chown -S fchown -S fchownat -S lchown -F auid>=500 -F auid!=4294967295 -k perm_mod
        -a always,exit -F arch=b32 -S setxattr -S lsetxattr -S fsetxattr -S removexattr -S lremovexattr -S fremovexattr -F auid>=500 -F auid!=4294967295 -k 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 -S fchmod -S fchmodat -F auid>=500 -F auid!=4294967295 -k perm_mod
        -a always,exit -F arch=b64 -S chown -S fchown -S fchownat -S lchown -F auid>=500 -F auid!=4294967295 -k perm_mod
        -a always,exit -F arch=b64 -S setxattr -S lsetxattr -S fsetxattr -S removexattr -S lremovexattr -S fremovexattr -F auid>=500 -F auid!=4294967295 -k perm_mod

contains 13 rules

#### Record Events that Modify the System's Discretionary Access Controls - fchown [ref]rule

At a minimum the audit system should collect file permission changes for all users and root. Add the following to `/etc/audit/audit.rules`:

    -a always,exit -F arch=b32 -S fchown -F auid>=500 -F auid!=4294967295 -k perm_mod

If the system is 64 bit then also add the following:

    -a always,exit -F arch=b64 -S fchown -F auid>=500 -F auid!=4294967295 -k perm_mod

Rationale:

The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.

Severity: unknown

References: CCI-000126, AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064, SV-50353r3_rule

Remediation Shell script: (show)

    # Perform the remediation for 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
    	PATTERN="-a always,exit -F arch=$ARCH -S .* -F auid>=500 -F auid!=4294967295 -k *"
    	GROUP="chown"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S chown -S fchown -S fchownat -S lchown -F auid>=500 -F auid!=4294967295 -k perm_mod"
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # 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
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict

    #
    # What architecture are we on?
    #
    - name: Set architecture for audit fchown tasks
      set_fact:
        audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"

    #
    # Inserts/replaces the rule in /etc/audit/rules.d
    #
    - name: Search /etc/audit/rules.d for other DAC audit rules
      find:
        paths: "/etc/audit/rules.d"
        recurse: no
        contains: "-F key=perm_mod$"
        patterns: "*.rules"
      register: find_fchown

    - name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/privileged.rules
      when: find_fchown.matched == 0

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - "{{ find_fchown.files | map(attribute='path') | list | first }}"
      when: find_fchown.matched > 0

    - name: Inserts/replaces the fchown rule in rules.d when on x86
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b32 -S fchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      tags:
        - audit_rules_dac_modification_fchown
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27177-5
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000188

    - name: Inserts/replaces the fchown rule in rules.d when on x86_64
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b64 -S fchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_fchown
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27177-5
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000188
    #
    # Inserts/replaces the rule in /etc/audit/audit.rules
    #
    - name: Inserts/replaces the fchown rule in /etc/audit/audit.rules when on x86
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
      with_items:
        - "-a always,exit -F arch=b32 -S fchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      tags:
        - audit_rules_dac_modification_fchown
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27177-5
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000188

    - name: Inserts/replaces the fchown rule in audit.rules when on x86_64
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
        create: yes
      with_items:
        - "-a always,exit -F arch=b64 -S fchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_fchown
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27177-5
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000188

#### Record Events that Modify the System's Discretionary Access Controls - setxattr [ref]rule

At a minimum the audit system should collect file permission changes for all users and root. Add the following to `/etc/audit/audit.rules`:

    -a always,exit -F arch=b32 -S setxattr -F auid>=500 -F auid!=4294967295 -k perm_mod

If the system is 64 bit then also add the following:

    -a always,exit -F arch=b64 -S setxattr -F auid>=500 -F auid!=4294967295 -k perm_mod

Rationale:

The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.

Severity: unknown

References: CCI-000126, AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064, SV-50366r3_rule

Remediation Shell script: (show)

    # Perform the remediation for 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
    	PATTERN="-a always,exit .* -F auid>=500 -F auid!=4294967295 -k *"
    	GROUP="xattr"
    	FULL_RULE="-a always,exit -F arch=${ARCH} -S setxattr -S lsetxattr -S fsetxattr -S removexattr -S lremovexattr -S fremovexattr -F auid>=500 -F auid!=4294967295 -k perm_mod"
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # 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
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict

    #
    # What architecture are we on?
    #
    - name: Set architecture for audit setxattr tasks
      set_fact:
        audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"

    #
    # Inserts/replaces the rule in /etc/audit/rules.d
    #
    - name: Search /etc/audit/rules.d for other DAC audit rules
      find:
        paths: "/etc/audit/rules.d"
        recurse: no
        contains: "-F key=perm_mod$"
        patterns: "*.rules"
      register: find_setxattr

    - name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/privileged.rules
      when: find_setxattr.matched == 0

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - "{{ find_setxattr.files | map(attribute='path') | list | first }}"
      when: find_setxattr.matched > 0

    - name: Inserts/replaces the setxattr rule in rules.d when on x86
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b32 -S setxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      tags:
        - audit_rules_dac_modification_setxattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27185-8
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000196

    - name: Inserts/replaces the setxattr rule in rules.d when on x86_64
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b64 -S setxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_setxattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27185-8
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000196
    #
    # Inserts/replaces the rule in /etc/audit/audit.rules
    #
    - name: Inserts/replaces the setxattr rule in /etc/audit/audit.rules when on x86
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
      with_items:
        - "-a always,exit -F arch=b32 -S setxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      tags:
        - audit_rules_dac_modification_setxattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27185-8
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000196

    - name: Inserts/replaces the setxattr rule in audit.rules when on x86_64
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
        create: yes
      with_items:
        - "-a always,exit -F arch=b64 -S setxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_setxattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27185-8
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000196

#### Record Events that Modify the System's Discretionary Access Controls - chown [ref]rule

At a minimum the audit system should collect file permission changes for all users and root. Add the following to `/etc/audit/audit.rules`:

    -a always,exit -F arch=b32 -S chown -F auid>=500 -F auid!=4294967295 -k perm_mod

If the system is 64 bit then also add the following:

    -a always,exit -F arch=b64 -S chown -F auid>=500 -F auid!=4294967295 -k perm_mod

Rationale:

The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.

Severity: unknown

References: CCI-000126, AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064, SV-50346r3_rule

Remediation Shell script: (show)

    # Perform the remediation for 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
    	PATTERN="-a always,exit -F arch=$ARCH -S .* -F auid>=500 -F auid!=4294967295 -k *"
    	GROUP="chown"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S chown -S fchown -S fchownat -S lchown -F auid>=500 -F auid!=4294967295 -k perm_mod"
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # 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
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict

    #
    # What architecture are we on?
    #
    - name: Set architecture for audit chown tasks
      set_fact:
        audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"

    #
    # Inserts/replaces the rule in /etc/audit/rules.d
    #
    - name: Search /etc/audit/rules.d for other DAC audit rules
      find:
        paths: "/etc/audit/rules.d"
        recurse: no
        contains: "-F key=perm_mod$"
        patterns: "*.rules"
      register: find_chown

    - name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/privileged.rules
      when: find_chown.matched == 0

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - "{{ find_chown.files | map(attribute='path') | list | first }}"
      when: find_chown.matched > 0

    - name: Inserts/replaces the chown rule in rules.d when on x86
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b32 -S chown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      tags:
        - audit_rules_dac_modification_chown
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27173-4
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000185

    - name: Inserts/replaces the chown rule in rules.d when on x86_64
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b64 -S chown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_chown
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27173-4
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000185
    #
    # Inserts/replaces the rule in /etc/audit/audit.rules
    #
    - name: Inserts/replaces the chown rule in /etc/audit/audit.rules when on x86
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
      with_items:
        - "-a always,exit -F arch=b32 -S chown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      tags:
        - audit_rules_dac_modification_chown
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27173-4
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000185

    - name: Inserts/replaces the chown rule in audit.rules when on x86_64
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
        create: yes
      with_items:
        - "-a always,exit -F arch=b64 -S chown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_chown
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27173-4
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000185

#### Record Events that Modify the System's Discretionary Access Controls - lsetxattr [ref]rule

At a minimum the audit system should collect file permission changes for all users and root. Add the following to `/etc/audit/audit.rules`:

    -a always,exit -F arch=b32 -S lsetxattr -F auid>=500 -F auid!=4294967295 -k perm_mod

If the system is 64 bit then also add the following:

    -a always,exit -F arch=b64 -S lsetxattr -F auid>=500 -F auid!=4294967295 -k perm_mod

Rationale:

The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.

Severity: unknown

References: CCI-000126, AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064, SV-50362r3_rule

Remediation Shell script: (show)

    # Perform the remediation for 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
    	PATTERN="-a always,exit .* -F auid>=500 -F auid!=4294967295 -k *"
    	GROUP="xattr"
    	FULL_RULE="-a always,exit -F arch=${ARCH} -S setxattr -S lsetxattr -S fsetxattr -S removexattr -S lremovexattr -S fremovexattr -F auid>=500 -F auid!=4294967295 -k perm_mod"
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # 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
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict

    #
    # What architecture are we on?
    #
    - name: Set architecture for audit lsetxattr tasks
      set_fact:
        audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"

    #
    # Inserts/replaces the rule in /etc/audit/rules.d
    #
    - name: Search /etc/audit/rules.d for other DAC audit rules
      find:
        paths: "/etc/audit/rules.d"
        recurse: no
        contains: "-F key=perm_mod$"
        patterns: "*.rules"
      register: find_lsetxattr

    - name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/privileged.rules
      when: find_lsetxattr.matched == 0

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - "{{ find_lsetxattr.files | map(attribute='path') | list | first }}"
      when: find_lsetxattr.matched > 0

    - name: Inserts/replaces the lsetxattr rule in rules.d when on x86
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b32 -S lsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      tags:
        - audit_rules_dac_modification_lsetxattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27183-3
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000194

    - name: Inserts/replaces the lsetxattr rule in rules.d when on x86_64
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b64 -S lsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_lsetxattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27183-3
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000194
    #
    # Inserts/replaces the rule in /etc/audit/audit.rules
    #
    - name: Inserts/replaces the lsetxattr rule in /etc/audit/audit.rules when on x86
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
      with_items:
        - "-a always,exit -F arch=b32 -S lsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      tags:
        - audit_rules_dac_modification_lsetxattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27183-3
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000194

    - name: Inserts/replaces the lsetxattr rule in audit.rules when on x86_64
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
        create: yes
      with_items:
        - "-a always,exit -F arch=b64 -S lsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_lsetxattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27183-3
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000194

#### Record Events that Modify the System's Discretionary Access Controls - lchown [ref]rule

At a minimum the audit system should collect file permission changes for all users and root. Add the following to `/etc/audit/audit.rules`:

    -a always,exit -F arch=b32 -S lchown -F auid>=500 -F auid!=4294967295 -k perm_mod

If the system is 64 bit then also add the following:

    -a always,exit -F arch=b64 -S lchown -F auid>=500 -F auid!=4294967295 -k perm_mod

Rationale:

The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.

Severity: unknown

References: CCI-000126, AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064, SV-50359r3_rule

Remediation Shell script: (show)

    # Perform the remediation for 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
    	PATTERN="-a always,exit -F arch=$ARCH -S .* -F auid>=500 -F auid!=4294967295 -k *"
    	GROUP="chown"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S chown -S fchown -S fchownat -S lchown -F auid>=500 -F auid!=4294967295 -k perm_mod"
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # 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
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict

    #
    # What architecture are we on?
    #
    - name: Set architecture for audit lchown tasks
      set_fact:
        audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"

    #
    # Inserts/replaces the rule in /etc/audit/rules.d
    #
    - name: Search /etc/audit/rules.d for other DAC audit rules
      find:
        paths: "/etc/audit/rules.d"
        recurse: no
        contains: "-F key=perm_mod$"
        patterns: "*.rules"
      register: find_lchown

    - name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/privileged.rules
      when: find_lchown.matched == 0

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - "{{ find_lchown.files | map(attribute='path') | list | first }}"
      when: find_lchown.matched > 0

    - name: Inserts/replaces the lchown rule in rules.d when on x86
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b32 -S lchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      tags:
        - audit_rules_dac_modification_lchown
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27181-7
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000192

    - name: Inserts/replaces the lchown rule in rules.d when on x86_64
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b64 -S lchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_lchown
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27181-7
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000192
    #
    # Inserts/replaces the rule in /etc/audit/audit.rules
    #
    - name: Inserts/replaces the lchown rule in /etc/audit/audit.rules when on x86
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
      with_items:
        - "-a always,exit -F arch=b32 -S lchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      tags:
        - audit_rules_dac_modification_lchown
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27181-7
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000192

    - name: Inserts/replaces the lchown rule in audit.rules when on x86_64
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
        create: yes
      with_items:
        - "-a always,exit -F arch=b64 -S lchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_lchown
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27181-7
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000192

#### Record Events that Modify the System's Discretionary Access Controls - chmod [ref]rule

At a minimum the audit system should collect file permission changes for all users and root. Add the following to `/etc/audit/audit.rules`:

    -a always,exit -F arch=b32 -S chmod -F auid>=500 -F auid!=4294967295 -k perm_mod

If the system is 64 bit then also add the following:

    -a always,exit -F arch=b64 -S chmod  -F auid>=500 -F auid!=4294967295 -k perm_mod

Rationale:

The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.

Severity: unknown

References: CCI-000126, AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064, SV-50344r3_rule

Remediation Shell script: (show)

    # Perform the remediation for 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
    	PATTERN="-a always,exit -F arch=$ARCH -S .* -F auid>=500 -F auid!=4294967295 -k *"
    	GROUP="chmod"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S chmod -S fchmod -S fchmodat -F auid>=500 -F auid!=4294967295 -k perm_mod"
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # 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
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict

    #
    # What architecture are we on?
    #
    - name: Set architecture for audit chmod tasks
      set_fact:
        audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"

    #
    # Inserts/replaces the rule in /etc/audit/rules.d
    #
    - name: Search /etc/audit/rules.d for other DAC audit rules
      find:
        paths: "/etc/audit/rules.d"
        recurse: no
        contains: "-F key=perm_mod$"
        patterns: "*.rules"
      register: find_chmod

    - name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/privileged.rules
      when: find_chmod.matched == 0

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - "{{ find_chmod.files | map(attribute='path') | list | first }}"
      when: find_chmod.matched > 0

    - name: Inserts/replaces the chmod rule in rules.d when on x86
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b32 -S chmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      tags:
        - audit_rules_dac_modification_chmod
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-26280-8
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000184

    - name: Inserts/replaces the chmod rule in rules.d when on x86_64
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b64 -S chmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_chmod
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-26280-8
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000184
    #
    # Inserts/replaces the rule in /etc/audit/audit.rules
    #
    - name: Inserts/replaces the chmod rule in /etc/audit/audit.rules when on x86
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
      with_items:
        - "-a always,exit -F arch=b32 -S chmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      tags:
        - audit_rules_dac_modification_chmod
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-26280-8
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000184

    - name: Inserts/replaces the chmod rule in audit.rules when on x86_64
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
        create: yes
      with_items:
        - "-a always,exit -F arch=b64 -S chmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_chmod
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-26280-8
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000184

#### Record Events that Modify the System's Discretionary Access Controls - removexattr [ref]rule

At a minimum the audit system should collect file permission changes for all users and root. Add the following to `/etc/audit/audit.rules`:

    -a always,exit -F arch=b32 -S removexattr -F auid>=500 -F auid!=4294967295 -k perm_mod

If the system is 64 bit then also add the following:

    -a always,exit -F arch=b64 -S removexattr -F auid>=500 -F auid!=4294967295 -k perm_mod

Rationale:

The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.

Severity: unknown

References: CCI-000126, AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064, SV-50364r3_rule

Remediation Shell script: (show)

    # Perform the remediation for 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
    	PATTERN="-a always,exit .* -F auid>=500 -F auid!=4294967295 -k *"
    	GROUP="xattr"
    	FULL_RULE="-a always,exit -F arch=${ARCH} -S setxattr -S lsetxattr -S fsetxattr -S removexattr -S lremovexattr -S fremovexattr -F auid>=500 -F auid!=4294967295 -k perm_mod"
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # 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
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict

    #
    # What architecture are we on?
    #
    - name: Set architecture for audit removexattr tasks
      set_fact:
        audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"

    #
    # Inserts/replaces the rule in /etc/audit/rules.d
    #
    - name: Search /etc/audit/rules.d for other DAC audit rules
      find:
        paths: "/etc/audit/rules.d"
        recurse: no
        contains: "-F key=perm_mod$"
        patterns: "*.rules"
      register: find_removexattr

    - name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/privileged.rules
      when: find_removexattr.matched == 0

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - "{{ find_removexattr.files | map(attribute='path') | list | first }}"
      when: find_removexattr.matched > 0

    - name: Inserts/replaces the removexattr rule in rules.d when on x86
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b32 -S removexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      tags:
        - audit_rules_dac_modification_removexattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27184-1
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000195

    - name: Inserts/replaces the removexattr rule in rules.d when on x86_64
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b64 -S removexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_removexattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27184-1
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000195
    #
    # Inserts/replaces the rule in /etc/audit/audit.rules
    #
    - name: Inserts/replaces the removexattr rule in /etc/audit/audit.rules when on x86
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
      with_items:
        - "-a always,exit -F arch=b32 -S removexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      tags:
        - audit_rules_dac_modification_removexattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27184-1
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000195

    - name: Inserts/replaces the removexattr rule in audit.rules when on x86_64
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
        create: yes
      with_items:
        - "-a always,exit -F arch=b64 -S removexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_removexattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27184-1
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000195

#### Record Events that Modify the System's Discretionary Access Controls - fchmod [ref]rule

At a minimum the audit system should collect file permission changes for all users and root. Add the following to `/etc/audit/audit.rules`:

    -a always,exit -F arch=b32 -S fchmod -F auid>=500 -F auid!=4294967295 -k perm_mod

If the system is 64 bit then also add the following:

    -a always,exit -F arch=b64 -S fchmod -F auid>=500 -F auid!=4294967295 -k perm_mod

Rationale:

The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.

Severity: unknown

References: CCI-000126, AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064, SV-50348r3_rule

Remediation Shell script: (show)

    # Perform the remediation for 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
    	PATTERN="-a always,exit -F arch=$ARCH -S .* -F auid>=500 -F auid!=4294967295 -k *"
    	GROUP="chmod"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S chmod -S fchmod -S fchmodat -F auid>=500 -F auid!=4294967295 -k perm_mod"
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # 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
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict

    #
    # What architecture are we on?
    #
    - name: Set architecture for audit fchmod tasks
      set_fact:
        audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"

    #
    # Inserts/replaces the rule in /etc/audit/rules.d
    #
    - name: Search /etc/audit/rules.d for other DAC audit rules
      find:
        paths: "/etc/audit/rules.d"
        recurse: no
        contains: "-F key=perm_mod$"
        patterns: "*.rules"
      register: find_fchmod

    - name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/privileged.rules
      when: find_fchmod.matched == 0

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - "{{ find_fchmod.files | map(attribute='path') | list | first }}"
      when: find_fchmod.matched > 0

    - name: Inserts/replaces the fchmod rule in rules.d when on x86
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b32 -S fchmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      tags:
        - audit_rules_dac_modification_fchmod
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27174-2
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000186

    - name: Inserts/replaces the fchmod rule in rules.d when on x86_64
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b64 -S fchmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_fchmod
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27174-2
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000186
    #
    # Inserts/replaces the rule in /etc/audit/audit.rules
    #
    - name: Inserts/replaces the fchmod rule in /etc/audit/audit.rules when on x86
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
      with_items:
        - "-a always,exit -F arch=b32 -S fchmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      tags:
        - audit_rules_dac_modification_fchmod
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27174-2
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000186

    - name: Inserts/replaces the fchmod rule in audit.rules when on x86_64
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
        create: yes
      with_items:
        - "-a always,exit -F arch=b64 -S fchmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_fchmod
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27174-2
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000186

#### Record Events that Modify the System's Discretionary Access Controls - fchownat [ref]rule

At a minimum the audit system should collect file permission changes for all users and root. Add the following to `/etc/audit/audit.rules`:

    -a always,exit -F arch=b32 -S fchownat -F auid>=500 -F auid!=4294967295 -k perm_mod

If the system is 64 bit then also add the following:

    -a always,exit -F arch=b64 -S fchownat -F auid>=500 -F auid!=4294967295 -k perm_mod

Rationale:

The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.

Severity: unknown

References: CCI-000126, AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064, SV-50355r3_rule

Remediation Shell script: (show)

    # Perform the remediation for 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
    	PATTERN="-a always,exit -F arch=$ARCH -S .* -F auid>=500 -F auid!=4294967295 -k *"
    	GROUP="chown"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S chown -S fchown -S fchownat -S lchown -F auid>=500 -F auid!=4294967295 -k perm_mod"
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # 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
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict

    #
    # What architecture are we on?
    #
    - name: Set architecture for audit fchownat tasks
      set_fact:
        audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"

    #
    # Inserts/replaces the rule in /etc/audit/rules.d
    #
    - name: Search /etc/audit/rules.d for other DAC audit rules
      find:
        paths: "/etc/audit/rules.d"
        recurse: no
        contains: "-F key=perm_mod$"
        patterns: "*.rules"
      register: find_fchownat

    - name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/privileged.rules
      when: find_fchownat.matched == 0

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - "{{ find_fchownat.files | map(attribute='path') | list | first }}"
      when: find_fchownat.matched > 0

    - name: Inserts/replaces the fchownat rule in rules.d when on x86
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b32 -S fchownat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      tags:
        - audit_rules_dac_modification_fchownat
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27178-3
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000189

    - name: Inserts/replaces the fchownat rule in rules.d when on x86_64
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b64 -S fchownat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_fchownat
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27178-3
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000189
    #
    # Inserts/replaces the rule in /etc/audit/audit.rules
    #
    - name: Inserts/replaces the fchownat rule in /etc/audit/audit.rules when on x86
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
      with_items:
        - "-a always,exit -F arch=b32 -S fchownat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      tags:
        - audit_rules_dac_modification_fchownat
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27178-3
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000189

    - name: Inserts/replaces the fchownat rule in audit.rules when on x86_64
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
        create: yes
      with_items:
        - "-a always,exit -F arch=b64 -S fchownat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_fchownat
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27178-3
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000189

#### Record Events that Modify the System's Discretionary Access Controls - fremovexattr [ref]rule

At a minimum the audit system should collect file permission changes for all users and root. Add the following to `/etc/audit/audit.rules`:

    -a always,exit -F arch=b32 -S fremovexattr -F auid>=500 -F auid!=4294967295 -k perm_mod

If the system is 64 bit then also add the following:

    -a always,exit -F arch=b64 -S fremovexattr -F auid>=500 -F auid!=4294967295 -k perm_mod

Rationale:

The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.

Severity: unknown

References: CCI-000126, AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064, SV-50357r3_rule

Remediation Shell script: (show)

    # Perform the remediation for 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
    	PATTERN="-a always,exit .* -F auid>=500 -F auid!=4294967295 -k *"
    	GROUP="xattr"
    	FULL_RULE="-a always,exit -F arch=${ARCH} -S setxattr -S lsetxattr -S fsetxattr -S removexattr -S lremovexattr -S fremovexattr -F auid>=500 -F auid!=4294967295 -k perm_mod"
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # 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
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict

    #
    # What architecture are we on?
    #
    - name: Set architecture for audit fremovexattr tasks
      set_fact:
        audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"

    #
    # Inserts/replaces the rule in /etc/audit/rules.d
    #
    - name: Search /etc/audit/rules.d for other DAC audit rules
      find:
        paths: "/etc/audit/rules.d"
        recurse: no
        contains: "-F key=perm_mod$"
        patterns: "*.rules"
      register: find_fremovexattr

    - name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/privileged.rules
      when: find_fremovexattr.matched == 0

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - "{{ find_fremovexattr.files | map(attribute='path') | list | first }}"
      when: find_fremovexattr.matched > 0

    - name: Inserts/replaces the fremovexattr rule in rules.d when on x86
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b32 -S fremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      tags:
        - audit_rules_dac_modification_fremovexattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27179-1
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000190

    - name: Inserts/replaces the fremovexattr rule in rules.d when on x86_64
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b64 -S fremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_fremovexattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27179-1
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000190
    #
    # Inserts/replaces the rule in /etc/audit/audit.rules
    #
    - name: Inserts/replaces the fremovexattr rule in /etc/audit/audit.rules when on x86
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
      with_items:
        - "-a always,exit -F arch=b32 -S fremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      tags:
        - audit_rules_dac_modification_fremovexattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27179-1
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000190

    - name: Inserts/replaces the fremovexattr rule in audit.rules when on x86_64
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
        create: yes
      with_items:
        - "-a always,exit -F arch=b64 -S fremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_fremovexattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27179-1
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000190

#### Record Events that Modify the System's Discretionary Access Controls - lremovexattr [ref]rule

At a minimum the audit system should collect file permission changes for all users and root. Add the following to `/etc/audit/audit.rules`:

    -a always,exit -F arch=b32 -S lremovexattr -F auid>=500 -F auid!=4294967295 -k perm_mod

If the system is 64 bit then also add the following:

    -a always,exit -F arch=b64 -S lremovexattr -F auid>=500 -F auid!=4294967295 -k perm_mod

Rationale:

The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.

Severity: unknown

References: CCI-000126, AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064, SV-50360r3_rule

Remediation Shell script: (show)

    # Perform the remediation for 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
    	PATTERN="-a always,exit .* -F auid>=500 -F auid!=4294967295 -k *"
    	GROUP="xattr"
    	FULL_RULE="-a always,exit -F arch=${ARCH} -S setxattr -S lsetxattr -S fsetxattr -S removexattr -S lremovexattr -S fremovexattr -F auid>=500 -F auid!=4294967295 -k perm_mod"
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # 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
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict

    #
    # What architecture are we on?
    #
    - name: Set architecture for audit lremovexattr tasks
      set_fact:
        audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"

    #
    # Inserts/replaces the rule in /etc/audit/rules.d
    #
    - name: Search /etc/audit/rules.d for other DAC audit rules
      find:
        paths: "/etc/audit/rules.d"
        recurse: no
        contains: "-F key=perm_mod$"
        patterns: "*.rules"
      register: find_lremovexattr

    - name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/privileged.rules
      when: find_lremovexattr.matched == 0

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - "{{ find_lremovexattr.files | map(attribute='path') | list | first }}"
      when: find_lremovexattr.matched > 0

    - name: Inserts/replaces the lremovexattr rule in rules.d when on x86
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b32 -S lremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      tags:
        - audit_rules_dac_modification_lremovexattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27182-5
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000193

    - name: Inserts/replaces the lremovexattr rule in rules.d when on x86_64
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b64 -S lremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_lremovexattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27182-5
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000193
    #
    # Inserts/replaces the rule in /etc/audit/audit.rules
    #
    - name: Inserts/replaces the lremovexattr rule in /etc/audit/audit.rules when on x86
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
      with_items:
        - "-a always,exit -F arch=b32 -S lremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      tags:
        - audit_rules_dac_modification_lremovexattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27182-5
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000193

    - name: Inserts/replaces the lremovexattr rule in audit.rules when on x86_64
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
        create: yes
      with_items:
        - "-a always,exit -F arch=b64 -S lremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_lremovexattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27182-5
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000193

#### Record Events that Modify the System's Discretionary Access Controls - fsetxattr [ref]rule

At a minimum the audit system should collect file permission changes for all users and root. Add the following to `/etc/audit/audit.rules`:

    -a always,exit -F arch=b32 -S fsetxattr -F auid>=500 -F auid!=4294967295 -k perm_mod

If the system is 64 bit then also add the following:

    -a always,exit -F arch=b64 -S fsetxattr -F auid>=500 -F auid!=4294967295 -k perm_mod

Rationale:

The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.

Severity: unknown

References: CCI-000126, AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064, SV-50358r3_rule

Remediation Shell script: (show)

    # Perform the remediation for 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
    	PATTERN="-a always,exit .* -F auid>=500 -F auid!=4294967295 -k *"
    	GROUP="xattr"
    	FULL_RULE="-a always,exit -F arch=${ARCH} -S setxattr -S lsetxattr -S fsetxattr -S removexattr -S lremovexattr -S fremovexattr -F auid>=500 -F auid!=4294967295 -k perm_mod"
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # 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
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict

    #
    # What architecture are we on?
    #
    - name: Set architecture for audit fsetxattr tasks
      set_fact:
        audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"

    #
    # Inserts/replaces the rule in /etc/audit/rules.d
    #
    - name: Search /etc/audit/rules.d for other DAC audit rules
      find:
        paths: "/etc/audit/rules.d"
        recurse: no
        contains: "-F key=perm_mod$"
        patterns: "*.rules"
      register: find_fsetxattr

    - name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/privileged.rules
      when: find_fsetxattr.matched == 0

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - "{{ find_fsetxattr.files | map(attribute='path') | list | first }}"
      when: find_fsetxattr.matched > 0

    - name: Inserts/replaces the fsetxattr rule in rules.d when on x86
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b32 -S fsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      tags:
        - audit_rules_dac_modification_fsetxattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27180-9
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000191

    - name: Inserts/replaces the fsetxattr rule in rules.d when on x86_64
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b64 -S fsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_fsetxattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27180-9
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000191
    #
    # Inserts/replaces the rule in /etc/audit/audit.rules
    #
    - name: Inserts/replaces the fsetxattr rule in /etc/audit/audit.rules when on x86
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
      with_items:
        - "-a always,exit -F arch=b32 -S fsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      tags:
        - audit_rules_dac_modification_fsetxattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27180-9
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000191

    - name: Inserts/replaces the fsetxattr rule in audit.rules when on x86_64
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
        create: yes
      with_items:
        - "-a always,exit -F arch=b64 -S fsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_fsetxattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27180-9
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000191

#### Record Events that Modify the System's Discretionary Access Controls - fchmodat [ref]rule

At a minimum the audit system should collect file permission changes for all users and root. Add the following to `/etc/audit/audit.rules`:

    -a always,exit -F arch=b32 -S fchmodat -F auid>=500 -F auid!=4294967295 -k perm_mod

If the system is 64 bit then also add the following:

    -a always,exit -F arch=b64 -S fchmodat -F auid>=500 -F auid!=4294967295 -k perm_mod

Rationale:

The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.

Severity: unknown

References: CCI-000126, AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064, SV-50351r3_rule

Remediation Shell script: (show)

    # Perform the remediation for 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
    	PATTERN="-a always,exit -F arch=$ARCH -S .* -F auid>=500 -F auid!=4294967295 -k *"
    	GROUP="chmod"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S chmod -S fchmod -S fchmodat -F auid>=500 -F auid!=4294967295 -k perm_mod"
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # 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
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict

    #
    # What architecture are we on?
    #
    - name: Set architecture for audit fchmodat tasks
      set_fact:
        audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"

    #
    # Inserts/replaces the rule in /etc/audit/rules.d
    #
    - name: Search /etc/audit/rules.d for other DAC audit rules
      find:
        paths: "/etc/audit/rules.d"
        recurse: no
        contains: "-F key=perm_mod$"
        patterns: "*.rules"
      register: find_fchmodat

    - name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/privileged.rules
      when: find_fchmodat.matched == 0

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - "{{ find_fchmodat.files | map(attribute='path') | list | first }}"
      when: find_fchmodat.matched > 0

    - name: Inserts/replaces the fchmodat rule in rules.d when on x86
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b32 -S fchmodat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      tags:
        - audit_rules_dac_modification_fchmodat
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27175-9
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000187

    - name: Inserts/replaces the fchmodat rule in rules.d when on x86_64
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b64 -S fchmodat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_fchmodat
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27175-9
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000187
    #
    # Inserts/replaces the rule in /etc/audit/audit.rules
    #
    - name: Inserts/replaces the fchmodat rule in /etc/audit/audit.rules when on x86
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
      with_items:
        - "-a always,exit -F arch=b32 -S fchmodat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      tags:
        - audit_rules_dac_modification_fchmodat
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27175-9
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000187

    - name: Inserts/replaces the fchmodat rule in audit.rules when on x86_64
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
        create: yes
      with_items:
        - "-a always,exit -F arch=b64 -S fchmodat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_fchmodat
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27175-9
        - NIST-800-53-AC-3(10)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.5.5
        - DISA-STIG-RHEL-06-000187

### Records Events that Modify Date and Time Information [ref]group

Arbitrary changes to the system time can be used to obfuscate nefarious activities in log files, as well as to confuse network services that are highly dependent upon an accurate system time. All changes to the system time should be audited.

contains 5 rules

#### Record Attempts to Alter Time Through stime [ref]rule

Add the following line to `/etc/audit/audit.rules` for both 32-bit and 64-bit systems:

    # audit_time_rules
    -a always,exit -F arch=b32 -S stime -k audit_time_rules

Since the 64-bit version of the "stime" system call is not defined in the audit lookup table, the corresponding "-F arch=b64" form of this rule is not expected to be defined on 64-bit systems (the aforementioned "-F arch=b32" stime rule form itself is sufficient for both 32-bit and 64-bit systems). The -k option allows for the specification of a key in string form that can be used for better reporting capability through ausearch and aureport. Multiple system calls can be defined on the same line to save space if desired, but is not required. See an example of multiple combined syscalls:

    -a always,exit -F arch=b64 -S adjtimex -S settimeofday -k audit_time_rules

Rationale:

Arbitrary changes to the system time can be used to obfuscate nefarious activities in log files, as well as to confuse network services that are highly dependent upon an accurate system time (such as sshd). All changes to the system time should be audited.

Severity: unknown

References: CCI-001487, CCI-000169, AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.4.2.b, SRG-OS-000062, SV-50326r4_rule

Remediation Shell script: (show)

    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # 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
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    # Perform the remediation for the 'adjtimex', 'settimeofday', and 'stime' audit
    # system calls on Red Hat Enterprise Linux 6 OS
    function rhel6_perform_audit_adjtimex_settimeofday_stime_remediation {

    # Retrieve hardware architecture of the underlying system
    [ $(getconf LONG_BIT) = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
    	PATTERN="-a always,exit -F arch=${ARCH} -S .* -k *"
    	# Create expected audit group and audit rule form for particular system call & architecture
    	if [ ${ARCH} = "b32" ]
    	then
    		# stime system call is known at 32-bit arch (see e.g "$ ausyscall i386 stime" 's output)
    		# so append it to the list of time group system calls to be audited
    		GROUP="\(adjtimex\|settimeofday\|stime\)"
    		FULL_RULE="-a always,exit -F arch=${ARCH} -S adjtimex -S settimeofday -S stime -k audit_time_rules"
    	elif [ ${ARCH} = "b64" ]
    	then
    		# stime system call isn't known at 64-bit arch (see "$ ausyscall x86_64 stime" 's output)
    		# therefore don't add it to the list of time group system calls to be audited
    		GROUP="\(adjtimex\|settimeofday\)"
    		FULL_RULE="-a always,exit -F arch=${ARCH} -S adjtimex -S settimeofday -k audit_time_rules"
    	fi
    	# Perform the remediation itself
    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

    }

    rhel6_perform_audit_adjtimex_settimeofday_stime_remediation

#### Record attempts to alter time through settimeofday [ref]rule

On a 32-bit system, add the following to `/etc/audit/audit.rules`:

    # audit_time_rules
    -a always,exit -F arch=b32 -S settimeofday -k audit_time_rules

On a 64-bit system, add the following to `/etc/audit/audit.rules`:

    # audit_time_rules
    -a always,exit -F arch=b64 -S settimeofday -k audit_time_rules

The -k option allows for the specification of a key in string form that can be used for better reporting capability through ausearch and aureport. Multiple system calls can be defined on the same line to save space if desired, but is not required. See an example of multiple combined syscalls:

    -a always,exit -F arch=b64 -S adjtimex -S settimeofday -k audit_time_rules

Rationale:

Arbitrary changes to the system time can be used to obfuscate nefarious activities in log files, as well as to confuse network services that are highly dependent upon an accurate system time (such as sshd). All changes to the system time should be audited.

Severity: unknown

References: CCI-001487, CCI-000169, AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.4.2.b, SRG-OS-000062, SV-50323r3_rule

Remediation Shell script: (show)

    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # 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
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    # Perform the remediation for the 'adjtimex', 'settimeofday', and 'stime' audit
    # system calls on Red Hat Enterprise Linux 6 OS
    function rhel6_perform_audit_adjtimex_settimeofday_stime_remediation {

    # Retrieve hardware architecture of the underlying system
    [ $(getconf LONG_BIT) = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
    	PATTERN="-a always,exit -F arch=${ARCH} -S .* -k *"
    	# Create expected audit group and audit rule form for particular system call & architecture
    	if [ ${ARCH} = "b32" ]
    	then
    		# stime system call is known at 32-bit arch (see e.g "$ ausyscall i386 stime" 's output)
    		# so append it to the list of time group system calls to be audited
    		GROUP="\(adjtimex\|settimeofday\|stime\)"
    		FULL_RULE="-a always,exit -F arch=${ARCH} -S adjtimex -S settimeofday -S stime -k audit_time_rules"
    	elif [ ${ARCH} = "b64" ]
    	then
    		# stime system call isn't known at 64-bit arch (see "$ ausyscall x86_64 stime" 's output)
    		# therefore don't add it to the list of time group system calls to be audited
    		GROUP="\(adjtimex\|settimeofday\)"
    		FULL_RULE="-a always,exit -F arch=${ARCH} -S adjtimex -S settimeofday -k audit_time_rules"
    	fi
    	# Perform the remediation itself
    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

    }

    rhel6_perform_audit_adjtimex_settimeofday_stime_remediation

#### Record Attempts to Alter the localtime File [ref]rule

Add the following to `/etc/audit/audit.rules`:

    -w /etc/localtime -p wa -k audit_time_rules

The -k option allows for the specification of a key in string form that can be used for better reporting capability through ausearch and aureport and should always be used.Rationale:

Arbitrary changes to the system time can be used to obfuscate nefarious activities in log files, as well as to confuse network services that are highly dependent upon an accurate system time (such as sshd). All changes to the system time should be audited.

Severity: unknown

References: CCI-001487, CCI-000169, AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.4.2.b, SRG-OS-000062, SV-50331r2_rule

Remediation Shell script: (show)

    # Perform the remediation
    # Function to fix audit file system object watch rule for given path:
    # * if rule exists, also verifies the -w bits match the requirements
    # * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
    #   audit rules file, depending on the tool which was used to load audit rules
    #
    # Expects four arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules'
    # * path                        	value of -w audit rule's argument
    # * required access bits        	value of -p audit rule's argument
    # * key                         	value of -k audit rule's argument
    #
    # Example call:
    #
    #       fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    #
    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
    	echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # 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/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
    	# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
    	# Get pair -- filepath : matching_row into @matches array
    	IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
    	# Reset IFS back to default
    	unset IFS
    	# 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=("${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/$key.rules' into list of files for inspection
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		# If the $key.rules file doesn't exist yet, create it with correct permissions
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    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]+$path" "$audit_rules_file"
    	then
    		# Rule is found => verify yet if existing rule definition contains
    		# all of the required access type bits

    		# Escape slashes in path for use in sed pattern below
    		local esc_path=${path//$'/'/$'\/'}
    		# Define BRE whitespace class shortcut
    		local 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\+$esc_path$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 "$required_access_bits" | 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\+$esc_path$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 $path -p $required_access_bits -k $key" >> "$audit_rules_file"
    	fi
    done
    }

    fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"

#### Record Attempts to Alter Time Through clock_settime [ref]rule

On a 32-bit system, add the following to `/etc/audit/audit.rules`:

    # time-change
    -a always,exit -F arch=b32 -S clock_settime -F a0=0x0 -F key=time-change

On a 64-bit system, add the following to `/etc/audit/audit.rules`:

    # time-change
    -a always,exit -F arch=b64 -S clock_settime -F a0=0x0 -F key=time-change

The -k option allows for the specification of a key in string form that can be used for better reporting capability through ausearch and aureport. Multiple system calls can be defined on the same line to save space if desired, but is not required. See an example of multiple combined syscalls:

    -a always,exit -F arch=b64 -S adjtimex -S settimeofday -k audit_time_rules

Rationale:

Arbitrary changes to the system time can be used to obfuscate nefarious activities in log files, as well as to confuse network services that are highly dependent upon an accurate system time (such as sshd). All changes to the system time should be audited.

Severity: unknown

References: CCI-001487, CCI-000169, AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.4.2.b, SRG-OS-000062, SV-50328r3_rule

Remediation Shell script: (show)

    # 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
    	PATTERN="-a always,exit -F arch=$ARCH -S clock_settime -F a0=.* \(-F key=\|-k \).*"
    	GROUP="clock_settime"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S clock_settime -F a0=0x0 -k time-change"
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # 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
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

#### Record attempts to alter time through adjtimex [ref]rule

On a 32-bit system, add the following to `/etc/audit/audit.rules`:

    # audit_time_rules
    -a always,exit -F arch=b32 -S adjtimex -k audit_time_rules

On a 64-bit system, add the following to `/etc/audit/audit.rules`:

    # audit_time_rules
    -a always,exit -F arch=b64 -S adjtimex -k audit_time_rules

The -k option allows for the specification of a key in string form that can be used for better reporting capability through ausearch and aureport. Multiple system calls can be defined on the same line to save space if desired, but is not required. See an example of multiple combined syscalls:

    -a always,exit -F arch=b64 -S adjtimex -S settimeofday -k audit_time_rules

Rationale:

Arbitrary changes to the system time can be used to obfuscate nefarious activities in log files, as well as to confuse network services that are highly dependent upon an accurate system time (such as sshd). All changes to the system time should be audited.

Severity: unknown

References: CCI-001487, CCI-000169, AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.4.2.b, SRG-OS-000062

Remediation Shell script: (show)

    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # 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
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    # Perform the remediation for the 'adjtimex', 'settimeofday', and 'stime' audit
    # system calls on Red Hat Enterprise Linux 6 OS
    function rhel6_perform_audit_adjtimex_settimeofday_stime_remediation {

    # Retrieve hardware architecture of the underlying system
    [ $(getconf LONG_BIT) = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
    	PATTERN="-a always,exit -F arch=${ARCH} -S .* -k *"
    	# Create expected audit group and audit rule form for particular system call & architecture
    	if [ ${ARCH} = "b32" ]
    	then
    		# stime system call is known at 32-bit arch (see e.g "$ ausyscall i386 stime" 's output)
    		# so append it to the list of time group system calls to be audited
    		GROUP="\(adjtimex\|settimeofday\|stime\)"
    		FULL_RULE="-a always,exit -F arch=${ARCH} -S adjtimex -S settimeofday -S stime -k audit_time_rules"
    	elif [ ${ARCH} = "b64" ]
    	then
    		# stime system call isn't known at 64-bit arch (see "$ ausyscall x86_64 stime" 's output)
    		# therefore don't add it to the list of time group system calls to be audited
    		GROUP="\(adjtimex\|settimeofday\)"
    		FULL_RULE="-a always,exit -F arch=${ARCH} -S adjtimex -S settimeofday -k audit_time_rules"
    	fi
    	# Perform the remediation itself
    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

    }

    rhel6_perform_audit_adjtimex_settimeofday_stime_remediation

#### Ensure auditd Collects Information on Kernel Module Loading and Unloading [ref]rule

Add the following to `/etc/audit/audit.rules` in order to capture kernel module loading and unloading events, setting ARCH to either b32 or b64 as appropriate for your system:

    -w /sbin/insmod -p x -k modules
    -w /sbin/rmmod -p x -k modules
    -w /sbin/modprobe -p x -k modules
    -a always,exit -F arch=ARCH -S init_module -S delete_module -k 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.

Severity: unknown

References: CCI-000126, AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.7, SRG-OS-000064, SV-50381r2_rule

Remediation Shell script: (show)

    # First perform the remediation of the syscall rule
    # Retrieve hardware architecture of the underlying system
    # Note: 32-bit kernel modules can't be loaded / unloaded on 64-bit kernel =>
    #       it's not required on a 64-bit system to check also for the presence
    #       of 32-bit's equivalent of the corresponding rule. Therefore for
    #       each system it's enought to check presence of system's native rule form.
    [ $(getconf LONG_BIT) = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
    	PATTERN="-a always,exit -F arch=$ARCH -S .* -k *"
    	# Use escaped BRE regex to specify rule group
    	GROUP="\(init\|delete\)_module"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S init_module -S delete_module -k modules"
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # 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
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

    # Then perform the remediations for the watch rules
    # Function to fix audit file system object watch rule for given path:
    # * if rule exists, also verifies the -w bits match the requirements
    # * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
    #   audit rules file, depending on the tool which was used to load audit rules
    #
    # Expects four arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules'
    # * path                        	value of -w audit rule's argument
    # * required access bits        	value of -p audit rule's argument
    # * key                         	value of -k audit rule's argument
    #
    # Example call:
    #
    #       fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    #
    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
    	echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # 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/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
    	# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
    	# Get pair -- filepath : matching_row into @matches array
    	IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
    	# Reset IFS back to default
    	unset IFS
    	# 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=("${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/$key.rules' into list of files for inspection
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		# If the $key.rules file doesn't exist yet, create it with correct permissions
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    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]+$path" "$audit_rules_file"
    	then
    		# Rule is found => verify yet if existing rule definition contains
    		# all of the required access type bits

    		# Escape slashes in path for use in sed pattern below
    		local esc_path=${path//$'/'/$'\/'}
    		# Define BRE whitespace class shortcut
    		local 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\+$esc_path$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 "$required_access_bits" | 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\+$esc_path$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 $path -p $required_access_bits -k $key" >> "$audit_rules_file"
    	fi
    done
    }

    fix_audit_watch_rule "auditctl" "/sbin/insmod" "x" "modules"
    fix_audit_watch_rule "auditctl" "/sbin/rmmod" "x" "modules"
    # Function to fix audit file system object watch rule for given path:
    # * if rule exists, also verifies the -w bits match the requirements
    # * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
    #   audit rules file, depending on the tool which was used to load audit rules
    #
    # Expects four arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules'
    # * path                        	value of -w audit rule's argument
    # * required access bits        	value of -p audit rule's argument
    # * key                         	value of -k audit rule's argument
    #
    # Example call:
    #
    #       fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    #
    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
    	echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # 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/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
    	# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
    	# Get pair -- filepath : matching_row into @matches array
    	IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
    	# Reset IFS back to default
    	unset IFS
    	# 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=("${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/$key.rules' into list of files for inspection
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		# If the $key.rules file doesn't exist yet, create it with correct permissions
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    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]+$path" "$audit_rules_file"
    	then
    		# Rule is found => verify yet if existing rule definition contains
    		# all of the required access type bits

    		# Escape slashes in path for use in sed pattern below
    		local esc_path=${path//$'/'/$'\/'}
    		# Define BRE whitespace class shortcut
    		local 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\+$esc_path$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 "$required_access_bits" | 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\+$esc_path$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 $path -p $required_access_bits -k $key" >> "$audit_rules_file"
    	fi
    done
    }

    fix_audit_watch_rule "auditctl" "/sbin/modprobe" "x" "modules"

#### Ensure auditd Collects Information on the Use of Privileged Commands [ref]rule

At a minimum the audit system should collect the execution of privileged commands for all users and root. To find the relevant setuid / setgid programs, run the following command for each local partition PART:

    $ sudo find PART -xdev -type f -perm -4000 -o -type f -perm -2000 2>/dev/null

Then, for each setuid / setgid program on the system, add a line of the following form to `/etc/audit/audit.rules`, where SETUID_PROG_PATH is the full path to each setuid / setgid program in the list:

    -a always,exit -F path=SETUID_PROG_PATH -F perm=x -F auid>=500 -F auid!=4294967295 -k privileged

Rationale:

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.

Severity: unknown

References: CCI-000040, AC-3(10)), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AC-6(9), AU-12(a), AU-12(c), IR-5, Req-10.2.2, SRG-OS-000020, SV-50368r4_rule

Remediation Shell script: (show)

    # Perform the remediation
    # Function to perform remediation for 'audit_rules_privileged_commands' rule
    #
    # Expects two arguments:
    #
    # audit_tool		tool used to load audit rules
    # 			One of 'auditctl' or 'augenrules'
    #
    # min_auid		Minimum original ID the user logged in with
    # 			'500' for RHEL-6 and before, '1000' for RHEL-7 and after.
    #
    # Example Call(s):
    #
    #      perform_audit_rules_privileged_commands_remediation "auditctl" "500"
    #      perform_audit_rules_privileged_commands_remediation "augenrules"	"1000"
    #
    function perform_audit_rules_privileged_commands_remediation {
    #
    # Load function arguments into local variables
    local tool="$1"
    local min_auid="$2"

    # Check sanity of the input
    if [ $# -ne "2" ]
    then
    	echo "Usage: perform_audit_rules_privileged_commands_remediation 'auditctl | augenrules' '500 | 1000'"
    	echo "Aborting."
    	exit 1
    fi

    declare -a files_to_inspect=()

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then:
    # * add '/etc/audit/audit.rules'to the list of files to be inspected,
    # * specify '/etc/audit/audit.rules' as the output audit file, where
    #   missing rules should be inserted
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("/etc/audit/audit.rules")
    	output_audit_file="/etc/audit/audit.rules"
    #
    # If the audit tool is 'augenrules', then:
    # * add '/etc/audit/rules.d/*.rules' to the list of files to be inspected
    #   (split by newline),
    # * specify /etc/audit/rules.d/privileged.rules' as the output file, where
    #   missing rules should be inserted
    elif [ "$tool" == 'augenrules' ]
    then
    	IFS=$'\n' files_to_inspect=($(find /etc/audit/rules.d -maxdepth 1 -type f -name '*.rules' -print))
    	output_audit_file="/etc/audit/rules.d/privileged.rules"
    fi

    # Obtain the list of SUID/SGID binaries on the particular system (split by newline)
    # into privileged_binaries array
    IFS=$'\n' privileged_binaries=($(find / -xdev -type f -perm -4000 -o -type f -perm -2000 2>/dev/null))

    # Keep list of SUID/SGID binaries that have been already handled within some previous iteration
    declare -a sbinaries_to_skip=()

    # For each found sbinary in privileged_binaries list
    for sbinary in "${privileged_binaries[@]}"
    do

    	# Check if this sbinary wasn't already handled in some of the previous iterations
    	# Return match only if whole sbinary definition matched (not in the case just prefix matched!!!)
    	if [[ $(sed -ne "\|${sbinary}|p" <<< "${sbinaries_to_skip[*]}") ]]
    	then
    		# If so, don't process it second time & go to process next sbinary
    		continue
    	fi

    	# Reset the counter of inspected files when starting to check
    	# presence of existing audit rule for new sbinary
    	local count_of_inspected_files=0

    	# Define expected rule form for this binary
    	expected_rule="-a always,exit -F path=${sbinary} -F perm=x -F auid>=${min_auid} -F auid!=4294967295 -k privileged"

    	# If list of audit rules files to be inspected is empty, just add new rule and move on to next binary
    	if [[ ${#files_to_inspect[@]} -eq 0 ]]; then
    		echo "$expected_rule" >> "$output_audit_file"
    		continue
    	fi

    	# Replace possible slash '/' character in sbinary definition so we could use it in sed expressions below
    	sbinary_esc=${sbinary//$'/'/$'\/'}

    	# For each audit rules file from the list of files to be inspected
    	for afile in "${files_to_inspect[@]}"
    	do

    		# Search current audit rules file's content for match. Match criteria:
    		# * existing rule is for the same SUID/SGID binary we are currently processing (but
    		#   can contain multiple -F path= elements covering multiple SUID/SGID binaries)
    		# * existing rule contains all arguments from expected rule form (though can contain
    		#   them in arbitrary order)

    		base_search=$(sed -e '/-a always,exit/!d' -e '/-F path='"${sbinary_esc}"'/!d' \
    				-e '/-F path=[^[:space:]]\+/!d'   -e '/-F perm=.*/!d'                 \
    				-e '/-F auid>='"${min_auid}"'/!d' -e '/-F auid!=4294967295/!d'        \
    				-e '/-k privileged/!d' "$afile")

    		# Increase the count of inspected files for this sbinary
    		count_of_inspected_files=$((count_of_inspected_files + 1))

    		# Require execute access type to be set for existing audit rule
    		exec_access='x'

    		# Search current audit rules file's content for presence of rule pattern for this sbinary
    		if [[ $base_search ]]
    		then

    			# Current audit rules file already contains rule for this binary =>
    			# Store the exact form of found rule for this binary for further processing
    			concrete_rule=$base_search

    			# Select all other SUID/SGID binaries possibly also present in the found rule
    			IFS=$'\n' handled_sbinaries=($(grep -o -e "-F path=[^[:space:]]\+" <<< "$concrete_rule"))
    			IFS=$' ' handled_sbinaries=(${handled_sbinaries[@]//-F path=/})

    			# Merge the list of such SUID/SGID binaries found in this iteration with global list ignoring duplicates
    			sbinaries_to_skip=($(for i in "${sbinaries_to_skip[@]}" "${handled_sbinaries[@]}"; do echo "$i"; done | sort -du))

    			# Separate concrete_rule into three sections using hash '#'
    			# sign as a delimiter around rule's permission section borders
    			concrete_rule="$(echo "$concrete_rule" | sed -n "s/\(.*\)\+\(-F perm=[rwax]\+\)\+/\1#\2#/p")"

    			# Split concrete_rule into head, perm, and tail sections using hash '#' delimiter
    			IFS=$'#' read -r rule_head rule_perm rule_tail <<<  "$concrete_rule"

    			# Extract already present exact access type [r|w|x|a] from rule's permission section
    			access_type=${rule_perm//-F perm=/}

    			# Verify current permission access type(s) for rule contain 'x' (execute) permission
    			if ! grep -q "$exec_access" <<< "$access_type"
    			then

    				# If not, append the 'x' (execute) permission to the existing access type bits
    				access_type="$access_type$exec_access"
    				# Reconstruct the permissions section for the rule
    				new_rule_perm="-F perm=$access_type"
    				# Update existing rule in current audit rules file with the new permission section
    				sed -i "s#${rule_head}\(.*\)${rule_tail}#${rule_head}${new_rule_perm}${rule_tail}#" "$afile"

    			fi

    		# If the required audit rule for particular sbinary wasn't found yet, insert it under following conditions:
    		#
    		# * in the "auditctl" mode of operation insert particular rule each time
    		#   (because in this mode there's only one file -- /etc/audit/audit.rules to be inspected for presence of this rule),
    		#
    		# * in the "augenrules" mode of operation insert particular rule only once and only in case we have already
    		#   searched all of the files from /etc/audit/rules.d/*.rules location (since that audit rule can be defined
    		#   in any of those files and if not, we want it to be inserted only once into /etc/audit/rules.d/privileged.rules file)
    		#
    		elif [ "$tool" == "auditctl" ] || [[ "$tool" == "augenrules" && $count_of_inspected_files -eq "${#files_to_inspect[@]}" ]]
    		then

    			# Current audit rules file's content doesn't contain expected rule for this
    			# SUID/SGID binary yet => append it
    			echo "$expected_rule" >> "$output_audit_file"
    			continue
    		fi

    	done

    done
    }

    perform_audit_rules_privileged_commands_remediation "auditctl" "500"

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | restrict

    - name: Search for privileged commands
      shell: "find / -xdev -type f -perm -4000 -o -type f -perm -2000 2>/dev/null | cat"
      check_mode: no
      register: find_result
      tags:
        - audit_rules_privileged_commands
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-26457-2
        - NIST-800-53-AC-3(10))
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.2.2
        - DISA-STIG-RHEL-06-000198

    # Inserts/replaces the rule in /etc/audit/rules.d

    - name: Search /etc/audit/rules.d for audit rule entries
      find:
        paths: "/etc/audit/rules.d"
        recurse: no
        contains: "^.*path={{ item }} .*$"
        patterns: "*.rules"
      with_items:
        - "{{ find_result.stdout_lines }}"
      register: files_result
      tags:
        - audit_rules_privileged_commands
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-26457-2
        - NIST-800-53-AC-3(10))
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.2.2
        - DISA-STIG-RHEL-06-000198

    - name: Overwrites the rule in rules.d
      lineinfile:
        path: "{{ item.1.path }}"
        line: '-a always,exit -F path={{ item.0.item }} -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
        create: no
        regexp: "^.*path={{ item.0.item }} .*$"
      with_subelements:
        - "{{ files_result.results }}"
        - files
      tags:
        - audit_rules_privileged_commands
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-26457-2
        - NIST-800-53-AC-3(10))
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.2.2
        - DISA-STIG-RHEL-06-000198

    - name: Adds the rule in rules.d
      lineinfile:
        path: /etc/audit/rules.d/privileged.rules
        line: '-a always,exit -F path={{ item.item }} -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
        create: yes
      with_items:
        - "{{ files_result.results }}"
      when: item.matched == 0
      tags:
        - audit_rules_privileged_commands
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-26457-2
        - NIST-800-53-AC-3(10))
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.2.2
        - DISA-STIG-RHEL-06-000198

    # Adds/overwrites the rule in /etc/audit/audit.rules

    - name: Inserts/replaces the rule in audit.rules
      lineinfile:
        path: /etc/audit/audit.rules
        line: '-a always,exit -F path={{ item.item }} -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
        create: yes
        regexp: "^.*path={{ item.item }} .*$"
      with_items:
        - "{{ files_result.results }}"
      tags:
        - audit_rules_privileged_commands
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-26457-2
        - NIST-800-53-AC-3(10))
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.2.2
        - DISA-STIG-RHEL-06-000198

#### Record Attempts to Alter Login and Logout Events [ref]rule

The audit system already collects login info for all users and root. To watch for attempted manual edits of files involved in storing login events, add the following to `/etc/audit/audit.rules`:

    -w /var/log/tallylog -p wa -k logins
    -w /var/run/faillock/ -p wa -k logins
    -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.

Severity: unknown

References: AC-3(10), AU-1(b), AU-12(a), AU-12(c), IR-5

Remediation Shell script: (show)

    # Perform the remediation
    # Function to fix audit file system object watch rule for given path:
    # * if rule exists, also verifies the -w bits match the requirements
    # * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
    #   audit rules file, depending on the tool which was used to load audit rules
    #
    # Expects four arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules'
    # * path                        	value of -w audit rule's argument
    # * required access bits        	value of -p audit rule's argument
    # * key                         	value of -k audit rule's argument
    #
    # Example call:
    #
    #       fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    #
    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
    	echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # 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/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
    	# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
    	# Get pair -- filepath : matching_row into @matches array
    	IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
    	# Reset IFS back to default
    	unset IFS
    	# 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=("${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/$key.rules' into list of files for inspection
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		# If the $key.rules file doesn't exist yet, create it with correct permissions
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    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]+$path" "$audit_rules_file"
    	then
    		# Rule is found => verify yet if existing rule definition contains
    		# all of the required access type bits

    		# Escape slashes in path for use in sed pattern below
    		local esc_path=${path//$'/'/$'\/'}
    		# Define BRE whitespace class shortcut
    		local 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\+$esc_path$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 "$required_access_bits" | 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\+$esc_path$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 $path -p $required_access_bits -k $key" >> "$audit_rules_file"
    	fi
    done
    }

    fix_audit_watch_rule "auditctl" "/var/log/tallylog" "wa" "logins"
    fix_audit_watch_rule "auditctl" "/var/run/faillock/" "wa" "logins"
    # Function to fix audit file system object watch rule for given path:
    # * if rule exists, also verifies the -w bits match the requirements
    # * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
    #   audit rules file, depending on the tool which was used to load audit rules
    #
    # Expects four arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules'
    # * path                        	value of -w audit rule's argument
    # * required access bits        	value of -p audit rule's argument
    # * key                         	value of -k audit rule's argument
    #
    # Example call:
    #
    #       fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    #
    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
    	echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # 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/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
    	# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
    	# Get pair -- filepath : matching_row into @matches array
    	IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
    	# Reset IFS back to default
    	unset IFS
    	# 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=("${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/$key.rules' into list of files for inspection
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		# If the $key.rules file doesn't exist yet, create it with correct permissions
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    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]+$path" "$audit_rules_file"
    	then
    		# Rule is found => verify yet if existing rule definition contains
    		# all of the required access type bits

    		# Escape slashes in path for use in sed pattern below
    		local esc_path=${path//$'/'/$'\/'}
    		# Define BRE whitespace class shortcut
    		local 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\+$esc_path$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 "$required_access_bits" | 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\+$esc_path$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 $path -p $required_access_bits -k $key" >> "$audit_rules_file"
    	fi
    done
    }

    fix_audit_watch_rule "auditctl" "/var/log/lastlog" "wa" "logins"

#### Ensure auditd Collects System Administrator Actions [ref]rule

At a minimum the audit system should collect administrator actions for all users and root. Add the following to `/etc/audit/audit.rules`:

    -w /etc/sudoers -p wa -k actions

Rationale:

The actions taken by system administrators should be audited to keep a record of what was executed on the system, as well as, for accountability purposes.

Severity: unknown

References: CCI-000126, AC-2(7)(b), AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.2, Req-10.2.5.b, SRG-OS-000064, SV-50379r2_rule

Remediation Shell script: (show)

    # Perform the remediation
    # Function to fix audit file system object watch rule for given path:
    # * if rule exists, also verifies the -w bits match the requirements
    # * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
    #   audit rules file, depending on the tool which was used to load audit rules
    #
    # Expects four arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules'
    # * path                        	value of -w audit rule's argument
    # * required access bits        	value of -p audit rule's argument
    # * key                         	value of -k audit rule's argument
    #
    # Example call:
    #
    #       fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    #
    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
    	echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # 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/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
    	# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
    	# Get pair -- filepath : matching_row into @matches array
    	IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
    	# Reset IFS back to default
    	unset IFS
    	# 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=("${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/$key.rules' into list of files for inspection
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		# If the $key.rules file doesn't exist yet, create it with correct permissions
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    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]+$path" "$audit_rules_file"
    	then
    		# Rule is found => verify yet if existing rule definition contains
    		# all of the required access type bits

    		# Escape slashes in path for use in sed pattern below
    		local esc_path=${path//$'/'/$'\/'}
    		# Define BRE whitespace class shortcut
    		local 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\+$esc_path$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 "$required_access_bits" | 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\+$esc_path$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 $path -p $required_access_bits -k $key" >> "$audit_rules_file"
    	fi
    done
    }

    fix_audit_watch_rule "auditctl" "/etc/sudoers" "wa" "actions"

#### Record Events that Modify the System's Network Environment [ref]rule

Add the following to `/etc/audit/audit.rules`, setting ARCH to either b32 or b64 as appropriate for your system:

    # audit_rules_networkconfig_modification
    -a always,exit -F arch=ARCH -S sethostname -S setdomainname -k audit_rules_networkconfig_modification
    -w /etc/issue -p wa -k audit_rules_networkconfig_modification
    -w /etc/issue.net -p wa -k audit_rules_networkconfig_modification
    -w /etc/hosts -p wa -k audit_rules_networkconfig_modification
    -w /etc/sysconfig/network -p wa -k audit_rules_networkconfig_modification

Rationale:

The network environment should not be modified by anything other than administrator action. Any change to network parameters should be audited.

Severity: unknown

References: AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-999999, SV-50341r4_rule

Remediation Shell script: (show)

    # 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
    	PATTERN="-a always,exit -F arch=$ARCH -S .* -k *"
    	# Use escaped BRE regex to specify rule group
    	GROUP="set\(host\|domain\)name"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S sethostname -S setdomainname -k audit_rules_networkconfig_modification"
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # 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
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

    # Then perform the remediations for the watch rules
    # Function to fix audit file system object watch rule for given path:
    # * if rule exists, also verifies the -w bits match the requirements
    # * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
    #   audit rules file, depending on the tool which was used to load audit rules
    #
    # Expects four arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules'
    # * path                        	value of -w audit rule's argument
    # * required access bits        	value of -p audit rule's argument
    # * key                         	value of -k audit rule's argument
    #
    # Example call:
    #
    #       fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    #
    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
    	echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # 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/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
    	# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
    	# Get pair -- filepath : matching_row into @matches array
    	IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
    	# Reset IFS back to default
    	unset IFS
    	# 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=("${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/$key.rules' into list of files for inspection
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		# If the $key.rules file doesn't exist yet, create it with correct permissions
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    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]+$path" "$audit_rules_file"
    	then
    		# Rule is found => verify yet if existing rule definition contains
    		# all of the required access type bits

    		# Escape slashes in path for use in sed pattern below
    		local esc_path=${path//$'/'/$'\/'}
    		# Define BRE whitespace class shortcut
    		local 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\+$esc_path$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 "$required_access_bits" | 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\+$esc_path$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 $path -p $required_access_bits -k $key" >> "$audit_rules_file"
    	fi
    done
    }

    fix_audit_watch_rule "auditctl" "/etc/issue" "wa" "audit_rules_networkconfig_modification"
    fix_audit_watch_rule "auditctl" "/etc/issue.net" "wa" "audit_rules_networkconfig_modification"
    # Function to fix audit file system object watch rule for given path:
    # * if rule exists, also verifies the -w bits match the requirements
    # * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
    #   audit rules file, depending on the tool which was used to load audit rules
    #
    # Expects four arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules'
    # * path                        	value of -w audit rule's argument
    # * required access bits        	value of -p audit rule's argument
    # * key                         	value of -k audit rule's argument
    #
    # Example call:
    #
    #       fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    #
    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
    	echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # 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/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
    	# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
    	# Get pair -- filepath : matching_row into @matches array
    	IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
    	# Reset IFS back to default
    	unset IFS
    	# 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=("${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/$key.rules' into list of files for inspection
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		# If the $key.rules file doesn't exist yet, create it with correct permissions
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    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]+$path" "$audit_rules_file"
    	then
    		# Rule is found => verify yet if existing rule definition contains
    		# all of the required access type bits

    		# Escape slashes in path for use in sed pattern below
    		local esc_path=${path//$'/'/$'\/'}
    		# Define BRE whitespace class shortcut
    		local 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\+$esc_path$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 "$required_access_bits" | 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\+$esc_path$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 $path -p $required_access_bits -k $key" >> "$audit_rules_file"
    	fi
    done
    }

    fix_audit_watch_rule "auditctl" "/etc/hosts" "wa" "audit_rules_networkconfig_modification"
    fix_audit_watch_rule "auditctl" "/etc/sysconfig/network" "wa" "audit_rules_networkconfig_modification"

#### Record Events that Modify User/Group Information [ref]rule

Add the following to `/etc/audit/audit.rules`, in order to capture events that modify account changes:

    # audit_rules_usergroup_modification
    -w /etc/group -p wa -k audit_rules_usergroup_modification
    -w /etc/passwd -p wa -k audit_rules_usergroup_modification
    -w /etc/gshadow -p wa -k audit_rules_usergroup_modification
    -w /etc/shadow -p wa -k audit_rules_usergroup_modification
    -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.

Severity: unknown

References: CCI-000018, CCI-001403, CCI-001404, CCI-001405, CCI-001684, CCI-001683, CCI-001685, CCI-001686, AC-2(4), AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.5, SRG-OS-000004, SRG-OS-000239, SRG-OS-000240, SRG-OS-000241, SV-50332r2_rule

Remediation Shell script: (show)

    # Perform the remediation
    # Function to fix audit file system object watch rule for given path:
    # * if rule exists, also verifies the -w bits match the requirements
    # * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
    #   audit rules file, depending on the tool which was used to load audit rules
    #
    # Expects four arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules'
    # * path                        	value of -w audit rule's argument
    # * required access bits        	value of -p audit rule's argument
    # * key                         	value of -k audit rule's argument
    #
    # Example call:
    #
    #       fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    #
    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
    	echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # 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/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
    	# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
    	# Get pair -- filepath : matching_row into @matches array
    	IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
    	# Reset IFS back to default
    	unset IFS
    	# 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=("${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/$key.rules' into list of files for inspection
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		# If the $key.rules file doesn't exist yet, create it with correct permissions
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    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]+$path" "$audit_rules_file"
    	then
    		# Rule is found => verify yet if existing rule definition contains
    		# all of the required access type bits

    		# Escape slashes in path for use in sed pattern below
    		local esc_path=${path//$'/'/$'\/'}
    		# Define BRE whitespace class shortcut
    		local 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\+$esc_path$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 "$required_access_bits" | 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\+$esc_path$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 $path -p $required_access_bits -k $key" >> "$audit_rules_file"
    	fi
    done
    }

    fix_audit_watch_rule "auditctl" "/etc/group" "wa" "audit_rules_usergroup_modification"
    fix_audit_watch_rule "auditctl" "/etc/passwd" "wa" "audit_rules_usergroup_modification"
    # Function to fix audit file system object watch rule for given path:
    # * if rule exists, also verifies the -w bits match the requirements
    # * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
    #   audit rules file, depending on the tool which was used to load audit rules
    #
    # Expects four arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules'
    # * path                        	value of -w audit rule's argument
    # * required access bits        	value of -p audit rule's argument
    # * key                         	value of -k audit rule's argument
    #
    # Example call:
    #
    #       fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    #
    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
    	echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # 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/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
    	# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
    	# Get pair -- filepath : matching_row into @matches array
    	IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
    	# Reset IFS back to default
    	unset IFS
    	# 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=("${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/$key.rules' into list of files for inspection
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		# If the $key.rules file doesn't exist yet, create it with correct permissions
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    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]+$path" "$audit_rules_file"
    	then
    		# Rule is found => verify yet if existing rule definition contains
    		# all of the required access type bits

    		# Escape slashes in path for use in sed pattern below
    		local esc_path=${path//$'/'/$'\/'}
    		# Define BRE whitespace class shortcut
    		local 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\+$esc_path$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 "$required_access_bits" | 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\+$esc_path$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 $path -p $required_access_bits -k $key" >> "$audit_rules_file"
    	fi
    done
    }

    fix_audit_watch_rule "auditctl" "/etc/gshadow" "wa" "audit_rules_usergroup_modification"
    fix_audit_watch_rule "auditctl" "/etc/shadow" "wa" "audit_rules_usergroup_modification"
    # Function to fix audit file system object watch rule for given path:
    # * if rule exists, also verifies the -w bits match the requirements
    # * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
    #   audit rules file, depending on the tool which was used to load audit rules
    #
    # Expects four arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules'
    # * path                        	value of -w audit rule's argument
    # * required access bits        	value of -p audit rule's argument
    # * key                         	value of -k audit rule's argument
    #
    # Example call:
    #
    #       fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    #
    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
    	echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # 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/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
    	# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
    	# Get pair -- filepath : matching_row into @matches array
    	IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
    	# Reset IFS back to default
    	unset IFS
    	# 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=("${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/$key.rules' into list of files for inspection
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		# If the $key.rules file doesn't exist yet, create it with correct permissions
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    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]+$path" "$audit_rules_file"
    	then
    		# Rule is found => verify yet if existing rule definition contains
    		# all of the required access type bits

    		# Escape slashes in path for use in sed pattern below
    		local esc_path=${path//$'/'/$'\/'}
    		# Define BRE whitespace class shortcut
    		local 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\+$esc_path$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 "$required_access_bits" | 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\+$esc_path$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 $path -p $required_access_bits -k $key" >> "$audit_rules_file"
    	fi
    done
    }

    fix_audit_watch_rule "auditctl" "/etc/security/opasswd" "wa" "audit_rules_usergroup_modification"

#### Record Attempts to Alter Process and Session Initiation Information [ref]rule

The audit system already collects process information for all users and root. To watch for attempted manual edits of files involved in storing such process information, add the following to `/etc/audit/audit.rules`:

    -w /var/run/utmp -p wa -k session
    -w /var/log/btmp -p wa -k session
    -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.

Severity: unknown

References: AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.3

Remediation Shell script: (show)

    # Perform the remediation
    # Function to fix audit file system object watch rule for given path:
    # * if rule exists, also verifies the -w bits match the requirements
    # * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
    #   audit rules file, depending on the tool which was used to load audit rules
    #
    # Expects four arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules'
    # * path                        	value of -w audit rule's argument
    # * required access bits        	value of -p audit rule's argument
    # * key                         	value of -k audit rule's argument
    #
    # Example call:
    #
    #       fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    #
    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
    	echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # 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/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
    	# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
    	# Get pair -- filepath : matching_row into @matches array
    	IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
    	# Reset IFS back to default
    	unset IFS
    	# 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=("${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/$key.rules' into list of files for inspection
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		# If the $key.rules file doesn't exist yet, create it with correct permissions
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    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]+$path" "$audit_rules_file"
    	then
    		# Rule is found => verify yet if existing rule definition contains
    		# all of the required access type bits

    		# Escape slashes in path for use in sed pattern below
    		local esc_path=${path//$'/'/$'\/'}
    		# Define BRE whitespace class shortcut
    		local 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\+$esc_path$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 "$required_access_bits" | 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\+$esc_path$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 $path -p $required_access_bits -k $key" >> "$audit_rules_file"
    	fi
    done
    }

    fix_audit_watch_rule "auditctl" "/var/run/utmp" "wa" "session"
    fix_audit_watch_rule "auditctl" "/var/log/btmp" "wa" "session"
    # Function to fix audit file system object watch rule for given path:
    # * if rule exists, also verifies the -w bits match the requirements
    # * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
    #   audit rules file, depending on the tool which was used to load audit rules
    #
    # Expects four arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules'
    # * path                        	value of -w audit rule's argument
    # * required access bits        	value of -p audit rule's argument
    # * key                         	value of -k audit rule's argument
    #
    # Example call:
    #
    #       fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    #
    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
    	echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # 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/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
    	# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
    	# Get pair -- filepath : matching_row into @matches array
    	IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
    	# Reset IFS back to default
    	unset IFS
    	# 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=("${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/$key.rules' into list of files for inspection
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		# If the $key.rules file doesn't exist yet, create it with correct permissions
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    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]+$path" "$audit_rules_file"
    	then
    		# Rule is found => verify yet if existing rule definition contains
    		# all of the required access type bits

    		# Escape slashes in path for use in sed pattern below
    		local esc_path=${path//$'/'/$'\/'}
    		# Define BRE whitespace class shortcut
    		local 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\+$esc_path$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 "$required_access_bits" | 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\+$esc_path$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 $path -p $required_access_bits -k $key" >> "$audit_rules_file"
    	fi
    done
    }

    fix_audit_watch_rule "auditctl" "/var/log/wtmp" "wa" "session"

#### Ensure auditd Collects Unauthorized Access Attempts to Files (unsuccessful) [ref]rule

At a minimum the audit system should collect unauthorized file accesses for all users and root. Add the following to `/etc/audit/audit.rules`:

    -a always,exit -F arch=b32 -S creat -S open -S openat -S open_by_handle_at -S truncate -S ftruncate -F exit=-EACCES -F auid>=500 -F auid!=4294967295 -k access
    -a always,exit -F arch=b32 -S creat -S open -S openat -S open_by_handle_at -S truncate -S ftruncate -F exit=-EPERM -F auid>=500 -F auid!=4294967295 -k access

If the system is 64 bit then also add the following:

    -a always,exit -F arch=b64 -S creat -S open -S openat -S open_by_handle_at -S truncate -S ftruncate -F exit=-EACCES -F auid>=500 -F auid!=4294967295 -k access
    -a always,exit -F arch=b64 -S creat -S open -S openat -S open_by_handle_at -S truncate -S ftruncate -F exit=-EPERM -F auid>=500 -F auid!=4294967295 -k access

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.

Severity: unknown

References: CCI-000126, AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.4, Req-10.2.1, SRG-OS-000064, SV-50367r2_rule

Remediation Shell script: (show)

    # 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

    	# First fix the -EACCES requirement
    	PATTERN="-a always,exit -F arch=$ARCH -S .* -F exit=-EACCES -F auid>=500 -F auid!=4294967295 -k *"
    	# Use escaped BRE regex to specify rule group
    	GROUP="\(creat\|open\|truncate\)"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S creat -S open -S openat -S open_by_handle_at -S truncate -S ftruncate -F exit=-EACCES -F auid>=500 -F auid!=4294967295 -k access"
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # 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
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"

    	# Then fix the -EPERM requirement
    	PATTERN="-a always,exit -F arch=$ARCH -S .* -F exit=-EPERM -F auid>=500 -F auid!=4294967295 -k *"
    	# No need to change content of $GROUP variable - it's the same as for -EACCES case above
    	FULL_RULE="-a always,exit -F arch=$ARCH -S creat -S open -S openat -S open_by_handle_at -S truncate -S ftruncate -F exit=-EPERM -F auid>=500 -F auid!=4294967295 -k access"
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # 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
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"

    done

#### Ensure auditd Collects File Deletion Events by User [ref]rule

At a minimum the audit system should collect file deletion events for all users and root. Add the following to `/etc/audit/audit.rules`, setting ARCH to either b32 or b64 as appropriate for your system:

    -a always,exit -F arch=ARCH -S rmdir -S unlink -S unlinkat -S rename -S renameat -F auid>=500 -F auid!=4294967295 -k delete

Rationale:

Auditing file deletions will create an audit trail for files that are removed from the system. The audit trail could aid in system troubleshooting, as well as, detecting malicious processes that attempt to delete log files to conceal their presence.

Severity: unknown

References: CCI-000126, AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.2, Req-10.2.5.b, SRG-OS-000064, SV-50376r4_rule

Remediation Shell script: (show)

    # Perform the remediation for 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
    	PATTERN="-a always,exit -F arch=$ARCH -S .* -F auid>=500 -F auid!=4294967295 -k delete"
    	# Use escaped BRE regex to specify rule group
    	GROUP="\(rmdir\|unlink\|rename\)"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S rmdir -S unlink -S unlinkat -S rename -S renameat -F auid>=500 -F auid!=4294967295 -k delete"
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # 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
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

#### Ensure auditd Collects Information on Exporting to Media (successful) [ref]rule

At a minimum the audit system should collect media exportation events for all users and root. Add the following to `/etc/audit/audit.rules`, setting ARCH to either b32 or b64 as appropriate for your system:

    -a always,exit -F arch=ARCH -S mount -F auid>=500 -F auid!=4294967295 -k export

Rationale:

The unauthorized exportation of data to external media could result in an information leak where classified information, Privacy Act information, and intellectual property could be lost. An audit trail should be created each time a filesystem is mounted to help identify and guard against information loss.

Severity: unknown

References: CCI-000126, AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.7, SRG-OS-000064, SV-50369r3_rule

Remediation Shell script: (show)

    # 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
    	PATTERN="-a always,exit -F arch=$ARCH -S .* -F auid>=500 -F auid!=4294967295 -k *"
    	GROUP="mount"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S mount -F auid>=500 -F auid!=4294967295 -k export"
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # 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
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

#### Make the auditd Configuration Immutable [ref]rule

Add the following to `/etc/audit/audit.rules` in order to make the configuration immutable:

    -e 2

With this setting, a reboot will be required to change any audit rules.Rationale:

Making the audit configuration immutable prevents accidental as well as malicious modification of the audit rules, although it may be problematic if legitimate changes are needed during system operation

Severity: unknown

References: AC-6, AU-1(b), AU-2(a), AU-2(c), AU-2(d), IR-5, Req-10.5.2

Remediation Shell script: (show)

    readonly AUDIT_RULES='/etc/audit/audit.rules'

    # If '-e .*' setting present in audit.rules already, delete it since the
    # auditctl(8) manual page instructs it should be the last rule in configuration
    sed -i '/-e[[:space:]]\+.*/d' $AUDIT_RULES

    # Append '-e 2' requirement at the end of audit.rules
    echo '' >> $AUDIT_RULES
    echo '# Set the audit.rules configuration immutable per security requirements' >> $AUDIT_RULES
    echo '# Reboot is required to change audit rules once this setting is applied' >> $AUDIT_RULES
    echo '-e 2' >> $AUDIT_RULES

#### System Audit Logs Must Be Owned By Root [ref]rule

To properly set the owner of `/var/log`, run the command:

    $ sudo chown root /var/log

Rationale:

Failure to give ownership of the audit log files to root allows the designated owner, and unauthorized users, potential access to sensitive information.

Severity: unknown

References: CCI-000166, AC-6, AU-1(b), AU-9, IR-5, Req-10.5.1, SRG-OS-000057, SV-50296r1_rule

Remediation Shell script: (show)

    if `grep -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
        chown root.${GROUP} /var/log/audit
        chown root.${GROUP} /var/log/audit/audit.log*
      else
        chown root.root /var/log/audit
        chown root.root /var/log/audit/audit.log*
      fi
    else
      chown root.root /var/log/audit
      chown root.root /var/log/audit/audit.log*
    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.

Severity: unknown

References: CCI-000166, AC-6, AU-1(b), AU-9, IR-5, Req-10.5, SRG-OS-000058, SV-50299r1_rule

Remediation Shell script: (show)

    if `grep -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 /var/log/audit/audit.log
        chmod 0440 /var/log/audit/audit.log.*
      else
        chmod 0600 /var/log/audit/audit.log
        chmod 0400 /var/log/audit/audit.log.*
      fi

      chmod 0640 /etc/audit/audit*
      chmod 0640 /etc/audit/rules.d/*
    else
      chmod 0600 /var/log/audit/audit.log
      chmod 0400 /var/log/audit/audit.log.*
      chmod 0640 /etc/audit/audit*
      chmod 0640 /etc/audit/rules.d/*
    fi

#### Record Events that Modify the System's Mandatory Access Controls [ref]rule

Add the following to `/etc/audit/audit.rules`:

    -w /etc/selinux/ -p wa -k MAC-policy

Rationale:

The system's mandatory access policy (SELinux) should not be arbitrarily changed by anything other than administrator action. All changes to MAC policy should be audited.

Severity: unknown

References: AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-999999, SV-50342r2_rule

Remediation Shell script: (show)

    # Perform the remediation
    # Function to fix audit file system object watch rule for given path:
    # * if rule exists, also verifies the -w bits match the requirements
    # * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
    #   audit rules file, depending on the tool which was used to load audit rules
    #
    # Expects four arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules'
    # * path                        	value of -w audit rule's argument
    # * required access bits        	value of -p audit rule's argument
    # * key                         	value of -k audit rule's argument
    #
    # Example call:
    #
    #       fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    #
    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
    	echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
    	echo "Aborting."
    	exit 1
    fi

    # 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  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # 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/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
    	# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
    	# Get pair -- filepath : matching_row into @matches array
    	IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
    	# Reset IFS back to default
    	unset IFS
    	# 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=("${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/$key.rules' into list of files for inspection
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		# If the $key.rules file doesn't exist yet, create it with correct permissions
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    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]+$path" "$audit_rules_file"
    	then
    		# Rule is found => verify yet if existing rule definition contains
    		# all of the required access type bits

    		# Escape slashes in path for use in sed pattern below
    		local esc_path=${path//$'/'/$'\/'}
    		# Define BRE whitespace class shortcut
    		local 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\+$esc_path$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 "$required_access_bits" | 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\+$esc_path$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 $path -p $required_access_bits -k $key" >> "$audit_rules_file"
    	fi
    done
    }

    fix_audit_watch_rule "auditctl" "/etc/selinux/" "wa" "MAC-policy"

#### Enable Auditing for Processes Which Start Prior to the Audit Daemon [ref]rule

To ensure all processes can be audited, even those which start prior to the audit daemon, add the argument `audit=1` to the kernel line in `/etc/grub.conf`, in the manner below:

    kernel /vmlinuz-version ro vga=ext root=/dev/VolGroup00/LogVol00 rhgb quiet audit=1

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.

Severity: low

References: CCI-000169, AC-17(1), AU-14(1), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-10, IR-5, Req-10.3, SRG-OS-000062, SV-50238r4_rule

Remediation Shell script: (show)

    /sbin/grubby --update-kernel=ALL --args="audit=1"

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict

    - name: "Enable Auditing for Processes Which Start Prior to the Audit Daemon"
      shell: /sbin/grubby --update-kernel=ALL --args="audit=1"
      tags:
        - bootloader_audit_argument
        - low_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-26785-6
        - NIST-800-53-AC-17(1)
        - NIST-800-53-AU-14(1)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-10
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10.3
        - DISA-STIG-RHEL-06-000525

#### Enable auditd Service [ref]rule

The `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 chkconfig --level 2345 auditd on

Rationale:

Ensuring the `auditd` service is active ensures audit records generated by the kernel can be written to disk, or that appropriate actions will be taken if other obstacles exist.

Severity: medium

References: CCI-000347, CCI-000157, CCI-000172, CCI-000880, CCI-001353, CCI-001462, CCI-001487, CCI-001115, CCI-001454, CCI-000067, CCI-000158, CCI-000831, CCI-001190, CCI-001312, CCI-001263, CCI-000130, CCI-000120, CCI-001589, AC-17(1), AU-1(b), AU-10, AU-12(a), AU-12(c), IR-5, Req-10, SRG-OS-000255, SRG-OS-000032, SRG-OS-000037, SV-50429r2_rule

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Strategy: | enable

    # Function to enable/disable and start/stop services on RHEL and Fedora systems.
    #
    # Example Call(s):
    #
    #     service_command enable bluetooth
    #     service_command disable bluetooth.service
    #
    #     Using xinetd:
    #     service_command disable rsh.socket xinetd=rsh
    #
    function service_command {

    # Load function arguments into local variables
    local service_state=$1
    local service=$2
    local xinetd=$(echo $3 | cut -d'=' -f2)

    # Check sanity of the input
    if [ $# -lt "2" ]
    then
      echo "Usage: service_command 'enable/disable' 'service_name.service'"
      echo
      echo "To enable or disable xinetd services add \'xinetd=service_name\'"
      echo "as the last argument"
      echo "Aborting."
      exit 1
    fi

    # If systemctl is installed, use systemctl command; otherwise, use the service/chkconfig commands
    if [ -f "/usr/bin/systemctl" ] ; then
      service_util="/usr/bin/systemctl"
    else
      service_util="/sbin/service"
      chkconfig_util="/sbin/chkconfig"
    fi

    # If disable is not specified in arg1, set variables to enable services.
    # Otherwise, variables are to be set to disable services.
    if [ "$service_state" != 'disable' ] ; then
      service_state="enable"
      service_operation="start"
      chkconfig_state="on"
    else
      service_state="disable"
      service_operation="stop"
      chkconfig_state="off"
    fi

    # If chkconfig_util is not empty, use chkconfig/service commands.
    if [ "x$chkconfig_util" != x ] ; then
      $service_util $service $service_operation
      $chkconfig_util --level 0123456 $service $chkconfig_state
    else
      $service_util $service_operation $service
      $service_util $service_state $service
      # 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.
      $service_util reset-failed $service
    fi

    # Test if local variable xinetd is empty using non-bashism.
    # If empty, then xinetd is not being used.
    if [ "x$xinetd" != x ] ; then
      grep -qi disable /etc/xinetd.d/$xinetd && \

      if [ "$service_operation" = 'disable' ] ; then
        sed -i "s/disable.*/disable         = no/gI" /etc/xinetd.d/$xinetd
      else
        sed -i "s/disable.*/disable         = yes/gI" /etc/xinetd.d/$xinetd
      fi
    fi

    }

    service_command enable auditd

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | enable

    - name: Enable service auditd
      service:
        name="{{item}}"
        enabled="yes"
        state="started"
      with_items:
        - auditd
      tags:
        - service_auditd_enabled
        - medium_severity
        - enable_strategy
        - low_complexity
        - low_disruption
        - CCE-27058-7
        - NIST-800-53-AC-17(1)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-10
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - PCI-DSS-Req-10
        - DISA-STIG-RHEL-06-000145

### Installing and Maintaining Software [ref]group

The following sections contain information on security-relevant choices during the initial operating system installation process and the setup of software updates.

contains 15 rules

### Software Integrity Checking [ref]group

Both 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.
Integrity checking cannot prevent intrusions, but can detect that they have occurred. Requirements for software 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 7 rules

### Verify Integrity with RPM [ref]group

The RPM package management system includes the ability to verify the integrity of installed packages by comparing the installed files with information about the files taken from the package metadata stored in the RPM database. Although an attacker could corrupt the RPM database (analogous to attacking the AIDE database as described above), this check can still reveal modification of important files. To list which files on the system differ from what is expected by the RPM database:

    $ rpm -qVa

See the man page for `rpm` to see a complete explanation of each column.

contains 2 rules

#### Verify and Correct File Permissions with RPM [ref]rule

The RPM package management system can check file access permissions of installed software packages, including many that are important to system security. After locating a file with incorrect permissions which can be found with

    $ rpm -Va | grep '^.M'

, run the following command to determine which package owns it:

    $ rpm -qf FILENAME

Next, run the following command to reset its permissions to the correct values:

    $ sudo rpm --setperms PACKAGENAME

Rationale:

Permissions on system binaries and configuration files that are too generous could allow an unauthorized user to gain privileges that they should not have. The permissions set by the vendor should be maintained. Any deviations from this baseline should be investigated.

Severity: unknown

References: CCI-001493, CCI-001494, CCI-001495, AC-6, CM-6(d), SI-7, Req-11.5, SRG-OS-999999, SRG-OS-000256, SV-50252r2_rule

Remediation Shell script: (show)

Complexity: | high
Disruption: | medium
Strategy: | restrict

    # Declare array to hold list of RPM packages we need to correct permissions for
    declare -a SETPERMS_RPM_LIST

    # Create a list of files on the system having permissions different from what
    # is expected by the RPM database
    FILES_WITH_INCORRECT_PERMS=($(rpm -Va --nofiledigest | grep '^.M' | cut -d ' ' -f4-))

    # For each file path from that list:
    # * Determine the RPM package the file path is shipped by,
    # * Include it into SETPERMS_RPM_LIST array

    for FILE_PATH in "${FILES_WITH_INCORRECT_PERMS[@]}"
    do
    	RPM_PACKAGE=$(rpm -qf "$FILE_PATH")
    	SETPERMS_RPM_LIST=("${SETPERMS_RPM_LIST[@]}" "$RPM_PACKAGE")
    done

    # Remove duplicate mention of same RPM in $SETPERMS_RPM_LIST (if any)
    SETPERMS_RPM_LIST=( $(echo "${SETPERMS_RPM_LIST[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ') )

    # For each of the RPM packages left in the list -- reset its permissions to the
    # correct values
    for RPM_PACKAGE in "${SETPERMS_RPM_LIST[@]}"
    do
    	rpm --setperms "${RPM_PACKAGE}"
    done

Remediation Ansible snippet: (show)

Complexity: | high
Disruption: | medium
Strategy: | restrict

    - name: "Read list of files with incorrect permissions"
      shell: "rpm -Va | grep '^.M' | cut -d ' ' -f5- | sed -r 's;^.*\\s+(.+);\\1;g'"
      register: files_with_incorrect_permissions
      failed_when: False
      changed_when: False
      check_mode: no
      tags:
        - rpm_verify_permissions
        - unknown_severity
        - restrict_strategy
        - high_complexity
        - medium_disruption
        - CCE-26731-0
        - NIST-800-53-AC-6
        - NIST-800-53-CM-6(d)
        - NIST-800-53-SI-7
        - PCI-DSS-Req-11.5
        - DISA-STIG-RHEL-06-000518

    - name: "Correct file permissions with RPM"
      shell: "rpm --setperms $(rpm -qf '{{item}}')"
      with_items: "{{ files_with_incorrect_permissions.stdout_lines }}"
      when: files_with_incorrect_permissions.stdout_lines | length > 0
      tags:
        - rpm_verify_permissions
        - unknown_severity
        - restrict_strategy
        - high_complexity
        - medium_disruption
        - CCE-26731-0
        - NIST-800-53-AC-6
        - NIST-800-53-CM-6(d)
        - NIST-800-53-SI-7
        - PCI-DSS-Req-11.5
        - DISA-STIG-RHEL-06-000518

#### Verify File Hashes with RPM [ref]rule

The RPM package management system can check the hashes of installed software packages, including many that are important to system security. Run the following command to list which files on the system have hashes that differ from what is expected by the RPM database:

    $ rpm -Va | grep '^..5'

A "c" in the second column indicates that a file is a configuration file, which may appropriately be expected to change. If the file was not expected to change, investigate the cause of the change using audit logs or other means. The package can then be reinstalled to restore the file. Run the following command to determine which package owns the file:

    $ rpm -qf FILENAME

The package can be reinstalled from a yum repository using the command:

    $ sudo yum reinstall PACKAGENAME

Alternatively, the package can be reinstalled from trusted media using the command:

    $ sudo rpm -Uvh PACKAGENAME

Rationale:

The hashes of important files like system executables should match the information given by the RPM database. Executables with erroneous hashes could be a sign of nefarious activity on the system.

Severity: unknown

References: CCI-001496, CM-6(d), SI-7, Req-11.5, SRG-OS-999999, SRG-OS-000278, SV-50247r4_rule

Remediation Ansible snippet: (show)

Complexity: | high
Disruption: | medium

    - name: "Set fact: Package manager reinstall command (dnf)"
      set_fact:
        package_manager_reinstall_cmd: dnf reinstall -y
      when: ansible_distribution == "Fedora"
      tags:
        - rpm_verify_hashes
        - unknown_severity
        - unknown_strategy
        - high_complexity
        - medium_disruption
        - CCE-27223-7
        - NIST-800-53-CM-6(d)
        - NIST-800-53-SI-7
        - PCI-DSS-Req-11.5
        - DISA-STIG-RHEL-06-000519

    - name: "Set fact: Package manager reinstall command (yum)"
      set_fact:
        package_manager_reinstall_cmd: yum reinstall -y
      when: ansible_distribution == "RedHat" or ansible_distribution == "OracleLinux"
      tags:
        - rpm_verify_hashes
        - unknown_severity
        - unknown_strategy
        - high_complexity
        - medium_disruption
        - CCE-27223-7
        - NIST-800-53-CM-6(d)
        - NIST-800-53-SI-7
        - PCI-DSS-Req-11.5
        - DISA-STIG-RHEL-06-000519

    - name: "Read files with incorrect hash"
      shell: "rpm -Va | grep -E '^..5.* /(bin|sbin|lib|lib64|usr)/' | sed -r 's;^.*\\s+(.+);\\1;g'"
      register: files_with_incorrect_hash
      changed_when: False
      when: package_manager_reinstall_cmd is defined
      check_mode: no
      tags:
        - rpm_verify_hashes
        - unknown_severity
        - unknown_strategy
        - high_complexity
        - medium_disruption
        - CCE-27223-7
        - NIST-800-53-CM-6(d)
        - NIST-800-53-SI-7
        - PCI-DSS-Req-11.5
        - DISA-STIG-RHEL-06-000519

    - name: "Reinstall packages of files with incorrect hash"
      shell: "{{package_manager_reinstall_cmd}} $(rpm -qf '{{item}}')"
      with_items: "{{ files_with_incorrect_hash.stdout_lines }}"
      when: package_manager_reinstall_cmd is defined and (files_with_incorrect_hash.stdout_lines | length > 0)
      tags:
        - rpm_verify_hashes
        - unknown_severity
        - unknown_strategy
        - high_complexity
        - medium_disruption
        - CCE-27223-7
        - NIST-800-53-CM-6(d)
        - NIST-800-53-SI-7
        - PCI-DSS-Req-11.5
        - DISA-STIG-RHEL-06-000519

### Additional Security Software [ref]group

Additional 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

#### Install Intrusion Detection Software [ref]rule

The base Red Hat platform already includes a sophisticated auditing system that can detect intruder activity, as well as SELinux, which provides host-based intrusion prevention capabilities by confining privileged programs and user sessions which may become compromised.
In DoD environments, supplemental intrusion detection tools, such as, the McAfee Host-based Security System, are available to integrate with existing infrastructure. When these supplemental tools interfere with the proper functioning of SELinux, SELinux takes precedence.

Rationale:

Host-based intrusion detection tools provide a system-level defense when an intruder gains access to a system or network.

Severity: medium

References: CCI-001263, SC-7, Req-11.4, SRG-OS-000196, SV-50468r3_rule

### Verify Integrity with AIDE [ref]group

AIDE 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]rule

Install the AIDE package with the command:

    $ sudo yum install aide

Rationale:

The AIDE package must be installed if it is to be available for integrity checking.

Severity: medium

References: CCI-001069, CM-3(d), CM-3(e), CM-6(d), SC-28, SI-7, Req-11.5, SRG-OS-000232, SV-50290r1_rule

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Strategy: | enable

    # Function to install packages on RHEL, Fedora, Debian, and possibly other systems.
    #
    # Example Call(s):
    #
    #     package_install aide
    #
    function package_install {

    # Load function arguments into local variables
    local package="$1"

    # Check sanity of the input
    if [ $# -ne "1" ]
    then
      echo "Usage: package_install 'package_name'"
      echo "Aborting."
      exit 1
    fi

    if which dnf ; then
      if ! rpm -q --quiet "$package"; then
        dnf install -y "$package"
      fi
    elif which yum ; then
      if ! rpm -q --quiet "$package"; then
        yum install -y "$package"
      fi
    elif which apt-get ; then
      apt-get install -y "$package"
    else
      echo "Failed to detect available packaging system, tried dnf, yum and apt-get!"
      echo "Aborting."
      exit 1
    fi

    }

    package_install aide

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | enable

    - name: Ensure aide is installed
      package:
        name="{{item}}"
        state=present
      with_items:
        - aide
      tags:
        - package_aide_installed
        - medium_severity
        - enable_strategy
        - low_complexity
        - low_disruption
        - CCE-27024-9
        - NIST-800-53-CM-3(d)
        - NIST-800-53-CM-3(e)
        - NIST-800-53-CM-6(d)
        - NIST-800-53-SC-28
        - NIST-800-53-SI-7
        - PCI-DSS-Req-11.5
        - DISA-STIG-RHEL-06-000016

Remediation Puppet snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | enable

    include install_aide

    class install_aide {
      package { 'aide':
        ensure => 'installed',
      }
    }

Remediation Anaconda snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | enable

    package --add=aide

#### Configure Periodic Execution of AIDE [ref]rule

To implement a daily execution of AIDE at 4:05am using cron, add the following line to `/etc/crontab`:

    05 4 * * * root /usr/sbin/aide --check

AIDE can be executed periodically through other means; this is merely one example.Rationale:

By default, AIDE does not install itself for periodic execution. Periodically running AIDE is necessary to reveal unexpected changes in installed files.

Severity: medium

References: CCI-000374, CCI-000416, CCI-001069, CCI-001263, CCI-001297, CCI-001589, CM-3(d), CM-3(e), CM-6(d), SC-28, SI-7, Req-11.5, SRG-OS-000202, SRG-OS-000094, SRG-OS-000098, SRG-OS-000232, SRG-OS-000196, SRG-OS-000265, SV-50471r2_rule

Remediation Shell script: (show)

    # Function to install packages on RHEL, Fedora, Debian, and possibly other systems.
    #
    # Example Call(s):
    #
    #     package_install aide
    #
    function package_install {

    # Load function arguments into local variables
    local package="$1"

    # Check sanity of the input
    if [ $# -ne "1" ]
    then
      echo "Usage: package_install 'package_name'"
      echo "Aborting."
      exit 1
    fi

    if which dnf ; then
      if ! rpm -q --quiet "$package"; then
        dnf install -y "$package"
      fi
    elif which yum ; then
      if ! rpm -q --quiet "$package"; then
        yum install -y "$package"
      fi
    elif which apt-get ; then
      apt-get install -y "$package"
    else
      echo "Failed to detect available packaging system, tried dnf, yum and apt-get!"
      echo "Aborting."
      exit 1
    fi

    }

    package_install aide

    if ! grep -q "/usr/sbin/aide --check" /etc/crontab ; then
        echo "05 4 * * * root /usr/sbin/aide --check" >> /etc/crontab
    fi

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | restrict

    - name: "Ensure AIDE is installed"
      package:
        name="{{item}}"
        state=present
      with_items:
        - aide
      tags:
        - aide_periodic_cron_checking
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27222-9
        - NIST-800-53-CM-3(d)
        - NIST-800-53-CM-3(e)
        - NIST-800-53-CM-6(d)
        - NIST-800-53-SC-28
        - NIST-800-53-SI-7
        - PCI-DSS-Req-11.5
        - DISA-STIG-RHEL-06-000306

    - name: "Configure Periodic Execution of AIDE"
      cron:
        name: "run AIDE check"
        minute: 05
        hour: 04
        weekday: 0
        user: root
        job: "/usr/sbin/aide --check"
      tags:
        - aide_periodic_cron_checking
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27222-9
        - NIST-800-53-CM-3(d)
        - NIST-800-53-CM-3(e)
        - NIST-800-53-CM-6(d)
        - NIST-800-53-SC-28
        - NIST-800-53-SI-7
        - PCI-DSS-Req-11.5
        - DISA-STIG-RHEL-06-000306

#### Build and Test AIDE Database [ref]rule

Run the following command to generate a new database:

    $ sudo /usr/sbin/aide --init

By default, the database will be written to the file `/var/lib/aide/aide.db.new.gz`. Storing the database, the configuration file `/etc/aide.conf`, and the binary `/usr/sbin/aide` (or hashes of these files), in a secure location (such as on read-only media) provides additional assurance about their integrity. The newly-generated database can be installed as follows:

    $ sudo cp /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz

To initiate a manual check, run the following command:

    $ sudo /usr/sbin/aide --check

If this check produces any unexpected output, investigate.Rationale:

For AIDE to be effective, an initial database of "known-good" information about files must be captured and it should be able to be verified against the installed files.

Severity: medium

References: CCI-000374, CCI-000416, CCI-001069, CCI-001263, CCI-001297, CCI-001589, CM-3(d), CM-3(e), CM-6(d), SC-28, SI-7, Req-11.5, SRG-OS-000232, SV-65601r1_rule

Remediation Shell script: (show)

    # Function to install packages on RHEL, Fedora, Debian, and possibly other systems.
    #
    # Example Call(s):
    #
    #     package_install aide
    #
    function package_install {

    # Load function arguments into local variables
    local package="$1"

    # Check sanity of the input
    if [ $# -ne "1" ]
    then
      echo "Usage: package_install 'package_name'"
      echo "Aborting."
      exit 1
    fi

    if which dnf ; then
      if ! rpm -q --quiet "$package"; then
        dnf install -y "$package"
      fi
    elif which yum ; then
      if ! rpm -q --quiet "$package"; then
        yum install -y "$package"
      fi
    elif which apt-get ; then
      apt-get install -y "$package"
    else
      echo "Failed to detect available packaging system, tried dnf, yum and apt-get!"
      echo "Aborting."
      exit 1
    fi

    }

    package_install aide

    /usr/sbin/aide --init
    /bin/cp -p /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | restrict

    - name: "Ensure AIDE is installed"
      package:
        name="{{item}}"
        state=present
      with_items:
        - aide
      tags:
        - aide_build_database
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27135-3
        - NIST-800-53-CM-3(d)
        - NIST-800-53-CM-3(e)
        - NIST-800-53-CM-6(d)
        - NIST-800-53-SC-28
        - NIST-800-53-SI-7
        - PCI-DSS-Req-11.5
        - DISA-STIG-RHEL-06-000018

    - name: "Build and Test AIDE Database"
      shell: /usr/sbin/aide --init
      tags:
        - aide_build_database
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27135-3
        - NIST-800-53-CM-3(d)
        - NIST-800-53-CM-3(e)
        - NIST-800-53-CM-6(d)
        - NIST-800-53-SC-28
        - NIST-800-53-SI-7
        - PCI-DSS-Req-11.5
        - DISA-STIG-RHEL-06-000018

    - name: Stage AIDE Database"
      copy:
        src: /var/lib/aide/aide.db.new.gz
        dest: /var/lib/aide/aide.db.gz
        backup: yes
        remote_src: yes
      tags:
        - aide_build_database
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27135-3
        - NIST-800-53-CM-3(d)
        - NIST-800-53-CM-3(e)
        - NIST-800-53-CM-6(d)
        - NIST-800-53-SC-28
        - NIST-800-53-SI-7
        - PCI-DSS-Req-11.5
        - DISA-STIG-RHEL-06-000018

#### Disable Prelinking [ref]rule

The prelinking feature changes binaries in an attempt to decrease their startup time. In order to disable it, change or add the following line inside the file `/etc/sysconfig/prelink`:

    PRELINKING=no

Next, run the following command to return binaries to a normal, non-prelinked state:

    $ sudo /usr/sbin/prelink -ua

Rationale:

Because the prelinking feature changes binaries, it can interfere with the operation of certain software and/or modes such as AIDE, FIPS, etc.

Severity: unknown

References: CM-6(d), SC-28, SI-7, Req-11.5

Remediation Shell script: (show)

    #
    # Disable prelinking altogether
    #
    if grep -q ^PRELINKING /etc/sysconfig/prelink
    then
      sed -i 's/PRELINKING.*/PRELINKING=no/g' /etc/sysconfig/prelink
    else
      echo -e "\n# Set PRELINKING=no per security requirements" >> /etc/sysconfig/prelink
      echo "PRELINKING=no" >> /etc/sysconfig/prelink
    fi

    #
    # Undo previous prelink changes to binaries
    #
    /usr/sbin/prelink -ua

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | restrict

    - name: Does prelink file exist
      stat:
        path=/etc/sysconfig/prelink
      register: prelink_exists
      tags:
        - disable_prelink
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27221-1
        - NIST-800-53-CM-6(d)
        - NIST-800-53-SC-28
        - NIST-800-53-SI-7
        - PCI-DSS-Req-11.5

    - name: disable prelinking
      lineinfile:
        path: /etc/sysconfig/prelink
        regexp: '^PRELINKING='
        line: 'PRELINKING=no'
      when: prelink_exists.stat.exists == True
      tags:
        - disable_prelink
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27221-1
        - NIST-800-53-CM-6(d)
        - NIST-800-53-SC-28
        - NIST-800-53-SI-7
        - PCI-DSS-Req-11.5

### GNOME Desktop Environment [ref]group

GNOME 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 4 rules

### Configure GNOME Screen Locking [ref]group

In the default GNOME desktop, the screen can be locked by choosing Lock Screen from the System menu.
The `gconftool-2` program can be used to enforce mandatory screen locking settings for the default GNOME environment. 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 Lock Screen icon from the System menu can also be dragged to the taskbar in order to facilitate even more convenient screen-locking.
The root account cannot be screen-locked, but this should have no practical effect as 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 configuring GNOME screensaver, see http://live.gnome.org/GnomeScreensaver. For more information about enforcing preferences in the GNOME environment using the GConf configuration system, see http://projects.gnome.org/gconf and the man page `gconftool-2(1)`.

contains 4 rules

#### Implement Blank Screensaver [ref]rule

Run the following command to set the screensaver mode in the GNOME desktop to a blank screen:

    $ sudo gconftool-2 --direct \
      --config-source xml:readwrite:/etc/gconf/gconf.xml.mandatory \
      --type string \
      --set /apps/gnome-screensaver/mode blank-only

Rationale:

Setting the screensaver mode to blank-only conceals the contents of the display from passersby.

Severity: unknown

References: CCI-000060, AC-11(b), Req-8.1.8, SRG-OS-000031, SV-50440r3_rule

Remediation Shell script: (show)

    # Install GConf2 package if not installed
    if ! rpm -q GConf2; then
      yum -y install GConf2
    fi

    # Set the screensaver mode in the GNOME desktop to a blank screen
    gconftool-2 --direct \
                --config-source "xml:readwrite:/etc/gconf/gconf.xml.mandatory" \
                --type string \
                --set /apps/gnome-screensaver/mode blank-only

#### Enable Screen Lock Activation After Idle Period [ref]rule

Run the following command to activate locking of the screensaver in the GNOME desktop when it is activated:

    $ sudo gconftool-2 --direct \
      --config-source xml:readwrite:/etc/gconf/gconf.xml.mandatory \
      --type bool \
      --set /apps/gnome-screensaver/lock_enabled true

Rationale:

Enabling the activation of the screen lock after an idle period ensures password entry will be required in order to access the system, preventing access by passersby.

Severity: medium

References: CCI-000057, AC-11(a), Req-8.1.8, SRG-OS-000029, SV-50439r3_rule

Remediation Shell script: (show)

    # Install GConf2 package if not installed
    if ! rpm -q GConf2; then
      yum -y install GConf2
    fi

    # Set the screensaver locking activation in the GNOME desktop when the
    # screensaver is activated
    gconftool-2 --direct \
                --config-source "xml:readwrite:/etc/gconf/gconf.xml.mandatory" \
                --type bool \
                --set /apps/gnome-screensaver/lock_enabled true

#### GNOME Desktop Screensaver Mandatory Use [ref]rule

Run the following command to activate the screensaver in the GNOME desktop after a period of inactivity:

    $ sudo gconftool-2 --direct \
      --config-source xml:readwrite:/etc/gconf/gconf.xml.mandatory \
      --type bool \
      --set /apps/gnome-screensaver/idle_activation_enabled true

Rationale:

Enabling idle activation of the screensaver ensures the screensaver will be activated after the idle delay. Applications requiring continuous, real-time screen display (such as network management products) require the login session does not have administrator rights and the display station is located in a controlled-access area.

Severity: medium

References: CCI-000057, AC-11(a), Req-8.1.8, SRG-OS-000029, SV-50431r3_rule

Remediation Shell script: (show)

    # Install GConf2 package if not installed
    if ! rpm -q GConf2; then
      yum -y install GConf2
    fi

    # Set the screensaver activation in the GNOME desktop after a period of inactivity
    gconftool-2 --direct \
                --config-source "xml:readwrite:/etc/gconf/gconf.xml.mandatory" \
                --type bool \
                --set /apps/gnome-screensaver/idle_activation_enabled true

#### Set GNOME Login Inactivity Timeout [ref]rule

Run the following command to set the idle time-out value for inactivity in the GNOME desktop to (N/A) minutes:

    $ sudo gconftool-2 \
      --direct \
      --config-source xml:readwrite:/etc/gconf/gconf.xml.mandatory \
      --type int \
      --set /desktop/gnome/session/idle_delay (N/A)

Rationale:

Setting the idle delay controls when the screensaver will start, and can be combined with screen locking to prevent access from passersby.

Severity: medium

References: CCI-000057, AC-11(a), Req-8.1.8, SRG-OS-000029, SV-50430r3_rule

Remediation Shell script: (show)

    inactivity_timeout_value="(N/A)"

    # Install GConf2 package if not installed
    if ! rpm -q GConf2; then
      yum -y install GConf2
    fi

    # Set the idle time-out value for inactivity in the GNOME desktop to meet the
    # requirement
    gconftool-2 --direct \
                --config-source "xml:readwrite:/etc/gconf/gconf.xml.mandatory" \
                --type int \
                --set /desktop/gnome/session/idle_delay ${inactivity_timeout_value}

### Updating Software [ref]group

The `yum` 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.
Red Hat Enterprise Linux systems contain an installed software catalog called the RPM database, which records metadata of installed packages. Consistently using `yum` or the graphical Software Update for all software installation allows for insight into the current inventory of installed software on the system.

contains 4 rules

#### Ensure gpgcheck Enabled In Main Yum Configuration [ref]rule

The `gpgcheck` option controls whether RPM packages' signatures are always checked prior to installation. To configure yum to check package signatures before installing them, ensure the following line appears in `/etc/yum.conf` in the `[main]` section:

    gpgcheck=1

Rationale:

Ensuring the validity of packages' cryptographic signatures prior to installation ensures the authenticity of the software and protects against malicious tampering.

Severity: medium

References: CCI-000352, CCI-000663, SI-7, MA-1(b), Req-6.2, SRG-OS-000103, SV-50283r1_rule

Remediation Shell script: (show)

    # Function to replace configuration setting in config file or add the configuration setting if
    # it does not exist.
    #
    # Expects arguments:
    #
    # config_file:		Configuration file that will be modified
    # key:			Configuration option to change
    # value:		Value of the configuration option to change
    # cce:			The CCE identifier or '@CCENUM@' if no CCE identifier exists
    # format:		The printf-like format string that will be given stripped key and value as arguments,
    #			so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
    #
    # Optional arugments:
    #
    # format:		Optional argument to specify the format of how key/value should be
    # 			modified/appended in the configuration file. The default is key = value.
    #
    # Example Call(s):
    #
    #     With default format of 'key = value':
    #     replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
    #
    #     With custom key/value format:
    #     replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
    #
    #     With a variable:
    #     replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
    #
    function replace_or_append {
      local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
      local config_file=$1
      local key=$2
      local value=$3
      local cce=$4
      local format=$5

      if [ "$case_insensitive_mode" = yes ]; then
        sed_case_insensitive_option="i"
        grep_case_insensitive_option="-i"
      fi
      [ -n "$format" ] || format="$default_format"
      # Check sanity of the input
      [ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 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 "$config_file"; then
        sed_command+=('--follow-symlinks')
      fi

      # Test that the cce arg is not empty or does not equal @CCENUM@.
      # If @CCENUM@ exists, it means that there is no CCE assigned.
      if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
        cce="CCE-${cce}"
      else
        cce="CCE"
      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' <<< "$key")

      # shellcheck disable=SC2059
      printf -v formatted_output "$format" "$stripped_key" "$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 grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
        "${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
      else
        # \n is precaution for case where file ends without trailing newline
        printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
        printf '%s\n' "$formatted_output" >> "$config_file"
      fi
    }

    replace_or_append '/etc/yum.conf' '^gpgcheck' '1' 'CCE-26709-6'

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | medium

    - name: Check existence of yum on Fedora
      stat:
        path: /etc/yum.conf
      register: yum_config_file
      check_mode: no
      when: ansible_distribution == "Fedora"

    # Old versions of Fedora use yum

    - name: Ensure GPG check is globally activated (yum)
      ini_file:
        dest: "{{item}}"
        section: main
        option: gpgcheck
        value: 1
        create: False
      with_items: "/etc/yum.conf"
      when: ansible_distribution == "RedHat" or ansible_distribution == "CentOS" or yum_config_file.stat.exists
      tags:
        - ensure_gpgcheck_globally_activated
        - medium_severity
        - unknown_strategy
        - low_complexity
        - medium_disruption
        - CCE-26709-6
        - NIST-800-53-SI-7
        - NIST-800-53-MA-1(b)
        - PCI-DSS-Req-6.2
        - DISA-STIG-RHEL-06-000013

    - name: Ensure GPG check is globally activated (dnf)
      ini_file:
        dest: "{{item}}"
        section: main
        option: gpgcheck
        value: 1
        create: False
      with_items: "/etc/dnf/dnf.conf"
      when: ansible_distribution == "Fedora"
      tags:
        - ensure_gpgcheck_globally_activated
        - medium_severity
        - unknown_strategy
        - low_complexity
        - medium_disruption
        - CCE-26709-6
        - NIST-800-53-SI-7
        - NIST-800-53-MA-1(b)
        - PCI-DSS-Req-6.2
        - DISA-STIG-RHEL-06-000013

#### Ensure Software Patches Installed [ref]rule

If the system is joined to the Red Hat Network, a Red Hat Satellite Server, or a yum server, run the following command to install updates:

    $ sudo yum update

If the system is not configured to use one of these sources, updates (in the form of RPM packages) can be manually downloaded from the Red Hat Network and installed using `rpm`.Rationale:

Installing software updates is a fundamental mitigation against the exploitation of publicly-known vulnerabilities.

Severity: medium

References: CCI-001227, CCI-001233, SI-2, MA-1(b), Req-6.2, SRG-OS-000191, SV-50281r1_rule

Remediation Shell script: (show)

Complexity: | low
Disruption: | high
Reboot: | true
Strategy: | patch

    yum -y update

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | high
Reboot: | true
Strategy: | patch

    - name: "Security patches are up to date"
      package:
        name: "*"
        state: "latest"
      tags:
        - security_patches_up_to_date
        - medium_severity
        - patch_strategy
        - low_complexity
        - high_disruption
        - CCE-27635-2
        - NIST-800-53-SI-2
        - NIST-800-53-MA-1(b)
        - PCI-DSS-Req-6.2
        - DISA-STIG-RHEL-06-000011

#### Ensure Red Hat GPG Key Installed [ref]rule

To ensure the system can cryptographically verify base software packages come from Red Hat (and to connect to the Red Hat Network to receive them), the Red Hat GPG key must properly be installed. To install the Red Hat GPG key, run:

    $ sudo subscription-manager register

If the system is not connected to the Internet or an RHN Satellite, then install the Red Hat GPG key from trusted media such as the Red Hat installation CD-ROM or DVD. Assuming the disc is mounted in `/media/cdrom`, use the following command as the root user to import it into the keyring:

    $ sudo rpm --import /media/cdrom/RPM-GPG-KEY

Rationale:

The Red Hat GPG key is necessary to cryptographically verify packages are from Red Hat.

Severity: high

References: CCI-000351, SI-7, MA-1(b), Req-6.2, SRG-OS-000090, SV-50276r3_rule

Remediation Shell script: (show)

    # The two fingerprints below are retrieved from https://access.redhat.com/security/team/key
    readonly REDHAT_RELEASE_2_FINGERPRINT="567E 347A D004 4ADE 55BA 8A5F 199E 2F91 FD43 1D51"
    readonly REDHAT_AUXILIARY_FINGERPRINT="43A6 E49C 4A38 F4BE 9ABF 2A53 4568 9C88 2FA6 58E0"
    # Location of the key we would like to import (once it's integrity verified)
    readonly REDHAT_RELEASE_KEY="/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release"

    RPM_GPG_DIR_PERMS=$(stat -c %a "$(dirname "$REDHAT_RELEASE_KEY")")

    # Verify /etc/pki/rpm-gpg directory permissions are safe
    if [ "${RPM_GPG_DIR_PERMS}" -le "755" ]
    then
      # If they are safe, try to obtain fingerprints from the key file
      # (to ensure there won't be e.g. CRC error).
      IFS=$'\n' GPG_OUT=($(gpg --with-fingerprint "${REDHAT_RELEASE_KEY}" | grep 'Key fingerprint ='))
      GPG_RESULT=$?
      # Reset IFS back to default
      unset IFS
      # No CRC error, safe to proceed
      if [ "${GPG_RESULT}" -eq "0" ]
      then
        tr -s ' ' <<< "${GPG_OUT}" | grep -vE "${REDHAT_RELEASE_2_FINGERPRINT}|${REDHAT_AUXILIARY_FINGERPRINT}" || {
          # If file doesn't contains any keys with unknown fingerprint, import it
          rpm --import "${REDHAT_RELEASE_KEY}"
        }
      fi
    fi

Remediation Ansible snippet: (show)

Complexity: | medium
Disruption: | medium
Strategy: | restrict

    - name: "Read permission of GPG key directory"
      stat:
        path: /etc/pki/rpm-gpg/
      register: gpg_key_directory_permission
      check_mode: no
      tags:
        - ensure_redhat_gpgkey_installed
        - high_severity
        - restrict_strategy
        - medium_complexity
        - medium_disruption
        - CCE-26506-6
        - NIST-800-53-SI-7
        - NIST-800-53-MA-1(b)
        - PCI-DSS-Req-6.2
        - DISA-STIG-RHEL-06-000008

    # It should fail if it doesn't find any fingerprints in file - maybe file was not parsed well.

    - name: Read signatures in GPG key
      shell: gpg --with-fingerprint '/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release' | grep 'Key fingerprint =' | tr -s ' ' | sed 's;.*= ;;g'
      changed_when: False
      register: gpg_fingerprints
      check_mode: no
      tags:
        - ensure_redhat_gpgkey_installed
        - high_severity
        - restrict_strategy
        - medium_complexity
        - medium_disruption
        - CCE-26506-6
        - NIST-800-53-SI-7
        - NIST-800-53-MA-1(b)
        - PCI-DSS-Req-6.2
        - DISA-STIG-RHEL-06-000008

    - name: Set Fact - Valid fingerprints
      set_fact:
         gpg_valid_fingerprints: ("567E 347A D004 4ADE 55BA 8A5F 199E 2F91 FD43 1D51" "43A6 E49C 4A38 F4BE 9ABF 2A53 4568 9C88 2FA6 58E0")
      tags:
        - ensure_redhat_gpgkey_installed
        - high_severity
        - restrict_strategy
        - medium_complexity
        - medium_disruption
        - CCE-26506-6
        - NIST-800-53-SI-7
        - NIST-800-53-MA-1(b)
        - PCI-DSS-Req-6.2
        - DISA-STIG-RHEL-06-000008

    - name: Import RedHat GPG key
      rpm_key:
        state: present
        key: /etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release
      when:
        (gpg_key_directory_permission.stat.mode <= '0755')
        and (( gpg_fingerprints.stdout_lines | difference(gpg_valid_fingerprints)) | length == 0)
        and (gpg_fingerprints.stdout_lines | length > 0)
        and (ansible_distribution == "RedHat")
      tags:
        - ensure_redhat_gpgkey_installed
        - high_severity
        - restrict_strategy
        - medium_complexity
        - medium_disruption
        - CCE-26506-6
        - NIST-800-53-SI-7
        - NIST-800-53-MA-1(b)
        - PCI-DSS-Req-6.2
        - DISA-STIG-RHEL-06-000008

#### Ensure gpgcheck Enabled For All Yum Package Repositories [ref]rule

To ensure signature checking is not disabled for any repos, remove any lines from files in `/etc/yum.repos.d` of the form:

    gpgcheck=0

Rationale:

Ensuring all packages' cryptographic signatures are valid prior to installation ensures the authenticity of the software and protects against malicious tampering.

Severity: low

References: CCI-000352, CCI-000663, SI-7, MA-1(b), Req-6.2, SRG-OS-000103, SV-50288r1_rule

Remediation Shell script: (show)

    sed -i 's/gpgcheck=.*/gpgcheck=1/g' /etc/yum.repos.d/*

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | medium

    #
    - name: Find All Yum Repositories
      find:
        paths: "/etc/yum.repos.d/"
        patterns: "*.repo"
      register: yum_find

    - name: Ensure gpgcheck Enabled For All Yum Package Repositories
      with_items: "{{ yum_find.files }}"
      lineinfile:
        create: yes
        dest: "{{ item.path }}"
        regexp: '^gpgcheck'
        line: 'gpgcheck=1'
      tags:
        - ensure_gpgcheck_never_disabled
        - low_severity
        - unknown_strategy
        - low_complexity
        - medium_disruption
        - CCE-26647-8
        - NIST-800-53-SI-7
        - NIST-800-53-MA-1(b)
        - PCI-DSS-Req-6.2
        - DISA-STIG-RHEL-06-000015

Red Hat and Red Hat Enterprise Linux are either registered trademarks or trademarks of Red Hat, Inc. in the United States and other countries. All other names are registered trademarks or trademarks of their respective companies.