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

> with profile PCI-DSS v3 Control Baseline for Red Hat Enterprise Linux 7
> 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 7. 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 `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 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 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 7, 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 7
Profile ID | xccdf_org.ssgproject.content_profile_pci-dss_centric

## Revision History

Current version: 0.1.31

- draft (as of 2017-08-11)

## Platforms

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

## Table of Contents

1. 2.

1. 2.1
2. 2.2
3. 2.3
4. 2.4
5. 2.5
6. 2.6

2. 3.

1. 3.1
2. 3.2
3. 3.3
4. 3.4
5. 3.5
6. 3.6
7. 3.7

3. 4.

1. 4.1
2. 4.2
3. 4.3

4. 5.

1. 5.1
2. 5.2
3. 5.3
4. 5.4

5. 6.

1. 6.1
2. 6.2
3. 6.3
4. 6.4
5. 6.5
6. 6.6
7. 6.7

6. 7.

1. 7.1
2. 7.2
3. 7.3

7. 8.

1. 8.1
2. 8.2
3. 8.3
4. 8.4
5. 8.5
6. 8.6
7. 8.7
8. 8.8

8. 10.

1. 10.1
2. 10.2
3. 10.3
4. 10.4
5. 10.5
6. 10.6
7. 10.7
8. 10.8

9. 11.

1. 11.1
2. 11.2
3. 11.3
4. 11.4
5. 11.5
6. 11.6

10. Values
11. Non PCI-DSS

## Checklist

contains 94 rules

### 2. [ref]group

Do not use vendor-supplied defaults for system passwords and other

### 2.1 [ref]group

Always change vendor-supplied

### 2.1.1 [ref]group

For wireless environments

### 2.1.1.a [ref]group

Interview responsible personnel and examine

### 2.1.1.b [ref]group

Interview personnel and examine policies and

### 2.1.1.c [ref]group

Examine vendor documentation and login to

### 2.1.1.d [ref]group

Examine vendor documentation and observe

### 2.1.1.e [ref]group

Examine vendor documentation and observe

### 2.1.a [ref]group

Choose a sample of system components, and attempt

### 2.1.b [ref]group

For the sample of system components, verify that all

### 2.1.c [ref]group

Interview personnel and examine supporting

### 2.2 [ref]group

Develop configuration standards for

### 2.2.1 [ref]group

Implement only one primary

### 2.2.1.a [ref]group

Select a sample of system components and

### 2.2.1.b [ref]group

If virtualization technologies are used, inspect the

### 2.2.2 [ref]group

Enable only necessary services,

### 2.2.2.a [ref]group

Select a sample of system components and

### 2.2.2.b [ref]group

Identify any enabled insecure services, daemons,

### 2.2.3 [ref]group

Implement additional security

### 2.2.3.a [ref]group

Inspect configuration settings to verify that security

### 2.2.4 [ref]group

Configure system security

### 2.2.4.a [ref]group

Interview system administrators and/or security

### 2.2.4.b [ref]group

Examine the system configuration standards to

### 2.2.4.c [ref]group

Select a sample of system components and

### 2.2.5 [ref]group

Remove all unnecessary

### 2.2.5.a [ref]group

Select a sample of system components and

### 2.2.5.b [ref]group

. Examine the documentation and security

### 2.2.5.c [ref]group

. Examine the documentation and security

### 2.2.a [ref]group

### 2.2.b [ref]group

Examine policies and interview personnel to

### 2.2.c [ref]group

Examine policies and interview personnel to

### 2.2.d [ref]group

Verify that system configuration standards include the

### 2.3 [ref]group

Encrypt all non-console

### 2.3.a [ref]group

Observe an administrator log on to each system and

### 2.3.b [ref]group

Review services and parameter files on systems to

### 2.3.c [ref]group

Observe an administrator log on to each system to

### 2.3.d [ref]group

Examine vendor documentation and interview

### 2.4 [ref]group

Maintain an inventory of system

### 2.4.a [ref]group

Examine system inventory to verify that a list of

### 2.4.b [ref]group

Interview personnel to verify the documented inventory

### 2.5 [ref]group

Ensure that security policies and

### 2.6 [ref]group

Shared hosting providers must

### 3. [ref]group

Protect stored cardholder data

### 3.1 [ref]group

Keep cardholder data storage to a

### 3.1.a [ref]group

Examine the data retention and disposal policies,

### 3.1.b [ref]group

Interview personnel to verify that:

### 3.1.c [ref]group

For a sample of system components that store cardholder

### 3.2 [ref]group

Do not store sensitive authentication

### 3.2.1 [ref]group

Do not store the full contents of

### 3.2.2 [ref]group

Do not store the card verification

### 3.2.3 [ref]group

Do not store the personal

### 3.2.a [ref]group

For issuers and/or companies that support issuing

### 3.2.b [ref]group

For issuers and/or companies that support issuing

### 3.2.c [ref]group

For all other entities, if sensitive authentication data is

### 3.2.d [ref]group

For all other entities, if sensitive authentication data is

### 3.3 [ref]group

Mask PAN when displayed (the first

### 3.3.a [ref]group

Examine written policies and procedures for masking the

### 3.3.b [ref]group

Examine system configurations to verify that full PAN is

### 3.3.c [ref]group

Examine displays of PAN (for example, on screen, on

### 3.4 [ref]group

Render PAN unreadable anywhere it

### 3.4.1 [ref]group

If disk encryption is used (rather

### 3.4.1.a [ref]group

If disk encryption is used, inspect the configuration

### 3.4.1.b [ref]group

Observe processes and interview personnel to verify

### 3.4.1.c [ref]group

Examine the configurations and observe the

### 3.4.a [ref]group

Examine documentation about the system used to protect

### 3.4.b [ref]group

Examine several tables or files from a sample of data

### 3.4.c [ref]group

Examine a sample of removable media (for example,

### 3.4.d [ref]group

Examine a sample of audit logs to confirm that the PAN is

### 3.4.e [ref]group

If

### 3.5 [ref]group

Document and implement

### 3.5.1 [ref]group

Restrict access to cryptographic

### 3.5.2 [ref]group

Store secret and private keys

### 3.5.2.a [ref]group

Examine documented procedures to verify that

### 3.5.2.b [ref]group

Examine system configurations and key storage

### 3.5.2.c [ref]group

Wherever key-encrypting keys are used, examine

### 3.5.3 [ref]group

Store cryptographic keys in the

### 3.6 [ref]group

Fully document and implement all

### 3.6.1 [ref]group

Generation of strong

### 3.6.1.a [ref]group

Verify that key-management procedures specify how

### 3.6.1.b [ref]group

Observe the method for generating keys to verify that

### 3.6.2 [ref]group

Secure cryptographic key

### 3.6.2.a [ref]group

Verify that key-management procedures specify how

### 3.6.2.b [ref]group

Observe the method for distributing keys to verify that

### 3.6.3 [ref]group

Secure cryptographic key storage

### 3.6.3.a [ref]group

Verify that key-management procedures specify how

### 3.6.3.b [ref]group

Observe the method for storing keys to verify that

### 3.6.4 [ref]group

Cryptographic key changes for

### 3.6.4.a [ref]group

Verify that key-management procedures include a

### 3.6.4.b [ref]group

Interview personnel to verify that keys are changed at

### 3.6.5 [ref]group

Retirement or replacement (for

### 3.6.5.a [ref]group

Verify that key-management procedures specify

### 3.6.5.b [ref]group

Interview personnel to verify the following processes

### 3.6.6 [ref]group

If manual clear-text cryptographic

### 3.6.6.a [ref]group

Verify that manual clear-text key-management

### 3.6.7 [ref]group

Prevention of unauthorized

### 3.6.7.a [ref]group

Verify that key-management procedures specify

### 3.6.7.b [ref]group

Interview personnel and/or observe processes to

### 3.6.8 [ref]group

Requirement for cryptographic

### 3.6.8.a [ref]group

Verify that key-management procedures specify

### 3.6.8.b [ref]group

Observe documentation or other evidence showing

### 3.6.b [ref]group

Examine the key-management procedures and processes

### 3.7 [ref]group

Ensure that security policies and

### 4. [ref]group

Encrypt transmission of cardholder data across open, public networks

contains 1 rule

### 4.1 [ref]group

Use strong cryptography and security

contains 1 rule

### 4.1.1 [ref]group

Ensure wireless networks transmitting

### 4.1.a [ref]group

Identify all locations where cardholder data is

### 4.1.b [ref]group

Review documented policies and procedures to verify

### 4.1.c [ref]group

Select and observe a sample of inbound and outbound

### 4.1.d [ref]group

Examine keys and certificates to verify that only

### 4.1.e [ref]group

Examine system configurations to verify that the

### 4.1.f [ref]group

Examine system configurations to verify that the proper

### 4.1.g [ref]group

For TLS implementations, examine system

#### Install libreswan Package [ref]rule

The Libreswan package provides an implementation of IPsec and IKE, which permits the creation of secure tunnels over untrusted networks. 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: medium

References: AC-17, MA-4, SC-9, 1130, 1131, Req-4.1

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable

    function package_command {

    # Load function arguments into local variables
    local package_operation=$1
    local package=$2

    # Check sanity of the input
    if [ $# -ne "2" ]
    then
      echo "Usage: package_command 'install/uninstall' 'rpm_package_name"
      echo "Aborting."
      exit 1
    fi

    # If dnf is installed, use dnf; otherwise, use yum
    if [ -f "/usr/bin/dnf" ] ; then
      install_util="/usr/bin/dnf"
    else
      install_util="/usr/bin/yum"
    fi

    if [ "$package_operation" != 'remove' ] ; then
      # If the rpm is not installed, install the rpm
      if ! /bin/rpm -q --quiet $package; then
        $install_util -y $package_operation $package
      fi
    else
      # If the rpm is installed, uninstall the rpm
      if /bin/rpm -q --quiet $package; then
        $install_util -y $package_operation $package
      fi
    fi

    }

    package_command install libreswan

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable

    - name: Ensure libreswan is installed
      package:
        name="{{item}}"
        state=present
      with_items:
        - libreswan

Remediation Puppet snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable

    include install_libreswan

    class install_libreswan {
      package { 'libreswan':
        ensure => 'installed',
      }
    }

Remediation Anaconda snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable

    package --add=libreswan

### 4.2 [ref]group

Never send unprotected PANs by end-

### 4.2.a [ref]group

If end-user messaging technologies are used to send

### 4.2.b [ref]group

Review written policies to verify the existence of a

### 4.3 [ref]group

Ensure that security policies and

### 5. [ref]group

Protect all systems against malware and regularly update anti-virus

### 5.1 [ref]group

Deploy anti-virus software on all

### 5.1.1 [ref]group

Ensure that anti-virus programs

### 5.1.2 [ref]group

For systems considered to be not

### 5.2 [ref]group

Ensure that all anti-virus mechanisms

### 5.2.a [ref]group

Examine policies and procedures to verify that anti-virus

### 5.2.b [ref]group

Examine anti-virus configurations, including the master

### 5.2.c [ref]group

Examine a sample of system components, including all

### 5.2.d [ref]group

Examine anti-virus configurations, including the master

### 5.3 [ref]group

Ensure that anti-virus mechanisms

### 5.3.a [ref]group

Examine anti-virus configurations, including the master

### 5.3.b [ref]group

Examine anti-virus configurations, including the master

### 5.3.c [ref]group

Interview responsible personnel and observe processes to

### 5.4 [ref]group

Ensure that security policies and

### 6. [ref]group

Develop and maintain secure systems and applications

contains 4 rules

### 6.1 [ref]group

Establish a process to identify security

### 6.1.a [ref]group

Examine policies and procedures to verify that

### 6.1.b [ref]group

Interview responsible personnel and observe

### 6.2 [ref]group

Ensure that all system components and

contains 4 rules

### 6.2.a [ref]group

Examine policies and procedures related to security-

### 6.2.b [ref]group

For a sample of system components and related

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

Changes to software components can have significant effects on the overall security of the operating system. This requirement ensures the software has not been tampered with and that it has been provided by a trusted vendor. The Red Hat GPG key is necessary to cryptographically verify packages are from Red Hat.

Severity: high

References: CM-5(3), SI-7, MA-1(b), 1749, 366, Req-6.2, 1.2.2

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # 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}"))
      GPG_RESULT=$?
      # No CRC error, safe to proceed
      if [ "${GPG_RESULT}" -eq "0" ]
      then
        for ITEM in "${GPG_OUT[@]}"
        do
          # Filter just hexadecimal fingerprints from gpg's output from
          # processing of a key file
          RESULT=$(echo ${ITEM} | sed -n "s/[[:space:]]*Key fingerprint = \(.*\)/\1/p" | tr -s '[:space:]')
          # If fingerprint matches Red Hat's release 2 or auxiliary key import the key
          if [[ ${RESULT} ]] && ([[ ${RESULT} = "${REDHAT_RELEASE_2_FINGERPRINT}" ]] || \
                                 [[ ${RESULT} = "${REDHAT_AUXILIARY_FINGERPRINT}" ]])
          then
            rpm --import "${REDHAT_RELEASE_KEY}"
          fi
        done
      fi
    fi

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

Changes to any software components can have significant effects on the overall security of the operating system. This requirement ensures the software has not been tampered with and that it has been provided by a trusted vendor.
Accordingly, patches, service packs, device drivers, or operating system components must be signed with a certificate recognized and approved by the organization.
Verifying the authenticity of the software prior to installation validates the integrity of the patch or upgrade received from a vendor. This ensures the software has not been tampered with and that it has been provided by a trusted vendor. Self-signed certificates are disallowed by this requirement. Certificates used to verify the software must be from an approved Certificate Authority (CA).

Severity: high

References: CM-5(3), SI-7, MA-1(b), 1749, SRG-OS-000366-GPOS-00153, Req-6.2, 1.2.3

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    sed -i 's/gpgcheck=.*/gpgcheck=1/g' /etc/yum.conf

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

Verifying the authenticity of the software prior to installation validates the integrity of the patch or upgrade received from a vendor. This ensures the software has not been tampered with and that it has been provided by a trusted vendor. Self-signed certificates are disallowed by this requirement. Certificates used to verify the software must be from an approved Certificate Authority (CA).

Severity: high

References: CM-5(3), SI-7, MA-1(b), 1749, 366, Req-6.2

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    sed -i 's/gpgcheck=.*/gpgcheck=1/g' /etc/yum.repos.d/*

#### 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`.
NOTE: U.S. Defense systems are required to be patched within 30 days or sooner as local policy dictates. Rationale:

Installing software updates is a fundamental mitigation against the exploitation of publicly-known vulnerabilities. If the most recent security patches and updates are not installed, unauthorized users may take advantage of weaknesses in the unpatched software. The lack of prompt attention to patching could result in a system compromise.

Severity: high

References: SI-2, SI-2(c), MA-1(b), 366, Req-6.2, 1.7, SRG-OS-000480-GPOS-00227

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    yum -y update

### 6.3 [ref]group

Develop internal and external software

### 6.3.1 [ref]group

Remove development, test and/or

### 6.3.2 [ref]group

Review custom code prior to release

### 6.3.2.a [ref]group

Examine written software-development procedures

### 6.3.2.b [ref]group

Select a sample of recent custom application

### 6.3.a [ref]group

Examine written software-development processes to

### 6.3.b [ref]group

Examine written software-development processes to

### 6.3.c [ref]group

Examine written software-development processes to

### 6.3.d [ref]group

Interview software developers to verify that written

### 6.4 [ref]group

Follow change control processes and

### 6.4.1 [ref]group

Separate development/test

### 6.4.1.a [ref]group

Examine network documentation and network

### 6.4.1.b [ref]group

Examine access controls settings to verify that

### 6.4.2 [ref]group

Separation of duties between

### 6.4.3 [ref]group

Production data (live PANs) are not

### 6.4.3.a [ref]group

Observe testing processes and interview

### 6.4.3.b [ref]group

Examine a sample of test data to verify production

### 6.4.4 [ref]group

Removal of test data and accounts

### 6.4.4.a [ref]group

Observe testing processes and interview

### 6.4.4.b [ref]group

Examine a sample of data and accounts from

### 6.4.5 [ref]group

Change control procedures for the

### 6.4.5.a [ref]group

Examine documented change control procedures

### 6.4.5.b [ref]group

For a sample of system components, interview

### 6.5 [ref]group

Address common coding vulnerabilities in

### 6.5.1 [ref]group

Injection flaws, particularly SQL

### 6.5.10 [ref]group

Broken authentication and session

### 6.5.2 [ref]group

Buffer overflows

### 6.5.3 [ref]group

Insecure cryptographic storage

### 6.5.4 [ref]group

Insecure communications

### 6.5.5 [ref]group

Improper error handling

### 6.5.6 [ref]group

Examine software-development policies and

### 6.5.7 [ref]group

Cross-site scripting (XSS)

### 6.5.8 [ref]group

Improper access control (such as

### 6.5.9 [ref]group

Cross-site request forgery (CSRF)

### 6.5.a [ref]group

Examine software-development policies and

### 6.5.b [ref]group

Interview a sample of developers to verify that they are

### 6.5.c [ref]group

Examine records of training to verify that software

### 6.6 [ref]group

For public-facing web applications,

### 6.7 [ref]group

Ensure that security policies and

### 7. [ref]group

Restrict access to cardholder data by business need to know

contains 2 rules

### 7.1 [ref]group

Limit access to system

contains 2 rules

### 7.1.1 [ref]group

Define access needs for

### 7.1.2 [ref]group

Restrict access to privileged

### 7.1.2.a [ref]group

Interview personnel responsible for assigning access to

### 7.1.2.b [ref]group

Select a sample of user IDs with privileged access and

### 7.1.3 [ref]group

Assign access based on

### 7.1.4 [ref]group

Require documented

#### Verify /boot/grub2/grub.cfg User Ownership [ref]rule

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

    $ sudo chown root /boot/grub2/grub.cfg

Rationale:

Only root should be able to modify important boot parameters.

Severity: medium

References: AC-6(7), 225, Req-7.1, 1.5.1

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    chown root /boot/grub2/grub.cfg

#### Verify /boot/grub2/grub.cfg Group Ownership [ref]rule

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

    $ sudo chgrp root /boot/grub2/grub.cfg

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: AC-6(7), 225, Req-7.1, 1.5.1

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    chgrp root /boot/grub2/grub.cfg

### 7.2 [ref]group

Establish an access control

### 7.2.1 [ref]group

Coverage of all system

### 7.2.2 [ref]group

Assignment of privileges to

### 7.2.3 [ref]group

### 7.3 [ref]group

Ensure that security policies and

### 8. [ref]group

Identify and authenticate access to system components

contains 31 rules

### 8.1 [ref]group

Define and implement policies and

contains 9 rules

### 8.1.1 [ref]group

Assign all users a unique ID

contains 1 rule

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

References: 770, 804, Req-8.1.1

### 8.1.2 [ref]group

Control addition, deletion, and

### 8.1.3 [ref]group

Immediately revoke access for

### 8.1.3.a [ref]group

Select a sample of users terminated in the past six

### 8.1.3.b [ref]group

Verify all physical authentication methods

### 8.1.4 [ref]group

Remove/disable inactive user

contains 1 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: medium

References: AC-2(2), AC-2(3), IA-4(e), 795, SRG-OS-000118-GPOS-00060, Req-8.1.4

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    var_account_disable_post_pw_expiration="90"

    function replace_or_append {
      local config_file=$1
      local key=$2
      local value=$3
      local cce=$4
      local format=$5

      # Check sanity of the input
      if [ $# -lt "3" ]
      then
            echo "Usage: replace_or_append 'config_file_location' 'key_to_search' 'new_value'"
            echo
            echo "If symlinks need to be taken into account, add yes/no to the last argument"
            echo "to allow to 'follow_symlinks'."
            echo "Aborting."
            exit 1
      fi

      # Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
      # Otherwise, regular sed command will do.
      if test -L $config_file; then
        sed_command="sed -i --follow-symlinks"
      else
        sed_command="sed -i"
      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 ! [ "x$cce" = x ] && [ "$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)

      # If there is no print format specified in the last arg, use the default format.
      if ! [ "x$format" = x ] ; then
        printf -v formatted_output "$format" $stripped_key $value
      else
        formatted_output="$stripped_key = $value"
      fi

      # If the key exists, change it. Otherwise, add it to the config_file.
      if `grep -qi $key $config_file` ; then
        $sed_command "s/$key.*/$formatted_output/g" $config_file
      else
        echo -ne "\n# Per $cce: Set $formatted_output in $config_file" >> $config_file
        echo -ne "\n$formatted_output" >> $config_file
      fi

    }

    replace_or_append /etc/default/useradd INACTIVE "$var_account_disable_post_pw_expiration" '' '%s=%s'

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    - name: "Disable POST password expiration"
      lineinfile:
        create=yes
        dest="/etc/default/useradd"
        regexp="^INACTIVE"
        line="INACTIVE=-1"

### 8.1.5 [ref]group

Manage IDs used by vendors to

### 8.1.5.a [ref]group

Interview personnel and observe processes for

### 8.1.5.b [ref]group

Interview personnel and observe processes to verify

### 8.1.6 [ref]group

Limit repeated access attempts

contains 1 rule

### 8.1.6.a [ref]group

For a sample of system components, inspect system

### 8.1.6.b [ref]group

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

- 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=900

- 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: AC-7(b), 2238, SRG-OS-000329-GPOS-00128, SRG-OS-000021-GPOS-00005, Req-8.1.6, 6.3.3

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    var_accounts_passwords_pam_faillock_deny="6"

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

    for pamFile in "${AUTH_FILES[@]}"
    do

    	# pam_faillock.so already present?
    	if grep -q "^auth.*pam_faillock.so.*" $pamFile; then

    		# pam_faillock.so present, deny directive present?
    		if grep -q "^auth.*[default=die].*pam_faillock.so.*authfail.*deny=" $pamFile; then

    			# both pam_faillock.so & deny present, just correct deny directive value
    			sed -i --follow-symlinks "s/\(^auth.*required.*pam_faillock.so.*preauth.*silent.*\)\(deny *= *\).*/\1\2$var_accounts_passwords_pam_faillock_deny/" $pamFile
    			sed -i --follow-symlinks "s/\(^auth.*[default=die].*pam_faillock.so.*authfail.*\)\(deny *= *\).*/\1\2$var_accounts_passwords_pam_faillock_deny/" $pamFile

    		# pam_faillock.so present, but deny directive not yet
    		else

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

    	# pam_faillock.so not present yet
    	else

    		# insert pam_faillock.so preauth & authfail rows with proper value of the 'deny' option
    		sed -i --follow-symlinks "/^auth.*sufficient.*pam_unix.so.*/i auth        required      pam_faillock.so preauth silent deny=$var_accounts_passwords_pam_faillock_deny" $pamFile
    		sed -i --follow-symlinks "/^auth.*sufficient.*pam_unix.so.*/a auth        [default=die] pam_faillock.so authfail deny=$var_accounts_passwords_pam_faillock_deny" $pamFile
    		sed -i --follow-symlinks "/^account.*required.*pam_unix.so/i account     required      pam_faillock.so" $pamFile
    	fi
    done

### 8.1.7 [ref]group

Set the lockout duration to a

contains 1 rule

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

- 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=900

- 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: AC-7(b), 002238, SRG-OS-000329-GPOS-00128, SRG-OS-000021-GPOS-00005, Req-8.1.7, 6.3.3

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    var_accounts_passwords_pam_faillock_unlock_time="1800"

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

    for pamFile in "${AUTH_FILES[@]}"
    do

    	# pam_faillock.so already present?
    	if grep -q "^auth.*pam_faillock.so.*" $pamFile; then

    		# pam_faillock.so present, unlock_time directive present?
    		if grep -q "^auth.*[default=die].*pam_faillock.so.*authfail.*unlock_time=" $pamFile; then

    			# both pam_faillock.so & unlock_time present, just correct unlock_time directive value
    			sed -i --follow-symlinks "s/\(^auth.*required.*pam_faillock.so.*preauth.*silent.*\)\(unlock_time *= *\).*/\1\2$var_accounts_passwords_pam_faillock_unlock_time/" $pamFile
    			sed -i --follow-symlinks "s/\(^auth.*[default=die].*pam_faillock.so.*authfail.*\)\(unlock_time *= *\).*/\1\2$var_accounts_passwords_pam_faillock_unlock_time/" $pamFile

    		# pam_faillock.so present, but unlock_time directive not yet
    		else

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

    	# pam_faillock.so not present yet
    	else

    		# insert pam_faillock.so preauth & authfail rows with proper value of the 'unlock_time' option
    		sed -i --follow-symlinks "/^auth.*sufficient.*pam_unix.so.*/i auth        required      pam_faillock.so preauth silent unlock_time=$var_accounts_passwords_pam_faillock_unlock_time" $pamFile
    		sed -i --follow-symlinks "/^auth.*sufficient.*pam_unix.so.*/a auth        [default=die] pam_faillock.so authfail unlock_time=$var_accounts_passwords_pam_faillock_unlock_time" $pamFile
    		sed -i --follow-symlinks "/^account.*required.*pam_unix.so/i account     required      pam_faillock.so" $pamFile
    	fi
    done

### 8.1.8 [ref]group

If a session has been idle for

contains 5 rules

#### Set GNOME3 Screensaver Inactivity Timeout [ref]rule

The idle time-out value for inactivity in the GNOME3 desktop is configured via the `idle-delay` setting must be set under an appropriate configuration file(s) in the `/etc/dconf/db/local.d` directory and locked in `/etc/dconf/db/local.d/locks` directory to prevent user modification.
For example, to configure the system for a 15 minute delay, add the following to `/etc/dconf/db/local.d/00-security-settings`:

    [org/gnome/desktop/session]
    idle-delay=900

Once the setting has been added, add a lock to `/etc/dconf/db/local.d/locks/00-security-settings-lock` to prevent user modification. For example:

    /org/gnome/desktop/session/idle-delay

After the settings have been set, run `dconf update`. Rationale:

A session time-out lock is a temporary action taken when a user stops work and moves away from the immediate physical vicinity of the information system but does not logout because of the temporary nature of the absence. Rather than relying on the user to manually lock their operating system session prior to vacating the vicinity, GNOME3 can be configured to identify when a user's session has idled and take action to initiate a session lock.

Severity: medium

References: AC-11(a), 57, Req-8.1.8, SRG-OS-000029-GPOS-00010

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    inactivity_timeout_value="900"

    # Define constants to be reused below
    ORG_GNOME_DESKTOP_SESSION="org/gnome/desktop/session"
    SSG_DCONF_IDLE_DELAY_FILE="/etc/dconf/db/local.d/10-scap-security-guide"
    SESSION_LOCKS_FILE="/etc/dconf/db/local.d/locks/session"
    IDLE_DELAY_DEFINED="FALSE"

    # First update '[org/gnome/desktop/session] idle-delay' settings in
    # /etc/dconf/db/local.d/* if already defined
    for FILE in /etc/dconf/db/local.d/*
    do
    	if grep -q -d skip "$ORG_GNOME_DESKTOP_SESSION" "$FILE"
    	then
    		if grep 'idle-delay' "$FILE"
    		then
    			sed -i "s/idle-delay=.*/idle-delay=uint32 ${inactivity_timeout_value}/g" "$FILE"
    			IDLE_DELAY_DEFINED="TRUE"
    		fi
    	fi
    done

    # Then define '[org/gnome/desktop/session] idle-delay' setting
    # if still not defined yet
    if [ "$IDLE_DELAY_DEFINED" != "TRUE" ]
    then
    	echo "" >> $SSG_DCONF_IDLE_DELAY_FILE
    	echo "[org/gnome/desktop/session]" >>  $SSG_DCONF_IDLE_DELAY_FILE
    	echo "idle-delay=uint32 ${inactivity_timeout_value}" >> $SSG_DCONF_IDLE_DELAY_FILE
    fi

    # Verify if 'idle-delay' modification is locked. If not, lock it
    if ! grep -q "^/${ORG_GNOME_DESKTOP_SESSION}/idle-delay$" /etc/dconf/db/local.d/locks/*
    then
    	# Check if "$SESSION_LOCK_FILE" exists. If not, create it.
    	if [ ! -f "$SESSION_LOCKS_FILE" ]
    	then
    		touch "$SESSION_LOCKS_FILE"
    	fi
    	echo "/${ORG_GNOME_DESKTOP_SESSION}/idle-delay" >> "$SESSION_LOCKS_FILE"
    fi

#### Enable GNOME3 Screensaver Idle Activation [ref]rule

To activate the screensaver in the GNOME3 desktop after a period of inactivity, add or set `idle-activation-enabled` to `true` in `/etc/dconf/db/local.d/00-security-settings`. For example:

    [org/gnome/desktop/screensaver]
    idle_activation_enabled=true

Once the setting has been added, add a lock to `/etc/dconf/db/gdm.d/locks/00-security-settings-lock` to prevent user modification. For example:

    /org/gnome/desktop/screensaver/idle-activation-enabled

After the settings have been set, run `dconf update`. Rationale:

A session time-out lock is a temporary action taken when a user stops work and moves away from the immediate physical vicinity of the information system but does not logout because of the temporary nature of the absence. Rather than relying on the user to manually lock their operating system session prior to vacating the vicinity, GNOME desktops can be configured to identify when a user's session has idled and take action to initiate the session lock. 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: AC-11(a), 57, SRG-OS-000029-GPOS-00010, Req-8.1.8

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # Define constants to be reused below
    ORG_GNOME_DESKTOP_SCREENSAVER="org/gnome/desktop/screensaver"
    SSG_DCONF_IDLE_ACTIVATION_FILE="/etc/dconf/db/local.d/10-scap-security-guide"
    SCREENSAVER_LOCKS_FILE="/etc/dconf/db/local.d/locks/screensaver"
    IDLE_ACTIVATION_DEFINED="FALSE"

    # First update '[org/gnome/desktop/screensaver] idle-activation-enabled' settings in
    # /etc/dconf/db/local.d/* if already defined
    for FILE in /etc/dconf/db/local.d/*
    do
    	if grep -q -d skip "$ORG_GNOME_DESKTOP_SCREENSAVER" "$FILE"
    	then
    		if grep 'idle-activation-enabled' "$FILE"
    		then
    			sed -i "s/idle-activation-enabled=.*/idle-activation-enabled=true/g" "$FILE"
    			IDLE_ACTIVATION_DEFINED="TRUE"
    		fi
    	fi
    done

    # Then define '[org/gnome/desktop/screensaver] idle-activation-enabled' setting
    # if still not defined yet
    if [ "$IDLE_ACTIVATION_DEFINED" != "TRUE" ]
    then
    	echo "" >> $SSG_DCONF_IDLE_ACTIVATION_FILE
    	echo "[org/gnome/desktop/screensaver]" >>  $SSG_DCONF_IDLE_ACTIVATION_FILE
    	echo "idle-activation-enabled=true" >> $SSG_DCONF_IDLE_ACTIVATION_FILE
    fi

    # Verify if 'idle-activation-enabled' modification is locked. If not, lock it
    if ! grep -q "^/${ORG_GNOME_DESKTOP_SCREENSAVER}/idle-activation-enabled$" /etc/dconf/db/local.d/locks/*
    then
    	# Check if "$SCREENSAVER_LOCK_FILE" exists. If not, create it.
    	if [ ! -f "$SCREENSAVER_LOCKS_FILE" ]
    	then
    		touch "$SCREENSAVER_LOCKS_FILE"
    	fi
    	echo "/${ORG_GNOME_DESKTOP_SCREENSAVER}/idle-activation-enabled" >> "$SCREENSAVER_LOCKS_FILE"
    fi

#### Enable GNOME3 Screensaver Lock After Idle Period [ref]rule

To activate locking of the screensaver in the GNOME3 desktop when it is activated, add or set `lock-enabled` to `true` in `/etc/dconf/db/local.d/00-security-settings`. For example:

    [org/gnome/desktop/screensaver]
    lock-enabled=true

Once the settings have been added, add a lock to `/etc/dconf/db/gdm.d/locks/00-security-settings-lock` to prevent user modification. For example:

    /org/gnome/desktop/screensaver/lock-enabled

After the settings have been set, run `dconf update`. Rationale:

A session lock is a temporary action taken when a user stops work and moves away from the immediate physical vicinity of the information system but does not want to logout because of the temporary nature of the absense.

Severity: medium

References: AC-11(b), 56, Req-8.1.8, SRG-OS-000028-GPOS-00009, OS-SRG-000030-GPOS-00011

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # Define constants to be reused below
    ORG_GNOME_DESKTOP_SCREENSAVER="org/gnome/desktop/screensaver"
    SSG_DCONF_LOCK_ENABLED_FILE="/etc/dconf/db/local.d/10-scap-security-guide"
    SCREENSAVER_LOCKS_FILE="/etc/dconf/db/local.d/locks/screensaver"
    LOCK_ENABLED_DEFINED="FALSE"
    LOCK_DELAY_DEFINED="FALSE"

    # First update '[org/gnome/desktop/screensaver] lock-enabled' and
    # '[org/gnome/desktop/screensaver] lock-delay' settings in
    # /etc/dconf/db/local.d/* if already defined
    for FILE in /etc/dconf/db/local.d/*
    do
    	if grep -q -d skip "$ORG_GNOME_DESKTOP_SCREENSAVER" "$FILE"
    	then
    		if grep 'lock-enabled' "$FILE"
    		then
    			sed -i "s/lock-enabled=.*/lock-enabled=true/g" "$FILE"
    			LOCK_ENABLED_DEFINED="TRUE"
    		fi
    		if grep 'lock-delay' "$FILE"
    		then
    			sed -i "s/lock-delay=.*/lock-delay=uint32 0/g" "$FILE"
    			LOCK_DELAY_DEFINED="TRUE"
    		fi
    	fi
    done

    # Then define '[org/gnome/desktop/screensaver] lock-enabled' setting
    # if still not defined yet
    if [ "$LOCK_ENABLED_DEFINED" != "TRUE" ] || [ "$LOCK_DELAY_DEFINED" != "TRUE" ]
    then
    	echo "" >> $SSG_DCONF_LOCK_ENABLED_FILE
    	echo "[org/gnome/desktop/screensaver]" >>  $SSG_DCONF_LOCK_ENABLED_FILE
    	echo "lock-enabled=true" >> $SSG_DCONF_LOCK_ENABLED_FILE
    	echo "lock-delay=uint32 0" >> $SSG_DCONF_LOCK_ENABLED_FILE
    fi

    # Verify if 'lock-enabled' modification is locked. If not, lock it
    if ! grep -q "^/${ORG_GNOME_DESKTOP_SCREENSAVER}/lock-enabled$" /etc/dconf/db/local.d/locks/*
    then
    	# Check if "$SCREENSAVER_LOCK_FILE" exists. If not, create it.
    	if [ ! -f "$SCREENSAVER_LOCKS_FILE" ]
    	then
    		touch "$SCREENSAVER_LOCKS_FILE"
    	fi
    	echo "/${ORG_GNOME_DESKTOP_SCREENSAVER}/lock-enabled" >> "$SCREENSAVER_LOCKS_FILE"
    fi

    # Verify if 'lock-delay' modification is locked. If not, lock it
    if ! grep -q "^/${ORG_GNOME_DESKTOP_SCREENSAVER}/lock-delay$" /etc/dconf/db/local.d/locks/*
    then
            # Check if "$SCREENSAVER_LOCK_FILE" exists. If not, create it.
            if [ ! -f "$SCREENSAVER_LOCKS_FILE" ]
            then
                    touch "$SCREENSAVER_LOCKS_FILE"
            fi
            echo "/${ORG_GNOME_DESKTOP_SCREENSAVER}/lock-delay" >> "$SCREENSAVER_LOCKS_FILE"
    fi

#### Implement Blank Screensaver [ref]rule

To set the screensaver mode in the GNOME3 desktop to a blank screen, add or set `picture-uri` to `''` in `/etc/dconf/db/local.d/00-security-settings`. For example:

    [org/gnome/desktop/screensaver]
    picture-uri=''

Once the settings have been added, add a lock to `/etc/dconf/db/gdm.d/locks/00-security-settings-lock` to prevent user modification. For example:

    /org/gnome/desktop/screensaver/picture-uri

After the settings have been set, run `dconf update`. Rationale:

Setting the screensaver mode to blank-only conceals the contents of the display from passersby.

Severity: low

References: AC-11(b), 60, Req-8.1.8

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # Define constants to be reused below
    ORG_GNOME_DESKTOP_SCREENSAVER="org/gnome/desktop/screensaver"
    SSG_DCONF_MODE_BLANK_FILE="/etc/dconf/db/local.d/10-scap-security-guide"
    SCREENSAVER_LOCKS_FILE="/etc/dconf/db/local.d/locks/screensaver"
    MODE_BLANK_DEFINED="FALSE"

    # First update '[org/gnome/desktop/screensaver] picture-uri' settings in
    # /etc/dconf/db/local.d/* if already defined
    for FILE in /etc/dconf/db/local.d/*
    do
    	if grep -q -d skip "$ORG_GNOME_DESKTOP_SCREENSAVER" "$FILE"
    	then
    		if grep 'picture-uri' "$FILE"
    		then
    			sed -i "s/picture-uri=.*/picture-uri=string ''/g" "$FILE"
    			MODE_BLANK_DEFINED="TRUE"
    		fi
    	fi
    done

    # Then define '[org/gnome/desktop/screensaver] picture-uri' setting
    # if still not defined yet
    if [ "$MODE_BLANK_DEFINED" != "TRUE" ]
    then
    	echo "" >> $SSG_DCONF_MODE_BLANK_FILE
    	echo "[org/gnome/desktop/screensaver]" >>  $SSG_DCONF_MODE_BLANK_FILE
    	echo "picture-uri=string ''" >> $SSG_DCONF_MODE_BLANK_FILE
    fi

    # Verify if 'picture-uri' modification is locked. If not, lock it
    if ! grep -q "^/${ORG_GNOME_DESKTOP_SCREENSAVER}/picture-uri$" /etc/dconf/db/local.d/locks/*
    then
    	# Check if "$SCREENSAVER_LOCK_FILE" exists. If not, create it.
    	if [ ! -f "$SCREENSAVER_LOCKS_FILE" ]
    	then
    		touch "$SCREENSAVER_LOCKS_FILE"
    	fi
    	echo "/${ORG_GNOME_DESKTOP_SCREENSAVER}/picture-uri" >> "$SCREENSAVER_LOCKS_FILE"
    fi

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

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

Terminating an idle ssh session within a short time period reduces the window of opportunity for unauthorized personnel to take control of a management session enabled on the console or console port that has been let unattended.

Severity: low

References: AC-2(5), SA-8(i), AC-12, 1133, 2361, SRG-OS-000163-GPOS-00072, SRG-OS-000279-GPOS-00109, Req-8.1.8, 6.2.12

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    sshd_idle_timeout_value="900"

    function replace_or_append {
      local config_file=$1
      local key=$2
      local value=$3
      local cce=$4
      local format=$5

      # Check sanity of the input
      if [ $# -lt "3" ]
      then
            echo "Usage: replace_or_append 'config_file_location' 'key_to_search' 'new_value'"
            echo
            echo "If symlinks need to be taken into account, add yes/no to the last argument"
            echo "to allow to 'follow_symlinks'."
            echo "Aborting."
            exit 1
      fi

      # Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
      # Otherwise, regular sed command will do.
      if test -L $config_file; then
        sed_command="sed -i --follow-symlinks"
      else
        sed_command="sed -i"
      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 ! [ "x$cce" = x ] && [ "$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)

      # If there is no print format specified in the last arg, use the default format.
      if ! [ "x$format" = x ] ; then
        printf -v formatted_output "$format" $stripped_key $value
      else
        formatted_output="$stripped_key = $value"
      fi

      # If the key exists, change it. Otherwise, add it to the config_file.
      if `grep -qi $key $config_file` ; then
        $sed_command "s/$key.*/$formatted_output/g" $config_file
      else
        echo -ne "\n# Per $cce: Set $formatted_output in $config_file" >> $config_file
        echo -ne "\n$formatted_output" >> $config_file
      fi

    }

    replace_or_append '/etc/ssh/sshd_config' '^ClientAliveInterval' $sshd_idle_timeout_value 'CCE-27433-2' '%s %s'

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    - name: "SSHD: Set client alive interval"
      lineinfile:
        create=yes
        dest="/etc/ssh/sshd_config"
        regexp="^ClientAliveInterval"
        line="ClientAliveInterval 900"

### 8.1.a [ref]group

Review procedures and confirm they define processes for

### 8.1.b [ref]group

Verify that procedures are implemented for user

### 8.2 [ref]group

In addition to assigning a unique ID,

contains 11 rules

### 8.2.1 [ref]group

Using strong cryptography,

contains 4 rules

### 8.2.1.a [ref]group

Examine vendor documentation and system

### 8.2.1.b [ref]group

For a sample of system components, examine

### 8.2.1.c [ref]group

For a sample of system components, examine data

### 8.2.1.d [ref]group

#### 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: IA-5(h), http://iase.disa.mil/stigs/cci/Pages/index.aspx, Req-8.2.1

#### Set PAM's Password Hashing Algorithm [ref]rule

The PAM system service can be configured to only store encrypted representations of passwords. 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:

Passwords need to be protected at all times, and encryption is the standard method for protecting passwords. If passwords are not encrypted, they can be plainly read (i.e., clear text) and easily compromised. Passwords that are encrypted with a weak algorithm are no more protected than if they are kepy in plain text.
This setting ensures user and group account administration utilities are configured to store only encrypted representations of passwords. Additionally, the `crypt_style` configuration option ensures the use of a strong hashing algorithm that makes password cracking attacks more difficult.

Severity: medium

References: IA-5(b), IA-5(c), IA-5(1)(c), IA-7, 196, SRG-OS-000073-GPOS-00041, Req-8.2.1, 6.3.1

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    if ! grep -q "^password.*sufficient.*pam_unix.so.*sha512" /etc/pam.d/system-auth; then
    	sed -i --follow-symlinks "/^password.*sufficient.*pam_unix.so/ s/$/ sha512/" /etc/pam.d/system-auth
    fi

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

Passwords need to be protected at all times, and encryption is the standard method for protecting passwords. If passwords are not encrypted, they can be plainly read (i.e., clear text) and easily compromised. Passwords that are encrypted with a weak algorithm are no more protected than if they are kept in plain text.
Using a stronger hashing algorithm makes password cracking attacks more difficult.

Severity: medium

References: IA-5(b), IA-5(c), IA-5(1)(c), IA-7, 196, SRG-OS-000073-GPOS-00041, Req-8.2.1, 6.3.1

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    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

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

Passwords need to be protected at all times, and encryption is the standard method for protecting passwords. If passwords are not encrypted, they can be plainly read (i.e., clear text) and easily compromised. Passwords that are encrypted with a weak algorithm are no more protected than if they are kepy in plain text.
This setting ensures user and group account administration utilities are configured to store only encrypted representations of passwords. Additionally, the `crypt_style` configuration option ensures the use of a strong hashing algorithm that makes password cracking attacks more difficult.

Severity: medium

References: IA-5(b), IA-5(c), IA-5(1)(c), IA-7, 196, SRG-OS-000073-GPOS-00041, Req-8.2.1

### 8.2.2 [ref]group

Verify user identity before

### 8.2.3 [ref]group

Passwords/phrases must meet

contains 5 rules

### 8.2.3.a [ref]group

For a sample of system components, inspect system

### 8.2.3.b [ref]group

#### 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 into 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: AC-6, IA-5(b), IA-5(c), IA-5(1)(a), 366, SRG-OS-000480-GPOS-00227, Req-8.2.3

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

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

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

The pam_pwquality module's `dcredit` parameter controls requirements for usage of digits in a password. When set to a negative number, any password will be required to contain that many digits. When set to a positive number, pam_pwquality will grant +1 additional length credit for each digit. Modify the `dcredit` setting in `/etc/security/pwquality.conf` to require the use of a digit in passwords.

Rationale:

Use of a complex password helps to increase the time and resources required to compromise the password. Password complexity, or strength, is a measure of the effectiveness of a password in resisting attempts at guessing and brute-force attacks. Password complexity is one factor of several that determines how long it takes to crack a password. The more complex the password, the greater the number of possble combinations that need to be tested before the password is compromised. Requiring digits makes password guessing attacks more difficult by ensuring a larger search space.

Severity: medium

References: IA-5(1)(a), IA-5(b), IA-5(c), 194, 194, SRG-OS-000071-GPOS-00039, Req-8.2.3, 6.3.2

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure

    var_password_pam_dcredit="-1"

    function replace_or_append {
      local config_file=$1
      local key=$2
      local value=$3
      local cce=$4
      local format=$5

      # Check sanity of the input
      if [ $# -lt "3" ]
      then
            echo "Usage: replace_or_append 'config_file_location' 'key_to_search' 'new_value'"
            echo
            echo "If symlinks need to be taken into account, add yes/no to the last argument"
            echo "to allow to 'follow_symlinks'."
            echo "Aborting."
            exit 1
      fi

      # Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
      # Otherwise, regular sed command will do.
      if test -L $config_file; then
        sed_command="sed -i --follow-symlinks"
      else
        sed_command="sed -i"
      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 ! [ "x$cce" = x ] && [ "$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)

      # If there is no print format specified in the last arg, use the default format.
      if ! [ "x$format" = x ] ; then
        printf -v formatted_output "$format" $stripped_key $value
      else
        formatted_output="$stripped_key = $value"
      fi

      # If the key exists, change it. Otherwise, add it to the config_file.
      if `grep -qi $key $config_file` ; then
        $sed_command "s/$key.*/$formatted_output/g" $config_file
      else
        echo -ne "\n# Per $cce: Set $formatted_output in $config_file" >> $config_file
        echo -ne "\n$formatted_output" >> $config_file
      fi

    }

    replace_or_append '/etc/security/pwquality.conf' '^dcredit' $var_password_pam_dcredit 'CCE-27214-6' '%s = %s'

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

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

The shorter the password, the lower the number of possible combinations that need to be tested before the password is compromised.
Password complexity, or strength, is a measure of the effectiveness of a password in resisting attempts at guessing and brute-force attacks. Password length is one factor of several that helps to determine strength and how long it takes to crack a password. Use of more characters in a password helps to exponentially increase the time and/or resources required to compromose the password.

Severity: medium

References: IA-5(1)(a), 205, SRG-OS-000078-GPOS-00046, Req-8.2.3, 6.3.2

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure

    var_password_pam_minlen="7"

    function replace_or_append {
      local config_file=$1
      local key=$2
      local value=$3
      local cce=$4
      local format=$5

      # Check sanity of the input
      if [ $# -lt "3" ]
      then
            echo "Usage: replace_or_append 'config_file_location' 'key_to_search' 'new_value'"
            echo
            echo "If symlinks need to be taken into account, add yes/no to the last argument"
            echo "to allow to 'follow_symlinks'."
            echo "Aborting."
            exit 1
      fi

      # Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
      # Otherwise, regular sed command will do.
      if test -L $config_file; then
        sed_command="sed -i --follow-symlinks"
      else
        sed_command="sed -i"
      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 ! [ "x$cce" = x ] && [ "$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)

      # If there is no print format specified in the last arg, use the default format.
      if ! [ "x$format" = x ] ; then
        printf -v formatted_output "$format" $stripped_key $value
      else
        formatted_output="$stripped_key = $value"
      fi

      # If the key exists, change it. Otherwise, add it to the config_file.
      if `grep -qi $key $config_file` ; then
        $sed_command "s/$key.*/$formatted_output/g" $config_file
      else
        echo -ne "\n# Per $cce: Set $formatted_output in $config_file" >> $config_file
        echo -ne "\n$formatted_output" >> $config_file
      fi

    }

    replace_or_append '/etc/security/pwquality.conf' '^minlen' $var_password_pam_minlen 'CCE-27293-0' '%s = %s'

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

The pam_pwquality module's `ucredit=` parameter controls requirements for usage of uppercase letters in a password. When set to a negative number, any password will be required to contain that many uppercase characters. When set to a positive number, pam_pwquality will grant +1 additional length credit for each uppercase character. Modify the `ucredit` setting in `/etc/security/pwquality.conf` to require the use of an uppercase character in passwords.

Rationale:

Use of a complex password helps to increase the time and resources reuiqred to compromise the password. Password complexity, or strength, is a measure of the effectiveness of a password in resisting attempts at guessing and brute-force attacks.
Password complexity is one factor of several that determines how long it takes to crack a password. The more complex the password, the greater the number of possible combinations that need to be tested before the password is compromised.

Severity: medium

References: IA-5(b), IA-5(c), IA-5(1)(a), 192, SRG-OS-000069-GPOS-00037, Req-8.2.3, 6.3.2

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    var_password_pam_ucredit="-1"

    function replace_or_append {
      local config_file=$1
      local key=$2
      local value=$3
      local cce=$4
      local format=$5

      # Check sanity of the input
      if [ $# -lt "3" ]
      then
            echo "Usage: replace_or_append 'config_file_location' 'key_to_search' 'new_value'"
            echo
            echo "If symlinks need to be taken into account, add yes/no to the last argument"
            echo "to allow to 'follow_symlinks'."
            echo "Aborting."
            exit 1
      fi

      # Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
      # Otherwise, regular sed command will do.
      if test -L $config_file; then
        sed_command="sed -i --follow-symlinks"
      else
        sed_command="sed -i"
      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 ! [ "x$cce" = x ] && [ "$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)

      # If there is no print format specified in the last arg, use the default format.
      if ! [ "x$format" = x ] ; then
        printf -v formatted_output "$format" $stripped_key $value
      else
        formatted_output="$stripped_key = $value"
      fi

      # If the key exists, change it. Otherwise, add it to the config_file.
      if `grep -qi $key $config_file` ; then
        $sed_command "s/$key.*/$formatted_output/g" $config_file
      else
        echo -ne "\n# Per $cce: Set $formatted_output in $config_file" >> $config_file
        echo -ne "\n$formatted_output" >> $config_file
      fi

    }

    replace_or_append '/etc/security/pwquality.conf' '^ucredit' $var_password_pam_ucredit 'CCE-27200-5' '%s = %s'

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

The pam_pwquality module's `lcredit` parameter controls requirements for usage of lowercase letters in a password. When set to a negative number, any password will be required to contain that many lowercase characters. When set to a positive number, pam_pwquality will grant +1 additional length credit for each lowercase character. Modify the `lcredit` setting in `/etc/security/pwquality.conf` to require the use of a lowercase character in passwords.

Rationale:

Use of a complex password helps to increase the time and resources required to compromise the password. Password complexity, or strength, is a measure of the effectiveness of a password in resisting attempts at guessing and brute-force attacks.
Password complexity is one factor of several that determines how long it takes to crack a password. The more complex the password, the greater the number of possble combinations that need to be tested before the password is compromised. Requiring a minimum number of lowercase characters makes password guessing attacks more difficult by ensuring a larger search space.

Severity: medium

References: IA-5(b), IA-5(c), IA-5(1)(a), 193, SRG-OS-000070-GPOS-00038, Req-8.2.3

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    var_password_pam_lcredit="-1"

    function replace_or_append {
      local config_file=$1
      local key=$2
      local value=$3
      local cce=$4
      local format=$5

      # Check sanity of the input
      if [ $# -lt "3" ]
      then
            echo "Usage: replace_or_append 'config_file_location' 'key_to_search' 'new_value'"
            echo
            echo "If symlinks need to be taken into account, add yes/no to the last argument"
            echo "to allow to 'follow_symlinks'."
            echo "Aborting."
            exit 1
      fi

      # Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
      # Otherwise, regular sed command will do.
      if test -L $config_file; then
        sed_command="sed -i --follow-symlinks"
      else
        sed_command="sed -i"
      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 ! [ "x$cce" = x ] && [ "$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)

      # If there is no print format specified in the last arg, use the default format.
      if ! [ "x$format" = x ] ; then
        printf -v formatted_output "$format" $stripped_key $value
      else
        formatted_output="$stripped_key = $value"
      fi

      # If the key exists, change it. Otherwise, add it to the config_file.
      if `grep -qi $key $config_file` ; then
        $sed_command "s/$key.*/$formatted_output/g" $config_file
      else
        echo -ne "\n# Per $cce: Set $formatted_output in $config_file" >> $config_file
        echo -ne "\n$formatted_output" >> $config_file
      fi

    }

    replace_or_append '/etc/security/pwquality.conf' '^lcredit' $var_password_pam_lcredit 'CCE-27345-8' '%s = %s'

### 8.2.4 [ref]group

Change user

contains 1 rule

### 8.2.4.a [ref]group

For a sample of system components, inspect system

### 8.2.4.b [ref]group

#### 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, replacing DAYS appropriately:

    PASS_MAX_DAYS DAYS

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

Any password, no matter how complex, can eventually be cracked. Therefore, passwords need to be changed periodically. If the operating system does not limit the lifetime of passwords and force users to change their passwords, there is the risk that the operating system passwords could be compromised.
Setting the password maximum age ensures users are required to periodically change their passwords. Requiring shorter password lifetimes increases the risk of users writing down the password in a convenient location subject to physical compromise.

Severity: medium

References: IA-5(f), IA-5(g), IA-5(1)(d), 199, SRG-OS-000076-GPOS-00044, Req-8.2.4, 7.1.1

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    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

### 8.2.5 [ref]group

Do not allow an individual to

contains 1 rule

### 8.2.5.a [ref]group

For a sample of system components, obtain and

### 8.2.5.b [ref]group

#### 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: IA-5(f), IA-5(1)(e), 200, SRG-OS-000077-GPOS-00045, Req-8.2.5, 6.3.4

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    var_password_pam_unix_remember="4"

    if grep -q "remember=" /etc/pam.d/system-auth; then
    	sed -i --follow-symlinks "s/\(^password.*sufficient.*pam_unix.so.*\)\(\(remember *= *\)[^ $]*\)/\1remember=$var_password_pam_unix_remember/" /etc/pam.d/system-auth
    else
    	sed -i --follow-symlinks "/^password[[:space:]]\+sufficient[[:space:]]\+pam_unix.so/ s/$/ remember=$var_password_pam_unix_remember/" /etc/pam.d/system-auth
    fi

### 8.2.6 [ref]group

Set passwords/phrases for first-

### 8.3 [ref]group

Incorporate two-factor authentication

contains 1 rule

### 8.3.a [ref]group

Examine system configurations for remote access servers

### 8.3.b [ref]group

Observe a sample of personnel (for example, users and

#### 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/7/html/System-Level_Authentication_Guide/smartcards.html#authconfig-smartcards

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: IA-2(2), 765, 766, 767, 768, 771, 772, 884, Req-8.3, SRG-OS-000104-GPOS-00051, SRG-OS-000106-GPOS-00053, SRG-OS-000107-GPOS-00054, SRG-OS-000109-GPOS-00056, SRG-OS-000108-GPOS-00055, SRG-OS-000108-GPOS-00057, SRG-OS-000108-GPOS-00058

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # Install required packages

    function package_command {

    # Load function arguments into local variables
    local package_operation=$1
    local package=$2

    # Check sanity of the input
    if [ $# -ne "2" ]
    then
      echo "Usage: package_command 'install/uninstall' 'rpm_package_name"
      echo "Aborting."
      exit 1
    fi

    # If dnf is installed, use dnf; otherwise, use yum
    if [ -f "/usr/bin/dnf" ] ; then
      install_util="/usr/bin/dnf"
    else
      install_util="/usr/bin/yum"
    fi

    if [ "$package_operation" != 'remove' ] ; then
      # If the rpm is not installed, install the rpm
      if ! /bin/rpm -q --quiet $package; then
        $install_util -y $package_operation $package
      fi
    else
      # If the rpm is installed, uninstall the rpm
      if /bin/rpm -q --quiet $package; then
        $install_util -y $package_operation $package
      fi
    fi

    }

    package_command install esc
    package_command install pam_pkcs11

    # Enable pcscd.socket systemd activation socket

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

    # 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: It is not possible to use the 'authconfig' command to perform the
    #       remediation for us, because call of 'authconfig' would discard changes
    #       for other remediations (see RH BZ#1357019 for details)
    #
    #	Therefore we need to configure the necessary 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 nodebug"

    # 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
    sed -i "s/^[$SP]*cert_policy[$SP]\+=[$SP]\+none;/\t\tcert_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
    sed -i "/ocsp_on/! s/^[$SP]*cert_policy[$SP]\+=[$SP]\+\(.*\);/\t\tcert_policy = \1, ocsp_on;/" "$PAM_PKCS11_CONF"

### 8.4 [ref]group

Document and communicate

### 8.4.a [ref]group

Examine

### 8.4.b [ref]group

Review authentication policies and procedures that are

### 8.4.c [ref]group

Interview a sample of users to verify that they are familiar

### 8.5 [ref]group

Do not use group, shared, or generic

contains 1 rule

### 8.5.1 [ref]group

### 8.5.a [ref]group

For a sample of system components, examine user ID lists

contains 1 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:

If a user is assigned the Group Identifier (GID) of a group not existing on the system, and a group with the Gruop Identifier (GID) is subsequently created, the user may have unintended rights to any files associated with the group.

Severity: low

References: IA-2, 764, SRG-OS-000104-GPOS-00051, Req-8.5.a

### 8.5.b [ref]group

Examine authentication policies and procedures to verify

### 8.5.c [ref]group

Interview system administrators to verify that group and

### 8.6 [ref]group

Where other authentication

### 8.6.a [ref]group

Examine authentication policies and procedures to verify

### 8.6.b [ref]group

Interview security personnel to verify authentication

### 8.6.c [ref]group

Examine system configuration settings and/or physical

### 8.7 [ref]group

All access to any database

contains 9 rules

### 8.7.a [ref]group

Review database and application configuration settings

### 8.7.b [ref]group

Examine database and application configuration settings to

### 8.7.c [ref]group

Examine database access control settings and database

contains 9 rules

#### 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: AC-6, http://iase.disa.mil/stigs/cci/Pages/index.aspx, Req-8.7.c

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    chown root /etc/shadow

#### 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: AC-6, http://iase.disa.mil/stigs/cci/Pages/index.aspx, Req-8.7.c

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    chgrp root /etc/shadow

#### 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: AC-6, http://iase.disa.mil/stigs/cci/Pages/index.aspx, Req-8.7.c

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure

    chmod 0000 /etc/shadow

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure

    - name: Ensure permission 0000 on /etc/shadow
      file:
        path="{{item}}"
        mode=0000
      with_items:
        - /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

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    chown root /etc/group

#### 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: AC-6, http://iase.disa.mil/stigs/cci/Pages/index.aspx, Req-8.7.c

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    chgrp root /etc/group

#### 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: AC-6, http://iase.disa.mil/stigs/cci/Pages/index.aspx, Req-8.7.c

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    chmod 644 /etc/group

#### 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: AC-6, http://iase.disa.mil/stigs/cci/Pages/index.aspx, Req-8.7.c

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    chown root /etc/passwd

#### 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: AC-6, http://iase.disa.mil/stigs/cci/Pages/index.aspx, Req-8.7.c

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    chgrp root /etc/passwd

#### 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: AC-6, http://iase.disa.mil/stigs/cci/Pages/index.aspx, Req-8.7.c

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    chmod 0644 /etc/passwd

### 8.7.d [ref]group

Examine database access control settings, database

### 8.8 [ref]group

Ensure that security policies and

### 10. [ref]group

Track and monitor all access to network resources and cardholder data

contains 48 rules

### 10.1 [ref]group

Implement audit trails to link all

### 10.2 [ref]group

Implement automated audit trails for

contains 10 rules

### 10.2.1 [ref]group

All individual user accesses to

contains 1 rule

#### 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. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following lines to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

    -a always,exit -F arch=b32 -S creat -S open -S openat -S open_by_handle_at -S truncate -S ftruncate -F exit=-EACCES -F auid>=1000 -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>=1000 -F auid!=4294967295 -k access

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

    -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>=1000 -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>=1000 -F auid!=4294967295 -k access

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following lines to `/etc/audit/audit.rules` file:

    -a always,exit -F arch=b32 -S creat -S open -S openat -S open_by_handle_at -S truncate -S ftruncate -F exit=-EACCES -F auid>=1000 -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>=1000 -F auid!=4294967295 -k access

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

    -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>=1000 -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>=1000 -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: medium

References: AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 172, 2884, SRG-OS-000064-GPOS-00033, SRG-OS-000458-GPOS-00203, SRG-OS-000461-GPOS-00205, SRG-OS-000392-GPOS-00172, Req-10.2.4, Req-10.2.1, 5.2.10

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # 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>=1000 -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>=1000 -F auid!=4294967295 -k access"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    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

    # 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'!"
            exit 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:]]\+\)')
            # 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))
            # 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"))
            # 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"
                                    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"
                                    # 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

    }

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

    	# Then fix the -EPERM requirement
    	PATTERN="-a always,exit -F arch=$ARCH -S .* -F exit=-EPERM -F auid>=1000 -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>=1000 -F auid!=4294967295 -k access"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    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

    # 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'!"
            exit 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:]]\+\)')
            # 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))
            # 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"))
            # 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"
                                    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"
                                    # 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

    }

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

    done

### 10.2.2 [ref]group

All actions taken by any

contains 2 rules

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

If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add a line of the following form to a file with suffix `.rules` in the directory `/etc/audit/rules.d` for each setuid / setgid program on the system, replacing the SETUID_PROG_PATH part with the full path of that setuid / setgid program in the list:

    -a always,exit -F path=SETUID_PROG_PATH -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add a line of the following form to `/etc/audit/audit.rules` for each setuid / setgid program on the system, replacing the SETUID_PROG_PATH part with the full path of that setuid / setgid program in the list:

    -a always,exit -F path=SETUID_PROG_PATH -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged

Rationale:

Misuse of privileged functions, either intentionally or unintentionally by authorized users, or by unauthorized external entities that have compromised system accounts, is a serious and ongoing concern and can have significant adverse impacts on organizations. Auditing the use of privileged functions is one way to detect such misuse and identify the risk from insider and advanced persistent threast.
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: medium

References: AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-2(4), AU-6(9), AU-12(a), AU-12(c), IR-5, 2234, SRG-OS-000327-GPOS-00127, Req-10.2.2, 5.2.10

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    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

            # Replace possible slash '/' character in sbinary definition so we could use it in sed expressions below
            sbinary_esc=${sbinary//$'/'/$'\/'}
            # 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_esc}$/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

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

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

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

            done

    done

    }

    perform_audit_rules_privileged_commands_remediation "auditctl" "1000"
    perform_audit_rules_privileged_commands_remediation "augenrules" "1000"

#### Ensure auditd Collects System Administrator Actions [ref]rule

At a minimum the audit system should collect administrator actions for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

    -w /etc/sudoers -p wa -k actions

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

    -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: low

References: AC-2(7)(b), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 126, Req-10.2.2, Req-10.2.5.b

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    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"
    fix_audit_watch_rule "augenrules" "/etc/sudoers" "wa" "actions"

### 10.2.3 [ref]group

Access to all audit trails

contains 2 rules

#### Record Attempts to Alter Logon and Logout Events [ref]rule

The audit system already collects login information for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following lines to a file with suffix `.rules` in the directory `/etc/audit/rules.d` in order to watch for attempted manual edits of files involved in storing logon events:

    -w /var/log/tallylog -p wa -k logins
    -w /var/run/faillock/ -p wa -k logins
    -w /var/log/lastlog -p wa -k logins

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following lines to `/etc/audit/audit.rules` file in order to watch for unattempted manual edits of files involved in storing logon events:

    -w /var/log/tallylog -p wa -k logins
    -w /var/run/faillock/ -p wa -k logins
    -w /var/log/lastlog -p wa -k logins

Rationale:

Manual editing of these files may indicate nefarious activity, such as an attacker attempting to remove evidence of an intrusion.

Severity: medium

References: AC-17(7), AU-1(b), AU-12(a), AU-12(c), IR-5, 172, 2884, SRG-OS-000392-GPOS-00172, SRG-OS-000470-GPOS-00214, SRG-OS-000473-GPOS-00218, Req-10.2.3, 5.2.8

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    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 "augenrules" "/var/log/tallylog" "wa" "logins"

    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/faillock/" "wa" "logins"
    fix_audit_watch_rule "augenrules" "/var/run/faillock/" "wa" "logins"

    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"
    fix_audit_watch_rule "augenrules" "/var/log/lastlog" "wa" "logins"

#### Record Attempts to Alter Process and Session Initiation Information [ref]rule

The audit system already collects process information for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following lines to a file with suffix `.rules` in the directory `/etc/audit/rules.d` in order to watch for attempted manual edits of files involved in storing such process information:

    -w /var/run/utmp -p wa -k session
    -w /var/log/btmp -p wa -k session
    -w /var/log/wtmp -p wa -k session

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following lines to `/etc/audit/audit.rules` file in order to watch for attempted manual edits of files involved in storing such process information:

    -w /var/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: low

References: AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.3, 5.2.9

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # Perform the remediation
    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    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 "augenrules" "/var/run/utmp" "wa" "session"

    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/btmp" "wa" "session"
    fix_audit_watch_rule "augenrules" "/var/log/btmp" "wa" "session"

    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"
    fix_audit_watch_rule "augenrules" "/var/log/wtmp" "wa" "session"

### 10.2.4 [ref]group

Invalid logical access attempts

contains 1 rule

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

To configure the system to notify users of last logon/access using `pam_lastlog`, add or correct the `pam_lastlog` settings in `/etc/pam.d/postlogin` to read as follows:

    session     [success=1 default=ignore] pam_succeed_if.so service !~ gdm* service !~ su* quiet
    session     [default=1]   pam_lastlog.so nowtmp showfailed
    session     optional      pam_lastlog.so silent noupdate 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: low

References: AC-9, 366, Req-10.2.4, SRG-OS-000480-GPOS-00227

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    if ! `grep -q ^[^#].*pam_succeed_if.*showfailed /etc/pam.d/postlogin` ; then
      if ! grep `^session.*pam_succeed_if.so /etc/pam.d/postlogin` ; then
        echo "session     [default=1]   pam_lastlog.so nowtmp showfailed" >> /etc/pam.d/postlogin
        echo "session     optional      pam_lastlog.so silent noupdate showfailed" >> /etc/pam.d/postlogin
      else
        sed -i '/^session.*pam_succeed_if.so/a session\t    optional\t  pam_lastlog.so silent noupdate showfailed' /etc/pam.d/postlogin
        sed -i '/^session.*pam_succeed_if.so/a session\t    [default=1]\t  pam_lastlog.so nowtmp showfailed' /etc/pam.d/postlogin
      fi
    else
      sed -i "s/session[ ]*\[default=1][ ]*pam_lastlog.so.*/session     [default=1]   pam_lastlog.so nowtmp showfailed/g" /etc/pam.d/postlogin
      sed -i "s/session[ ]*optional[ ]*pam_lastlog.so.*/session     optional      pam_lastlog.so silent noupdate showfailed/g" /etc/pam.d/postlogin
    fi

### 10.2.5 [ref]group

Use of and changes to

contains 1 rule

### 10.2.5.a [ref]group

Verify use of identification and authentication

### 10.2.5.b [ref]group

Verify all elevation of privileges is logged.

### 10.2.5.c [ref]group

Verify all changes, additions, or deletions to any account

#### Record Events that Modify User/Group Information [ref]rule

If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following lines to a file with suffix `.rules` in the directory `/etc/audit/rules.d`, in order to capture events that modify account changes:

    -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

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following lines to `/etc/audit/audit.rules` file, in order to capture events that modify account changes:

    -w /etc/group -p wa -k audit_rules_usergroup_modification
    -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: low

References: AC-2(4), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 18, 172, 1403, 2130, Req-10.2.5, 5.2.5, SRG–OS–000004–GPOS–00004, SRG–OS–000239–GPOS–00089, SRG–OS–000241–GPOS–00090, SRG–OS–000241–GPOS–00091, SRG–OS–000303–GPOS–00120, SRG–OS–000476–GPOS–00221

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # Perform the remediation
    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    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 "augenrules" "/etc/group" "wa" "audit_rules_usergroup_modification"

    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/passwd" "wa" "audit_rules_usergroup_modification"
    fix_audit_watch_rule "augenrules" "/etc/passwd" "wa" "audit_rules_usergroup_modification"

    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 "augenrules" "/etc/gshadow" "wa" "audit_rules_usergroup_modification"

    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/shadow" "wa" "audit_rules_usergroup_modification"
    fix_audit_watch_rule "augenrules" "/etc/shadow" "wa" "audit_rules_usergroup_modification"

    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"
    fix_audit_watch_rule "augenrules" "/etc/security/opasswd" "wa" "audit_rules_usergroup_modification"

### 10.2.6 [ref]group

Initialization, stopping, or

### 10.2.7 [ref]group

Creation and deletion of system-

contains 3 rules

#### 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. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`, setting ARCH to either b32 or b64 as appropriate for your system:

    -a always,exit -F arch=ARCH -S mount -F auid>=1000 -F auid!=4294967295 -k export

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file, setting ARCH to either b32 or b64 as appropriate for your system:

    -a always,exit -F arch=ARCH -S mount -F auid>=1000 -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: medium

References: AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-3(1), AU-12(a), AU-12(c), IR-5, 135, 2884, SRG-OS-000042-GPOS-00020, SRG-OS-000392-GPOS-00172, Req-10.2.7, 5.2.13

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # 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>=1000 -F auid!=4294967295 -k *"
    	GROUP="mount"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S mount -F auid>=1000 -F auid!=4294967295 -k export"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    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

    # 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'!"
            exit 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:]]\+\)')
            # 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))
            # 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"))
            # 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"
                                    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"
                                    # 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

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    	fix_audit_syscall_rule "augenrules" "$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. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`, setting ARCH to either b32 or b64 as appropriate for your system:

    -a always,exit -F arch=ARCH -S rmdir -S unlink -S unlinkat -S rename -S renameat -F auid>=1000 -F auid!=4294967295 -k delete

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file, setting ARCH to either b32 or b64 as appropriate for your system:

    -a always,exit -F arch=ARCH -S rmdir -S unlink -S unlinkat -S rename -S renameat -F auid>=1000 -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: medium

References: AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 366, SRG-OS-000480-GPOS-00227, Req-10.2.7, 5.2.14

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # 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>=1000 -F auid!=4294967295 -k *"
    	# 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>=1000 -F auid!=4294967295 -k delete"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    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

    # 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'!"
            exit 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:]]\+\)')
            # 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))
            # 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"))
            # 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"
                                    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"
                                    # 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

    }

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

#### Ensure auditd Collects Information on Kernel Module Loading and Unloading [ref]rule

If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following lines to a file with suffix `.rules` in the directory `/etc/audit/rules.d` to capture kernel module loading and unloading events, setting ARCH to either b32 or b64 as appropriate for your system:

    -w /usr/sbin/insmod -p x -k modules
    -w /usr/sbin/rmmod -p x -k modules
    -w /usr/sbin/modprobe -p x -k modules
    -a always,exit -F arch=ARCH -S init_module -S delete_module -k modules

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following lines to `/etc/audit/audit.rules` file in order to capture kernel module loading and unloading events, setting ARCH to either b32 or b64 as appropriate for your system:

    -w /usr/sbin/insmod -p x -k modules
    -w /usr/sbin/rmmod -p x -k modules
    -w /usr/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: medium

References: AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 172, SRG-OS-000471-GPOS-00216, SRG-OS-000477, GPOS-00222, Req-10.2.7, 5.2.17

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # 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"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    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

    # 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'!"
            exit 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:]]\+\)')
            # 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))
            # 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"))
            # 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"
                                    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"
                                    # 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

    }

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

    # Then perform the remediations for the watch rules
    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    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" "/usr/sbin/insmod" "x" "modules"
    fix_audit_watch_rule "augenrules" "/usr/sbin/insmod" "x" "modules"

    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" "/usr/sbin/rmmod" "x" "modules"
    fix_audit_watch_rule "augenrules" "/usr/sbin/rmmod" "x" "modules"

    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" "/usr/sbin/modprobe" "x" "modules"
    fix_audit_watch_rule "augenrules" "/usr/sbin/modprobe" "x" "modules"

### 10.3 [ref]group

Record at least the following audit

contains 1 rule

### 10.3.1 [ref]group

User identification

### 10.3.2 [ref]group

Type of event

### 10.3.3 [ref]group

Date and time

### 10.3.4 [ref]group

Success or failure indication

### 10.3.5 [ref]group

Origination of event

### 10.3.6 [ref]group

Identity or name of affected

#### 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 default GRUB 2 command line for the Linux operating system in `/etc/default/grub`, in the manner below:

    GRUB_CMDLINE_LINUX="crashkernel=auto rd.lvm.lv=VolGroup/LogVol06 rd.lvm.lv=VolGroup/lv_swap rhgb quiet rd.shell=0 audit=1"

Warning: The GRUB 2 configuration file, `grub.cfg`, is automatically updated each time a new kernel is installed. Note that any changes to `/etc/default/grub` require rebuilding the `grub.cfg` file. To update the GRUB 2 configuration file manually, use the

    grub2-mkconfig -o

command as follows:

- On BIOS-based machines, issue the following command as `root`:

      ~]# grub2-mkconfig -o /boot/grub2/grub.cfg

- On UEFI-based machines, issue the following command as `root`:

      ~]# grub2-mkconfig -o /boot/efi/EFI/redhat/grub.cfg

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: medium

References: AC-17(1), AU-14(1), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-10, IR-5, 1464, 130, Req-10.3, 5.2.3

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # Correct the form of default kernel command line in /etc/default/grub
    grep -q ^GRUB_CMDLINE_LINUX=\".*audit=0.*\" /etc/default/grub && \
      sed -i "s/audit=[^[:space:]\+]/audit=1/g" /etc/default/grub
    if ! [ $? -eq 0 ]; then
      sed -i "s/\(GRUB_CMDLINE_LINUX=\)\"\(.*\)\"/\1\"\2 audit=1\"/" /etc/default/grub
    fi

    # Correct the form of kernel command line for each installed kernel
    # in the bootloader
    /sbin/grubby --update-kernel=ALL --args="audit=1"

### 10.4 [ref]group

Using time-synchronization

contains 8 rules

### 10.4.1 [ref]group

Critical systems have the

contains 1 rule

### 10.4.1.a [ref]group

Examine the process for acquiring, distributing and

### 10.4.1.b [ref]group

Observe the time-related system-parameter settings for

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

Depending on specific functional requirements of a concrete production environment, the Red Hat Enterprise Linux 7 Server system can be configured to utilize the services of the `chronyd` NTP daemon (the default), or services of the `ntpd` NTP daemon. Refer to https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/System_Administrators_Guide/ch-Configuring_NTP_Using_the_chrony_Suite.html for more detailed comparison of the features of both of the choices, and for further guidance how to choose between the two NTP daemons.
To specify a remote NTP server for time synchronization, perform the following:

- if the system is configured to use the `chronyd` as the NTP daemon (the default), edit the file `/etc/chrony.conf` as follows,
- if the system is configured to use the `ntpd` as the NTP daemon, edit the file `/etc/ntp.conf` as documented below.

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: AU-8(1), 160, Req-10.4.1, Req-10.4.3, 3.6

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    var_multiple_time_servers="0.rhel.pool.ntp.org,1.rhel.pool.ntp.org,2.rhel.pool.ntp.org,3.rhel.pool.ntp.org"

    if ! `/usr/sbin/pidof ntpd`; then
      if ! `grep -q ^server /etc/chrony.conf` ; then
        if ! `grep -q '#[[:space:]]*server' /etc/chrony.conf` ; then
          for i in `echo "$var_multiple_time_servers" | tr ',' '\n'` ; do
            echo -ne "\nserver $i iburst" >> /etc/chrony.conf
          done
        else
          sed -i 's/#[ ]*server/server/g' /etc/chrony.conf
        fi
      fi
    else
      if ! `grep -q ^server /etc/ntp.conf` ; then
        if ! `grep -q '#[[:space:]]*server' /etc/ntp.conf` ; then
          for i in `echo "$var_multiple_time_servers" | tr ',' '\n'` ; do
            echo -ne "\nserver $i iburst" >> /etc/ntp.conf
          done
        else
          sed -i 's/#[ ]*server/server/g' /etc/ntp.conf
        fi
      fi
    fi

### 10.4.2 [ref]group

Time data is protected.

contains 5 rules

### 10.4.2.a [ref]group

Examine system configurations and time-

### 10.4.2.b [ref]group

Examine system configurations, time synchronization

contains 5 rules

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

If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

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

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

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

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

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

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

    -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: low

References: AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 5.2.4, Req-10.4.2.b, 1487, 169

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    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

    # 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'!"
            exit 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:]]\+\)')
            # 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))
            # 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"))
            # 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"
                                    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"
                                    # 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

    }

    function rhel7_fedora_perform_audit_adjtimex_settimeofday_stime_remediation {

    # Perform the remediation for the 'adjtimex', 'settimeofday', and 'stime' audit
    # system calls on Red Hat Enterprise Linux 7 or Fedora OSes
    #
    # 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 for both possible tools: 'auditctl' and 'augenrules'
            fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
            fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

    }

    rhel7_fedora_perform_audit_adjtimex_settimeofday_stime_remediation

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

If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

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

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

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

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

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

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

    -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: low

References: AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 5.2.4, Req-10.4.2.b, 1487, 169

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    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

    # 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'!"
            exit 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:]]\+\)')
            # 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))
            # 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"))
            # 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"
                                    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"
                                    # 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

    }

    function rhel7_fedora_perform_audit_adjtimex_settimeofday_stime_remediation {

    # Perform the remediation for the 'adjtimex', 'settimeofday', and 'stime' audit
    # system calls on Red Hat Enterprise Linux 7 or Fedora OSes
    #
    # 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 for both possible tools: 'auditctl' and 'augenrules'
            fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
            fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

    }

    rhel7_fedora_perform_audit_adjtimex_settimeofday_stime_remediation

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

If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d` for both 32 bit and 64 bit systems:

    -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). If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file for both 32 bit and 64 bit systems:

    -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 system calls:

    -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: low

References: AC-17(7), 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, 1487, 169

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    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

    # 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'!"
            exit 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:]]\+\)')
            # 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))
            # 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"))
            # 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"
                                    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"
                                    # 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

    }

    function rhel7_fedora_perform_audit_adjtimex_settimeofday_stime_remediation {

    # Perform the remediation for the 'adjtimex', 'settimeofday', and 'stime' audit
    # system calls on Red Hat Enterprise Linux 7 or Fedora OSes
    #
    # 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 for both possible tools: 'auditctl' and 'augenrules'
            fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
            fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

    }

    rhel7_fedora_perform_audit_adjtimex_settimeofday_stime_remediation

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

If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

    -a always,exit -F arch=b32 -S clock_settime -F a0=0x0 -F key=time-change

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

    -a always,exit -F arch=b64 -S clock_settime -F a0=0x0 -F key=time-change

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

    -a always,exit -F arch=b32 -S clock_settime -F a0=0x0 -F key=time-change

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

    -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: low

References: AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 5.2.4, Req-10.4.2.b, 1487, 169

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # 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"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    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

    # 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'!"
            exit 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:]]\+\)')
            # 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))
            # 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"))
            # 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"
                                    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"
                                    # 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

    }

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

#### Record Attempts to Alter the localtime File [ref]rule

If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

    -w /etc/localtime -p wa -k audit_time_rules

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

    -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: low

References: AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(b), IR-5, 5.2.4, Req-10.4.2.b, 1487, 169

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    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"
    fix_audit_watch_rule "augenrules" "/etc/localtime" "wa" "audit_time_rules"

### 10.4.3 [ref]group

Time settings are received from

contains 1 rule

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

Depending on specific functional requirements of a concrete production environment, the Red Hat Enterprise Linux 7 Server system can be configured to utilize the services of the `chronyd` NTP daemon (the default), or services of the `ntpd` NTP daemon. Refer to https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/System_Administrators_Guide/ch-Configuring_NTP_Using_the_chrony_Suite.html for more detailed comparison of the features of both of the choices, and for further guidance how to choose between the two NTP daemons.
Additional NTP servers can be specified for time synchronization. To do so, perform the following:

- if the system is configured to use the `chronyd` as the NTP daemon (the default), edit the file `/etc/chrony.conf` as follows,
- if the system is configured to use the `ntpd` as the NTP daemon, edit the file `/etc/ntp.conf` as documented below.

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: low

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

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    var_multiple_time_servers="0.rhel.pool.ntp.org,1.rhel.pool.ntp.org,2.rhel.pool.ntp.org,3.rhel.pool.ntp.org"

    if ! `/usr/sbin/pidof ntpd`; then
      if [ `grep -c '^server' /etc/chrony.conf` -lt 2 ]; then
        if ! `grep -q '#[[:space:]]*server' /etc/chrony.conf` ; then
          for i in `echo "$var_multiple_time_servers" | tr ',' '\n'` ; do
            echo -ne "\nserver $i iburst" >> /etc/chrony.conf
          done
        else
          sed -i 's/#[ ]*server/server/g' /etc/chrony.conf
        fi
      fi
    else
      if [ `grep -c '^server' /etc/ntp.conf` -lt 2 ]; then
        if ! `grep -q '#[[:space:]]*server' /etc/ntp.conf` ; then
          for i in `echo "$var_multiple_time_servers" | tr ',' '\n'` ; do
            echo -ne "\nserver $i iburst" >> /etc/ntp.conf
          done
        else
          sed -i 's/#[ ]*server/server/g' /etc/ntp.conf
        fi
      fi
    fi

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

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

    $ sudo systemctl enable chronyd.service

Note: The `chronyd` daemon is enabled by default.
The `ntpd` service can be enabled with the following command:

    $ sudo systemctl enable ntpd.service

Note: The `ntpd` daemon is not enabled by default. Though as mentioned in the previous sections in certain environments the `ntpd` daemon might be preferred to be used rather than the `chronyd` one. Refer to: https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/System_Administrators_Guide/ch-Configuring_NTP_Using_the_chrony_Suite.html for guidance which NTP daemon to choose depending on the environment used. Rationale:

Enabling some of `chronyd` or `ntpd` services ensures that the NTP daemon 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 `chronyd` and `ntpd` NTP daemons offer 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: AU-8(1), 160, Req-10.4, 3.6

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    if ! `rpm -q --quiet chrony` && ! `rpm -q --quiet ntp-`; then

    function package_command {

    # Load function arguments into local variables
    local package_operation=$1
    local package=$2

    # Check sanity of the input
    if [ $# -ne "2" ]
    then
      echo "Usage: package_command 'install/uninstall' 'rpm_package_name"
      echo "Aborting."
      exit 1
    fi

    # If dnf is installed, use dnf; otherwise, use yum
    if [ -f "/usr/bin/dnf" ] ; then
      install_util="/usr/bin/dnf"
    else
      install_util="/usr/bin/yum"
    fi

    if [ "$package_operation" != 'remove' ] ; then
      # If the rpm is not installed, install the rpm
      if ! /bin/rpm -q --quiet $package; then
        $install_util -y $package_operation $package
      fi
    else
      # If the rpm is installed, uninstall the rpm
      if /bin/rpm -q --quiet $package; then
        $install_util -y $package_operation $package
      fi
    fi

    }

      package_command install chrony
      service_command enable chronyd
    elif `rpm -q --quiet chrony`; then
      if ! [ `/usr/sbin/pidof ntpd` ] ; then

    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
    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 chronyd
      fi
    else

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

### 10.5 [ref]group

Secure audit trails so they cannot

contains 22 rules

### 10.5.1 [ref]group

Limit viewing of audit trails to

contains 4 rules

#### 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: AC-6, SI-11, 1314, Req-10.5.1, Req-10.5.2

#### 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: AC-6, SI-11, 1314, Req-10.5.1, Req-10.5.2

#### 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: SI-11, 1314, Req-10.5.1, Req-10.5.2, 5.1.4

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # 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

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

Unauthorized disclosure of audit records can reveal system and configuration data to attackers, thus compromising its confidentiality.

Severity: medium

References: AC-6, AU-1(b), AU-9, IR-5, 163, SRG-OS-000058-GPOS-00028, Req-10.5.1

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    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

### 10.5.2 [ref]group

Protect audit trail files from

contains 1 rule

#### Make the auditd Configuration Immutable [ref]rule

If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d` in order to make the auditd configuration immutable:

    -e 2

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file in order to make the auditd 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: low

References: AC-6, AU-1(b), AU-2(a), AU-2(c), AU-2(d), IR-5, Req-10.5.2, 5.2.18

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # Traverse all of:
    #
    # /etc/audit/audit.rules,			(for auditctl case)
    # /etc/audit/rules.d/*.rules			(for augenrules case)
    #
    # files to check if '-e .*' setting is present in that '*.rules' file already.
    # If found, delete such occurrence since auditctl(8) manual page instructs the
    # '-e 2' rule should be placed as the last rule in the configuration
    find /etc/audit /etc/audit/rules.d -maxdepth 1 -type f -name *.rules -exec sed -i '/-e[[:space:]]\+.*/d' {} ';'

    # Append '-e 2' requirement at the end of both:
    # * /etc/audit/audit.rules file 		(for auditctl case)
    # * /etc/audit/rules.d/immutable.rules		(for augenrules case)

    for AUDIT_FILE in "/etc/audit/audit.rules" "/etc/audit/rules.d/immutable.rules"
    do
    	echo '' >> $AUDIT_FILE
    	echo '# Set the audit.rules configuration immutable per security requirements' >> $AUDIT_FILE
    	echo '# Reboot is required to change audit rules once this setting is applied' >> $AUDIT_FILE
    	echo '-e 2' >> $AUDIT_FILE
    done

### 10.5.3 [ref]group

Promptly back up audit trail files

contains 1 rule

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

References: AU-1(b), AU-3(2), IR-5, 136, Req-10.5.3

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    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

### 10.5.4 [ref]group

Write logs for external-facing

### 10.5.5 [ref]group

Use file-integrity monitoring or

contains 15 rules

#### Record Events that Modify the System's Network Environment [ref]rule

If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following lines to a file with suffix `.rules` in the directory `/etc/audit/rules.d`, setting ARCH to either b32 or b64 as appropriate for your system:

    -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

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following lines to `/etc/audit/audit.rules` file, setting ARCH to either b32 or b64 as appropriate for your system:

    -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: low

References: AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, 5.2.6

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # 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"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    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

    # 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'!"
            exit 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:]]\+\)')
            # 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))
            # 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"))
            # 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"
                                    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"
                                    # 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

    }

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

    # Then perform the remediations for the watch rules
    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    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 "augenrules" "/etc/issue" "wa" "audit_rules_networkconfig_modification"

    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.net" "wa" "audit_rules_networkconfig_modification"
    fix_audit_watch_rule "augenrules" "/etc/issue.net" "wa" "audit_rules_networkconfig_modification"

    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 "augenrules" "/etc/hosts" "wa" "audit_rules_networkconfig_modification"

    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/sysconfig/network" "wa" "audit_rules_networkconfig_modification"
    fix_audit_watch_rule "augenrules" "/etc/sysconfig/network" "wa" "audit_rules_networkconfig_modification"

#### Record Events that Modify the System's Mandatory Access Controls [ref]rule

If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

    -w /etc/selinux/ -p wa -k MAC-policy

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

    -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: low

References: AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, 5.2.7

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    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"
    fix_audit_watch_rule "augenrules" "/etc/selinux/" "wa" "MAC-policy"

#### 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. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

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

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

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

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

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

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

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

Warning: Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.
Rationale:

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

Severity: low

References: AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 126, 172, Req-10.5.5, 5.2.10, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # 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>=1000 -F auid!=4294967295 -k *"
    	GROUP="chmod"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S chmod -S fchmod -S fchmodat -F auid>=1000 -F auid!=4294967295 -k perm_mod"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    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

    # 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'!"
            exit 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:]]\+\)')
            # 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))
            # 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"))
            # 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"
                                    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"
                                    # 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

    }

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

#### 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. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

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

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

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

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

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

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

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

Warning: Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.
Rationale:

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

Severity: low

References: AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 126, 172, Req-10.5.5, 5.2.10, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # 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>=1000 -F auid!=4294967295 -k *"
    	GROUP="chown"
    	FULL_RULE="-a always,exit -F arch=${ARCH} -S chown -S fchown -S fchownat -S lchown -F auid>=1000 -F auid!=4294967295 -k perm_mod"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    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

    # 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'!"
            exit 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:]]\+\)')
            # 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))
            # 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"))
            # 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"
                                    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"
                                    # 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

    }

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

#### 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. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

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

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

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

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

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

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

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

Warning: Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.
Rationale:

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

Severity: low

References: AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 126, 172, Req-10.5.5, 5.2.10, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # 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>=1000 -F auid!=4294967295 -k *"
    	GROUP="chmod"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S chmod -S fchmod -S fchmodat -F auid>=1000 -F auid!=4294967295 -k perm_mod"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    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

    # 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'!"
            exit 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:]]\+\)')
            # 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))
            # 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"))
            # 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"
                                    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"
                                    # 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

    }

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

#### 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. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

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

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

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

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

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

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

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

Warning: Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.
Rationale:

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

Severity: low

References: AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 126, 172, Req-10.5.5, 5.2.10, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # 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>=1000 -F auid!=4294967295 -k *"
    	GROUP="chmod"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S chmod -S fchmod -S fchmodat -F auid>=1000 -F auid!=4294967295 -k perm_mod"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    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

    # 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'!"
            exit 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:]]\+\)')
            # 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))
            # 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"))
            # 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"
                                    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"
                                    # 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

    }

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

#### 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. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

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

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

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

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

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

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

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

Warning: Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.
Rationale:

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

Severity: low

References: AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 126, 172, Req-10.5.5, 5.2.10, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # 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>=1000 -F auid!=4294967295 -k *"
    	GROUP="chown"
    	FULL_RULE="-a always,exit -F arch=${ARCH} -S chown -S fchown -S fchownat -S lchown -F auid>=1000 -F auid!=4294967295 -k perm_mod"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    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

    # 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'!"
            exit 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:]]\+\)')
            # 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))
            # 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"))
            # 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"
                                    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"
                                    # 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

    }

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

#### 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. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

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

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

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

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

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

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

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

Warning: Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.
Rationale:

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

Severity: low

References: AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 126, 172, Req-10.5.5, 5.2.10, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # 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>=1000 -F auid!=4294967295 -k *"
    	GROUP="chown"
    	FULL_RULE="-a always,exit -F arch=${ARCH} -S chown -S fchown -S fchownat -S lchown -F auid>=1000 -F auid!=4294967295 -k perm_mod"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    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

    # 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'!"
            exit 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:]]\+\)')
            # 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))
            # 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"))
            # 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"
                                    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"
                                    # 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

    }

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

#### 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.
If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

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

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

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

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

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

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

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

Warning: Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.
Rationale:

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

Severity: medium

References: AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 172, Req-10.5.5, 5.2.10, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # 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>=1000 -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>=1000 -F auid!=4294967295 -k perm_mod"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    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

    # 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'!"
            exit 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:]]\+\)')
            # 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))
            # 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"))
            # 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"
                                    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"
                                    # 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

    }

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

#### 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. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

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

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

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

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

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

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

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

Warning: Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.
Rationale:

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

Severity: low

References: AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 126, 172, Req-10.5.5, 5.2.10, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # 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>=1000 -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>=1000 -F auid!=4294967295 -k perm_mod"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    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

    # 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'!"
            exit 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:]]\+\)')
            # 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))
            # 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"))
            # 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"
                                    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"
                                    # 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

    }

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

#### 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. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

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

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

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

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

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

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

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

Warning: Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.
Rationale:

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

Severity: low

References: AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 126, 172, Req-10.5.5, 5.2.10, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # 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>=1000 -F auid!=4294967295 -k *"
    	GROUP="chown"
    	FULL_RULE="-a always,exit -F arch=${ARCH} -S chown -S fchown -S fchownat -S lchown -F auid>=1000 -F auid!=4294967295 -k perm_mod"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    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

    # 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'!"
            exit 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:]]\+\)')
            # 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))
            # 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"))
            # 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"
                                    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"
                                    # 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

    }

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

#### 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.
If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

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

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

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

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

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

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

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

Warning: Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.
Rationale:

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

Severity: medium

References: AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 172, Req-10.5.5, 5.2.10, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # 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>=1000 -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>=1000 -F auid!=4294967295 -k perm_mod"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    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

    # 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'!"
            exit 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:]]\+\)')
            # 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))
            # 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"))
            # 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"
                                    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"
                                    # 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

    }

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

#### 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. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

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

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

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

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

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

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

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

Warning: Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.
Rationale:

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

Severity: low

References: AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 126, 172, Req-10.5.5, 5.2.10, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # 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>=1000 -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>=1000 -F auid!=4294967295 -k perm_mod"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    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

    # 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'!"
            exit 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:]]\+\)')
            # 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))
            # 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"))
            # 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"
                                    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"
                                    # 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

    }

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

#### 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.
If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

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

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

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

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

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

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

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

Warning: Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.
Rationale:

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

Severity: medium

References: AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 172, Req-10.5.5, 5.2.10, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # 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>=1000 -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>=1000 -F auid!=4294967295 -k perm_mod"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    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

    # 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'!"
            exit 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:]]\+\)')
            # 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))
            # 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"))
            # 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"
                                    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"
                                    # 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

    }

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

#### 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. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

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

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

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

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

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

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

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

Warning: Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.
Rationale:

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

Severity: low

References: AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 126, 172, Req-10.5.5, 5.2.10, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    # 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>=1000 -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>=1000 -F auid!=4294967295 -k perm_mod"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    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

    # 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'!"
            exit 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:]]\+\)')
            # 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))
            # 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"))
            # 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"
                                    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"
                                    # 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

    }

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

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

References: AC-6, AU-1(b), AU-9, IR-5, http://iase.disa.mil/stigs/cci/Pages/index.aspx, Req-10.5

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    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

### 10.6 [ref]group

Review logs and security events for

### 10.6.1 [ref]group

Review the following at least

### 10.6.1.a [ref]group

Examine security policies and procedures to verify that

### 10.6.1.b [ref]group

Observe processes and interview personnel to verify

### 10.6.2 [ref]group

Review logs of all other system

### 10.6.2.a [ref]group

Examine security policies and procedures to verify that

### 10.6.2.b [ref]group

### 10.6.3 [ref]group

Follow up exceptions and

### 10.6.3.a [ref]group

Examine security policies and procedures to verify that

### 10.6.3.b [ref]group

Observe processes and interview personnel to verify

### 10.7 [ref]group

Retain audit trail history for at least

contains 7 rules

### 10.7.a [ref]group

Examine security policies and procedures to verify that they

contains 1 rule

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

Rationale:

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

Severity: medium

References: AU-1(b), AU-4, AU-5(1), AU-5(a), IR-5, 1855, Req-10.7.a, 5.2.1.2, SRG-OS-000343-GPOS-00134

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    var_auditd_action_mail_acct="root"

    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

### 10.7.b [ref]group

Interview personnel and examine audit logs to verify that

### 10.7.c [ref]group

Interview personnel and observe processes to verify that at

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

References: AU-9, 366, Req-10.7

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

    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

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    var_auditd_num_logs="5"

    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 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 6 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, 5.2.1.1

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    var_auditd_max_log_file="6"

    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

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

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    var_auditd_max_log_file_action="rotate"

    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

#### 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: AU-1(b), AU-4, AU-5(1), AU-5(b), IR-5, 1855, Req-10.7, 5.2.1.2, SRG-OS-000343-GPOS-00134

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    var_auditd_space_left_action="email"

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

#### 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: AU-1(b), AU-4, AU-5(b), IR-5, 140, 1343, Req-10.7, 5.2.1.2

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    var_auditd_admin_space_left_action="single"

    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

### 10.8 [ref]group

Ensure that security policies and

### 11. [ref]group

Regularly test security systems and processes

contains 7 rules

### 11.1 [ref]group

Implement processes to test for the

### 11.1.1 [ref]group

Maintain an inventory of

### 11.1.2 [ref]group

Implement incident response

### 11.1.2.a [ref]group

### 11.1.2.b [ref]group

Interview responsible personnel and/or inspect

### 11.1.a [ref]group

Examine policies and procedures to verify processes

### 11.1.b [ref]group

Verify that the methodology is adequate to detect and

### 11.1.c [ref]group

If wireless scanning is utilized, examine output from

### 11.1.d [ref]group

If automated monitoring is utilized (for example,

### 11.2 [ref]group

Run internal and external network

### 11.2.1 [ref]group

Perform quarterly internal

### 11.2.1.a [ref]group

Review the scan reports and verify that four

### 11.2.1.b [ref]group

Review the scan reports and verify that the scan

### 11.2.2 [ref]group

Perform quarterly external

### 11.2.2.c [ref]group

Review the scan reports to verify that the scans

### 11.2.3 [ref]group

Perform internal and external

### 11.2.3.a [ref]group

Inspect and correlate change control

### 11.2.3.b [ref]group

Review scan reports and verify that the scan

### 11.2.3.c [ref]group

Validate that the scan was performed by a qualified

### 11.3 [ref]group

Implement a methodology for

### 11.3.1 [ref]group

Perform

### 11.3.1.a [ref]group

Examine the scope of work and results from the

### 11.3.1.b [ref]group

Verify that the test was performed by a qualified

### 11.3.2 [ref]group

Perform

### 11.3.2.a [ref]group

Examine the scope of work and results from the

### 11.3.2.b [ref]group

Verify that the test was performed by a qualified

### 11.3.3 [ref]group

Exploitable vulnerabilities found

### 11.3.4 [ref]group

If segmentation is used to isolate

### 11.3.4.a [ref]group

Examine segmentation controls and review

### 11.3.4.b [ref]group

Examine the results from the most recent

### 11.4 [ref]group

Use intrusion-detection and/or

contains 1 rule

### 11.4.a [ref]group

Examine system configurations and network diagrams

### 11.4.b [ref]group

Examine system configurations and interview

### 11.4.c [ref]group

Examine IDS/IPS configurations and vendor

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

Warning: Note 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 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: high

References: SC-7, 1263, Req-11.4

### 11.5 [ref]group

Deploy a change-detection

contains 6 rules

### 11.5.1 [ref]group

Implement a process to respond to

### 11.5.a [ref]group

Verify the use of a change-detection mechanism within

### 11.5.b [ref]group

Verify the mechanism is configured to alert personnel

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

References: CM-6(d), CM-6(3), SC-28, SI-7, Req-11.5

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

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

#### 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: CM-3(d), CM-3(e), CM-6(d), CM-6(3), SC-28, SI-7, http://iase.disa.mil/stigs/cci/Pages/index.aspx, Req-11.5, 1.3.1

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable

    function package_command {

    # Load function arguments into local variables
    local package_operation=$1
    local package=$2

    # Check sanity of the input
    if [ $# -ne "2" ]
    then
      echo "Usage: package_command 'install/uninstall' 'rpm_package_name"
      echo "Aborting."
      exit 1
    fi

    # If dnf is installed, use dnf; otherwise, use yum
    if [ -f "/usr/bin/dnf" ] ; then
      install_util="/usr/bin/dnf"
    else
      install_util="/usr/bin/yum"
    fi

    if [ "$package_operation" != 'remove' ] ; then
      # If the rpm is not installed, install the rpm
      if ! /bin/rpm -q --quiet $package; then
        $install_util -y $package_operation $package
      fi
    else
      # If the rpm is installed, uninstall the rpm
      if /bin/rpm -q --quiet $package; then
        $install_util -y $package_operation $package
      fi
    fi

    }

    package_command install aide

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable

    - name: Ensure aide is installed
      package:
        name="{{item}}"
        state=present
      with_items:
        - aide

Remediation Puppet snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable

    include install_aide

    class install_aide {
      package { 'aide':
        ensure => 'installed',
      }
    }

Remediation Anaconda snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable

    package --add=aide

#### 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: CM-3(d), CM-3(e), CM-6(d), CM-6(3), SC-28, SI-7, Req-11.5

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    /usr/sbin/aide --init
    /bin/cp -p /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz

#### Configure Periodic Execution of AIDE [ref]rule

At a minimum, AIDE should be configured to run a weekly scan. At most, AIDE should be run daily. 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

To implement a weekly execution of AIDE at 4:05am using cron, add the following line to `/etc/crontab`:

    05 4 * * 0 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.
Unauthorized changes to the baseline configuration could make the system vulnerable to various attacks or allow unauthorized access to the operating system. Changes to operating system configurations can have unintended side effects, some of which may be relevant to security.
Detecting such changes and providing an automated response can help avoid unintended, negative consequences that could ultimately affect the security state of the operating system. The operating system's Information Management Officer (IMO)/Information System Security Officer (ISSO) and System Administrators (SAs) must be notified via email and/or monitoring system trap when there is an unauthorized modification of a configuration item.

Severity: medium

References: CM-3(d), CM-3(e), CM-3(5), CM-6(d), CM-6(3), SC-28, SI-7, 1744, Req-11.5, 1.3.1, SRG-OS-000363-GPOS-00150, 020130

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

    echo "05 4 * * * root /usr/sbin/aide --check" >> /etc/crontab

#### Verify and Correct File Permissions with RPM [ref]rule

Discretionary access control is weakened if a user or group has access permissions to system files and directories greater than the default. The RPM package management system can check file access permissions of installed software packages, including many that are important to system security. Verify that the file permissions, ownership, and gruop membership of system files and commands match vendor values. Check the file permissions, ownership, and group membership with the following command:

    $ sudo rpm -Va | grep '^.M'

Output indicates files that do not match vendor defaults. After locating a file with incorrect permissions, 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

Warning: Note: Due to a bug in the `gdm` package, the RPM verify command may continue to fail even after file permissions have been correctly set on `/var/log/gdm`. This is being tracked in Red Hat Bugzilla #1275532.
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: high

References: AC-6, AU-9(1), AU-9(3), CM-6(d), CM-6(3), 1494, 1496, Req-11.5, 1.2.6, 6.1.3, 6.1.4, 6.1.5, 6.1.6, 6.1.7, 6.1.8, 6.1.9, 6.2.3, SRG-OS-000257-GPOS-00098, SRG-OS-000278-GPOS-00108

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable

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

    # 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[@]}" | sort -n | uniq) )

    # 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

#### Verify File Hashes with RPM [ref]rule

Without cryptographic integrity protections, system executables and files can be altered by unauthorized users without detection. The RPM package management system can check the hashes of installed software packages, including many that are important to system security. To verify that the cryptographic hash of system files and commands match vendor values, 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: high

References: CM-6(d), CM-6(3), SI-7(1), 663, Req-11.5, 1.2.6, SRG-OS-000480-GPOS-00227

### 11.6 [ref]group

Ensure that security policies and

### Values [ref]group

Group of values used in PCI-DSS profile

### Non PCI-DSS [ref]group

Rules that are not part of PCI-DSS

contains 1 rule

#### 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 systemctl enable auditd.service

Rationale:

Without establishing what type of events occurred, it would be difficult to establish, correlate, and investigate the events leading up to an outage or attack.
Ensuring the `auditd` service is active ensures audit records generated by the kernel are appropriately recorded.

Severity: high

References: AU-3, AC-17(1), AU-1(b), AU-10, AU-12(a), AU-12(c), AU-14(1), IR-5, 126, 131, SRG-OS-000038-GPOS-00016, SRG-OS-000039-GPOS-00017, SRG-OS-000042-GPOS-00021, SRG-OS-000254-GPOS-00095, SRG-OS-000255-GPOS-00096, Req-10, 5.2.2

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable

    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
    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
Reboot: | false
Strategy: | enable

    - name: Enable service auditd
      service:
        name="{{item}}"
        enabled="yes"
        state="started"
      with_items:
        - auditd

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.