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

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

The SCAP Security Guide Project
https://www.open-scap.org/security-policies/scap-security-guide
This guide presents a catalog of security-relevant configuration settings for Red Hat Enterprise Linux 6. It is a rendering of content structured in the eXtensible Configuration Checklist Description Format (XCCDF) in order to support security automation. The SCAP content is is available in the `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 6, which provides required settings for US Department of Defense systems, is one example of a baseline created from this guidance.

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

- CentOS is not an exact copy of Red Hat Enterprise Linux. There may be configuration differences that produce false positives and/or false negatives. If this occurs please file a bug report.
- CentOS has its own build system, compiler options, patchsets, and is a community supported, non-commercial operating system. CentOS 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 CentOS.

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

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

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

## Revision History

Current version: 0.1.31

- draft (as of 2017-08-11)

## Platforms

- cpe:/o:redhat:enterprise_linux:6
- cpe:/o:centos:centos:6
- cpe:/o:redhat:enterprise_linux:6::client
- cpe:/o:redhat:enterprise_linux:6::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

### 4.1 [ref]group

Use strong cryptography and security

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

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

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

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

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

### 7.1 [ref]group

Limit access to system

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

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

### 8.1 [ref]group

Define and implement policies and

### 8.1.1 [ref]group

Assign all users a unique ID

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

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

### 8.1.6.a [ref]group

For a sample of system components, inspect system

### 8.1.6.b [ref]group

### 8.1.7 [ref]group

Set the lockout duration to a

### 8.1.8 [ref]group

If a session has been idle for

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

### 8.2.1 [ref]group

Using strong cryptography,

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

### 8.2.2 [ref]group

Verify user identity before

### 8.2.3 [ref]group

Passwords/phrases must meet

### 8.2.3.a [ref]group

For a sample of system components, inspect system

### 8.2.3.b [ref]group

### 8.2.4 [ref]group

Change user

### 8.2.4.a [ref]group

For a sample of system components, inspect system

### 8.2.4.b [ref]group

### 8.2.5 [ref]group

Do not allow an individual to

### 8.2.5.a [ref]group

For a sample of system components, obtain and

### 8.2.5.b [ref]group

### 8.2.6 [ref]group

Set passwords/phrases for first-

### 8.3 [ref]group

Incorporate two-factor authentication

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

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

### 8.5.1 [ref]group

### 8.5.a [ref]group

For a sample of system components, examine user ID lists

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

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

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

### 10.1 [ref]group

Implement audit trails to link all

### 10.2 [ref]group

Implement automated audit trails for

### 10.2.1 [ref]group

All individual user accesses to

### 10.2.2 [ref]group

All actions taken by any

### 10.2.3 [ref]group

Access to all audit trails

### 10.2.4 [ref]group

Invalid logical access attempts

### 10.2.5 [ref]group

Use of and changes to

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

### 10.2.6 [ref]group

Initialization, stopping, or

### 10.2.7 [ref]group

Creation and deletion of system-

### 10.3 [ref]group

Record at least the following audit

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

### 10.4 [ref]group

Using time-synchronization

### 10.4.1 [ref]group

Critical systems have the

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

### 10.4.2 [ref]group

Time data is protected.

### 10.4.2.a [ref]group

Examine system configurations and time-

### 10.4.2.b [ref]group

Examine system configurations, time synchronization

### 10.4.3 [ref]group

Time settings are received from

### 10.5 [ref]group

Secure audit trails so they cannot

### 10.5.1 [ref]group

Limit viewing of audit trails to

### 10.5.2 [ref]group

Protect audit trail files from

### 10.5.3 [ref]group

Promptly back up audit trail files

### 10.5.4 [ref]group

Write logs for external-facing

### 10.5.5 [ref]group

Use file-integrity monitoring or

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

### 10.7.a [ref]group

Examine security policies and procedures to verify that they

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

### 10.8 [ref]group

Ensure that security policies and

### 11. [ref]group

Regularly test security systems and processes

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

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

### 11.5 [ref]group

Deploy a change-detection

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

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

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

The Red Hat GPG key is necessary to cryptographically verify packages are from Red Hat.

Severity: high

References: SI-7, MA-1(b), 351

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:

Ensuring the validity of packages' cryptographic signatures prior to installation ensures the authenticity of the software and protects against malicious tampering.

Severity: medium

References: SI-7, MA-1(b), 352, 663

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:

Ensuring all packages' cryptographic signatures are valid prior to installation ensures the authenticity of the software and protects against malicious tampering.

Severity: low

References: SI-7, MA-1(b), 352, 663

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`. Rationale:

Installing software updates is a fundamental mitigation against the exploitation of publicly-known vulnerabilities.

Severity: medium

References: SI-2, MA-1(b), 1227, 1233

Remediation Shell script: (show)

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

    yum -y update

#### 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), SC-28, SI-7

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), SC-28, SI-7, 1069

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), SC-28, SI-7, 374, 416, 1069, 1263, 1297, 1589

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

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

    05 4 * * * root /usr/sbin/aide --check

AIDE can be executed periodically through other means; this is merely one example. Rationale:

By default, AIDE does not install itself for periodic execution. Periodically running AIDE is necessary to reveal unexpected changes in installed files.

Severity: medium

References: CM-3(d), CM-3(e), CM-6(d), SC-28, SI-7, 374, 416, 1069, 1263, 1297, 1589

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

The RPM package management system can check file access permissions of installed software packages, including many that are important to system security. After locating a file with incorrect permissions, 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 #1277603.
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: low

References: AC-6, CM-6(d), SI-7, 1493, 1494, 1495

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

The RPM package management system can check the hashes of installed software packages, including many that are important to system security. Run the following command to list which files on the system have hashes that differ from what is expected by the RPM database:

    $ rpm -Va | grep '^..5'

A "c" in the second column indicates that a file is a configuration file, which may appropriately be expected to change. If the file was not expected to change, investigate the cause of the change using audit logs or other means. The package can then be reinstalled to restore the file. Run the following command to determine which package owns the file:

    $ rpm -qf FILENAME

The package can be reinstalled from a yum repository using the command:

    $ sudo yum reinstall PACKAGENAME

Alternatively, the package can be reinstalled from trusted media using the command:

    $ sudo rpm -Uvh PACKAGENAME

Rationale:

The hashes of important files like system executables should match the information given by the RPM database. Executables with erroneous hashes could be a sign of nefarious activity on the system.

Severity: low

References: CM-6(d), SI-7, 1496

#### Install Intrusion Detection Software [ref]rule

The base Red Hat platform already includes a sophisticated auditing system that can detect intruder activity, as well as SELinux, which provides host-based intrusion prevention capabilities by confining privileged programs and user sessions which may become compromised.
In DoD environments, supplemental intrusion detection tools, such as, the McAfee Host-based Security System, are available to integrate with existing infrastructure. When these supplemental tools interfere with the proper functioning of SELinux, SELinux takes precedence.

Rationale:

Host-based intrusion detection tools provide a system-level defense when an intruder gains access to a system or network.

Severity: medium

References: SC-7, 1263

#### Set GNOME Login Inactivity Timeout [ref]rule

Run the following command to set the idle time-out value for inactivity in the GNOME desktop to 900 minutes:

    $ sudo gconftool-2 \
      --direct \
      --config-source xml:readwrite:/etc/gconf/gconf.xml.mandatory \
      --type int \
      --set /desktop/gnome/session/idle_delay 900

Rationale:

Setting the idle delay controls when the screensaver will start, and can be combined with screen locking to prevent access from passersby.

Severity: medium

References: AC-11(a), 57

Remediation Shell script: (show)

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

    inactivity_timeout_value="900"

    # Install GConf2 package if not installed
    if ! rpm -q GConf2; then
      yum -y install GConf2
    fi

    # Set the idle time-out value for inactivity in the GNOME desktop to meet the
    # requirement
    gconftool-2 --direct \
                --config-source "xml:readwrite:/etc/gconf/gconf.xml.mandatory" \
                --type int \
                --set /desktop/gnome/session/idle_delay ${inactivity_timeout_value}

#### GNOME Desktop Screensaver Mandatory Use [ref]rule

Run the following command to activate the screensaver in the GNOME desktop after a period of inactivity:

    $ sudo gconftool-2 --direct \
      --config-source xml:readwrite:/etc/gconf/gconf.xml.mandatory \
      --type bool \
      --set /apps/gnome-screensaver/idle_activation_enabled true

Rationale:

Enabling idle activation of the screensaver ensures the screensaver will be activated after the idle delay. Applications requiring continuous, real-time screen display (such as network management products) require the login session does not have administrator rights and the display station is located in a controlled-access area.

Severity: medium

References: AC-11(a), 57

Remediation Shell script: (show)

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

    # Install GConf2 package if not installed
    if ! rpm -q GConf2; then
      yum -y install GConf2
    fi

    # Set the screensaver activation in the GNOME desktop after a period of inactivity
    gconftool-2 --direct \
                --config-source "xml:readwrite:/etc/gconf/gconf.xml.mandatory" \
                --type bool \
                --set /apps/gnome-screensaver/idle_activation_enabled true

#### Enable Screen Lock Activation After Idle Period [ref]rule

Run the following command to activate locking of the screensaver in the GNOME desktop when it is activated:

    $ sudo gconftool-2 --direct \
      --config-source xml:readwrite:/etc/gconf/gconf.xml.mandatory \
      --type bool \
      --set /apps/gnome-screensaver/lock_enabled true

Rationale:

Enabling the activation of the screen lock after an idle period ensures password entry will be required in order to access the system, preventing access by passersby.

Severity: medium

References: AC-11(a), 57

Remediation Shell script: (show)

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

    # Install GConf2 package if not installed
    if ! rpm -q GConf2; then
      yum -y install GConf2
    fi

    # Set the screensaver locking activation in the GNOME desktop when the
    # screensaver is activated
    gconftool-2 --direct \
                --config-source "xml:readwrite:/etc/gconf/gconf.xml.mandatory" \
                --type bool \
                --set /apps/gnome-screensaver/lock_enabled true

#### Implement Blank Screensaver [ref]rule

Run the following command to set the screensaver mode in the GNOME desktop to a blank screen:

    $ sudo gconftool-2 --direct \
      --config-source xml:readwrite:/etc/gconf/gconf.xml.mandatory \
      --type string \
      --set /apps/gnome-screensaver/mode blank-only

Rationale:

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

Severity: low

References: AC-11(b), 60

Remediation Shell script: (show)

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

    # Install GConf2 package if not installed
    if ! rpm -q GConf2; then
      yum -y install GConf2
    fi

    # Set the screensaver mode in the GNOME desktop to a blank screen
    gconftool-2 --direct \
                --config-source "xml:readwrite:/etc/gconf/gconf.xml.mandatory" \
                --type string \
                --set /apps/gnome-screensaver/mode blank-only

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

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, 225

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, 225

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

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, 225

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, 225

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, 225

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, 225

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, 225

Remediation Shell script: (show)

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

    chmod 0644 /etc/passwd

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

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

Rationale:

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

Severity: high

References: IA-5(b), IA-5(c), IA-5(1)(a)

Remediation Shell script: (show)

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

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

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

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

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

Rationale:

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

Severity: low

References: 366

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

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

    PASS_MAX_DAYS 90

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

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

Severity: medium

References: IA-5(f), IA-5(g), IA-5(1)(d), 180, 199, 76

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

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

References: AC-2(2), AC-2(3), 16, 17, 795

Remediation Shell script: (show)

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

    var_account_disable_post_pw_expiration="90"

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

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

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

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

    session       required     pam_lastlog.so showfailed

Rationale:

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

Severity: low

References: 366

Remediation Shell script: (show)

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

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

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

The pam_cracklib module's `dcredit` parameter controls requirements for usage of digits in a password. When set to a negative number, any password will be required to contain that many digits. When set to a positive number, pam_cracklib will grant +1 additional length credit for each digit. Add `dcredit=-1` after pam_cracklib.so to require use of a digit in passwords.

Rationale:

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

Severity: low

References: IA-5(b), IA-5(c), 194

Remediation Shell script: (show)

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

    var_password_pam_dcredit="-1"

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

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

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

Rationale:

Password length is one factor of several that helps to determine strength and how long it takes to crack a password. Use of more characters in a password helps to exponentially increase the time and/or resources required to compromise the password.

Severity: low

References: IA-5(1)(a), 205

Remediation Shell script: (show)

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

    var_password_pam_minlen="7"

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

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

The pam_cracklib module's `ucredit=` parameter controls requirements for usage of uppercase letters in a password. When set to a negative number, any password will be required to contain that many uppercase characters. When set to a positive number, pam_cracklib will grant +1 additional length credit for each uppercase character. Add `ucredit=-1` after pam_cracklib.so to require use of an upper case character in passwords.

Rationale:

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

Severity: low

References: IA-5(b), IA-5(c), IA-5(1)(a), 192

Remediation Shell script: (show)

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

    var_password_pam_ucredit="-1"

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

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

The pam_cracklib module's `lcredit=` parameter controls requirements for usage of lowercase letters in a password. When set to a negative number, any password will be required to contain that many lowercase characters. When set to a positive number, pam_cracklib will grant +1 additional length credit for each lowercase character. Add `lcredit=-1` after pam_cracklib.so to require use of a lowercase character in passwords.

Rationale:

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

Severity: low

References: IA-5(b), IA-5(c), IA-5(1)(a), 193

Remediation Shell script: (show)

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

    var_password_pam_lcredit="-1"

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

#### Set 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(a), 44

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

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

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

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

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

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

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

    password    sufficient    pam_unix.so sha512 other arguments...

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

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

Severity: medium

References: IA-5(b), IA-5(c), IA-5(1)(c), IA-7, 803

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:

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, 803

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:

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, 803

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

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

    $ sudo chown root /etc/grub.conf

Rationale:

Only root should be able to modify important boot parameters.

Severity: medium

References: AC-6(7), 225

Remediation Shell script: (show)

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

    chown root /etc/grub.conf

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

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

    $ sudo chgrp root /etc/grub.conf

Rationale:

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

Severity: medium

References: AC-6(7), 225

Remediation Shell script: (show)

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

    chgrp root /etc/grub.conf

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

To enable smart card authentication, consult the documentation at:

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

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

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

Rationale:

Smart card login provides two-factor authentication stronger than that provided by a username and password combination. Smart cards leverage PKI (public key infrastructure) in order to provide and verify credentials.

Severity: medium

References: 765, 766, 767, 768, 771, 772, 884

Remediation Shell script: (show)

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

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

    # Enable pcscd service

    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

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

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

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

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

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

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

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

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

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

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

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

    $ sudo yum install openswan

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

    $ sudo yum install libreswan

Rationale:

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

Severity: low

References: AC-17, MA-4, SC-8, 1130, 1131

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 openswan

Remediation Ansible snippet: (show)

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

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

Remediation Puppet snippet: (show)

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

    include install_openswan

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

Remediation Anaconda snippet: (show)

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

    package --add=openswan

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

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

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

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

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

#### Enable auditd Service [ref]rule

The `auditd` service is an essential userspace component of the Linux Auditing System, as it is responsible for writing audit records to disk. The `auditd` service can be enabled with the following command:

    $ sudo chkconfig --level 2345 auditd on

Rationale:

Ensuring the `auditd` service is active ensures audit records generated by the kernel can be written to disk, or that appropriate actions will be taken if other obstacles exist.

Severity: medium

References: AC-17(1), AU-1(b), AU-10, AU-12(a), AU-12(c), IR-5, 347, 157, 172, 880, 1353, 1462, 1487, 1115, 1454, 067, 158, 831, 1190, 1312, 1263, 130, 120, 1589

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

#### Enable Auditing for Processes Which Start Prior to the Audit Daemon [ref]rule

To ensure all processes can be audited, even those which start prior to the audit daemon, add the argument `audit=1` to the kernel line in `/etc/grub.conf`, in the manner below:

    kernel /vmlinuz-version ro vga=ext root=/dev/VolGroup00/LogVol00 rhgb quiet audit=1

Rationale:

Each process on the system carries an "auditable" flag which indicates whether its activities can be audited. Although `auditd` takes care of enabling this for all processes which launch after it does, adding the kernel argument ensures it is set for every process during boot.

Severity: low

References: AC-17(1), AU-14(1), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-10, IR-5, 169

Remediation Shell script: (show)

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

    /sbin/grubby --update-kernel=ALL --args="audit=1"

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

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

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

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(b), IR-5, 140, 143

Remediation Shell script: (show)

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

    var_auditd_space_left_action="email"

    #
    # If space_left_action present in /etc/audit/auditd.conf, change value
    # to var_auditd_space_left_action, else
    # add "space_left_action = $var_auditd_space_left_action" to /etc/audit/auditd.conf
    #

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

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

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

#### 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(a), IR-5, 139, 144

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

#### Configure auditd to use audispd's syslog plugin [ref]rule

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

    $ sudo service auditd restart

Rationale:

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

Severity: low

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

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

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

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

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

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

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

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

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

Rationale:

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

Severity: low

References: AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 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 rhel6_perform_audit_adjtimex_settimeofday_stime_remediation {

    # Perform the remediation for the 'adjtimex', 'settimeofday', and 'stime' audit
    # system calls on Red Hat Enterprise Linux 6 OS
    #
    # Retrieve hardware architecture of the underlying system
    [ $(getconf LONG_BIT) = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

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

    }

    rhel6_perform_audit_adjtimex_settimeofday_stime_remediation

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

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

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

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

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

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

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

Rationale:

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

Severity: low

References: AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 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 rhel6_perform_audit_adjtimex_settimeofday_stime_remediation {

    # Perform the remediation for the 'adjtimex', 'settimeofday', and 'stime' audit
    # system calls on Red Hat Enterprise Linux 6 OS
    #
    # Retrieve hardware architecture of the underlying system
    [ $(getconf LONG_BIT) = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

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

    }

    rhel6_perform_audit_adjtimex_settimeofday_stime_remediation

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

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

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

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

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

Rationale:

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

Severity: low

References: AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 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 rhel6_perform_audit_adjtimex_settimeofday_stime_remediation {

    # Perform the remediation for the 'adjtimex', 'settimeofday', and 'stime' audit
    # system calls on Red Hat Enterprise Linux 6 OS
    #
    # Retrieve hardware architecture of the underlying system
    [ $(getconf LONG_BIT) = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

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

    }

    rhel6_perform_audit_adjtimex_settimeofday_stime_remediation

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

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

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

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

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

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

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

Rationale:

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

Severity: low

References: AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 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"

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

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

Add the following to `/etc/audit/audit.rules`:

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

The -k option allows for the specification of a key in string form that can be used for better reporting capability through ausearch and aureport and should always be used. Rationale:

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

Severity: low

References: AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 1487, 169

Remediation Shell script: (show)

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

    # Perform the remediation

    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
            echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
            echo "Aborting."
            exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #       auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #       augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #       augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
            echo "Unknown audit rules loading tool: $1. Aborting."
            echo "Use either 'auditctl' or 'augenrules'!"
            exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
            files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
            # Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
            # Get pair -- filepath : matching_row into @matches array
            IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
            # Reset IFS back to default
            unset $IFS
            # For each of the matched entries
            for match in "${matches[@]}"
            do
                    # Extract filepath from the match
                    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
                    # Append that path into list of files for inspection
                    files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
            done
            # Case when particular audit rule isn't defined yet
            if [ ${#files_to_inspect[@]} -eq "0" ]
            then
                    # Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
                    files_to_inspect="/etc/audit/rules.d/$key.rules"
                    # If the $key.rules file doesn't exist yet, create it with correct permissions
                    if [ ! -e "$files_to_inspect" ]
                    then
                            touch "$files_to_inspect"
                            chmod 0640 "$files_to_inspect"
                    fi
            fi
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do

            # Check if audit watch file system object rule for given path already present
            if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
            then
                    # Rule is found => verify yet if existing rule definition contains
                    # all of the required access type bits

                    # Escape slashes in path for use in sed pattern below
                    local esc_path=${path//$'/'/$'\/'}
                    # Define BRE whitespace class shortcut
                    local sp="[[:space:]]"
                    # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
                    current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
                    # Split required access bits string into characters array
                    # (to check bit's presence for one bit at a time)
                    for access_bit in $(echo "$required_access_bits" | grep -o .)
                    do
                            # For each from the required access bits (e.g. 'w', 'a') check
                            # if they are already present in current access bits for rule.
                            # If not, append that bit at the end
                            if ! grep -q "$access_bit" <<< "$current_access_bits"
                            then
                                    # Concatenate the existing mask with the missing bit
                                    current_access_bits="$current_access_bits$access_bit"
                            fi
                    done
                    # Propagate the updated rule's access bits (original + the required
                    # ones) back into the /etc/audit/audit.rules file for that rule
                    sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
            else
                    # Rule isn't present yet. Append it at the end of $audit_rules_file file
                    # with proper key

                    echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
            fi
    done
    }

    fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"

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

Add the following to `/etc/audit/audit.rules`, in order to capture events that modify account changes:

    # audit_rules_usergroup_modification
    -w /etc/group -p wa -k audit_rules_usergroup_modification
    -w /etc/passwd -p wa -k audit_rules_usergroup_modification
    -w /etc/gshadow -p wa -k audit_rules_usergroup_modification
    -w /etc/shadow -p wa -k audit_rules_usergroup_modification
    -w /etc/security/opasswd -p wa -k audit_rules_usergroup_modification

Rationale:

In addition to auditing new user and group accounts, these watches will alert the system administrator(s) to any modifications. Any unexpected users, groups, or modifications should be investigated for legitimacy.

Severity: low

References: AC-2(4), AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 18, 1403, 1404, 1405, 1684, 1683, 1685, 1686

Remediation Shell script: (show)

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

    # Perform the remediation

    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
            echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
            echo "Aborting."
            exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #       auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #       augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #       augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
            echo "Unknown audit rules loading tool: $1. Aborting."
            echo "Use either 'auditctl' or 'augenrules'!"
            exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
            files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
            # Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
            # Get pair -- filepath : matching_row into @matches array
            IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
            # Reset IFS back to default
            unset $IFS
            # For each of the matched entries
            for match in "${matches[@]}"
            do
                    # Extract filepath from the match
                    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
                    # Append that path into list of files for inspection
                    files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
            done
            # Case when particular audit rule isn't defined yet
            if [ ${#files_to_inspect[@]} -eq "0" ]
            then
                    # Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
                    files_to_inspect="/etc/audit/rules.d/$key.rules"
                    # If the $key.rules file doesn't exist yet, create it with correct permissions
                    if [ ! -e "$files_to_inspect" ]
                    then
                            touch "$files_to_inspect"
                            chmod 0640 "$files_to_inspect"
                    fi
            fi
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do

            # Check if audit watch file system object rule for given path already present
            if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
            then
                    # Rule is found => verify yet if existing rule definition contains
                    # all of the required access type bits

                    # Escape slashes in path for use in sed pattern below
                    local esc_path=${path//$'/'/$'\/'}
                    # Define BRE whitespace class shortcut
                    local sp="[[:space:]]"
                    # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
                    current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
                    # Split required access bits string into characters array
                    # (to check bit's presence for one bit at a time)
                    for access_bit in $(echo "$required_access_bits" | grep -o .)
                    do
                            # For each from the required access bits (e.g. 'w', 'a') check
                            # if they are already present in current access bits for rule.
                            # If not, append that bit at the end
                            if ! grep -q "$access_bit" <<< "$current_access_bits"
                            then
                                    # Concatenate the existing mask with the missing bit
                                    current_access_bits="$current_access_bits$access_bit"
                            fi
                    done
                    # Propagate the updated rule's access bits (original + the required
                    # ones) back into the /etc/audit/audit.rules file for that rule
                    sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
            else
                    # Rule isn't present yet. Append it at the end of $audit_rules_file file
                    # with proper key

                    echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
            fi
    done
    }

    fix_audit_watch_rule "auditctl" "/etc/group" "wa" "audit_rules_usergroup_modification"
    fix_audit_watch_rule "auditctl" "/etc/passwd" "wa" "audit_rules_usergroup_modification"

    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
            echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
            echo "Aborting."
            exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #       auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #       augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #       augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
            echo "Unknown audit rules loading tool: $1. Aborting."
            echo "Use either 'auditctl' or 'augenrules'!"
            exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
            files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
            # Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
            # Get pair -- filepath : matching_row into @matches array
            IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
            # Reset IFS back to default
            unset $IFS
            # For each of the matched entries
            for match in "${matches[@]}"
            do
                    # Extract filepath from the match
                    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
                    # Append that path into list of files for inspection
                    files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
            done
            # Case when particular audit rule isn't defined yet
            if [ ${#files_to_inspect[@]} -eq "0" ]
            then
                    # Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
                    files_to_inspect="/etc/audit/rules.d/$key.rules"
                    # If the $key.rules file doesn't exist yet, create it with correct permissions
                    if [ ! -e "$files_to_inspect" ]
                    then
                            touch "$files_to_inspect"
                            chmod 0640 "$files_to_inspect"
                    fi
            fi
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do

            # Check if audit watch file system object rule for given path already present
            if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
            then
                    # Rule is found => verify yet if existing rule definition contains
                    # all of the required access type bits

                    # Escape slashes in path for use in sed pattern below
                    local esc_path=${path//$'/'/$'\/'}
                    # Define BRE whitespace class shortcut
                    local sp="[[:space:]]"
                    # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
                    current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
                    # Split required access bits string into characters array
                    # (to check bit's presence for one bit at a time)
                    for access_bit in $(echo "$required_access_bits" | grep -o .)
                    do
                            # For each from the required access bits (e.g. 'w', 'a') check
                            # if they are already present in current access bits for rule.
                            # If not, append that bit at the end
                            if ! grep -q "$access_bit" <<< "$current_access_bits"
                            then
                                    # Concatenate the existing mask with the missing bit
                                    current_access_bits="$current_access_bits$access_bit"
                            fi
                    done
                    # Propagate the updated rule's access bits (original + the required
                    # ones) back into the /etc/audit/audit.rules file for that rule
                    sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
            else
                    # Rule isn't present yet. Append it at the end of $audit_rules_file file
                    # with proper key

                    echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
            fi
    done
    }

    fix_audit_watch_rule "auditctl" "/etc/gshadow" "wa" "audit_rules_usergroup_modification"
    fix_audit_watch_rule "auditctl" "/etc/shadow" "wa" "audit_rules_usergroup_modification"

    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
            echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
            echo "Aborting."
            exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #       auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #       augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #       augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
            echo "Unknown audit rules loading tool: $1. Aborting."
            echo "Use either 'auditctl' or 'augenrules'!"
            exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
            files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
            # Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
            # Get pair -- filepath : matching_row into @matches array
            IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
            # Reset IFS back to default
            unset $IFS
            # For each of the matched entries
            for match in "${matches[@]}"
            do
                    # Extract filepath from the match
                    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
                    # Append that path into list of files for inspection
                    files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
            done
            # Case when particular audit rule isn't defined yet
            if [ ${#files_to_inspect[@]} -eq "0" ]
            then
                    # Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
                    files_to_inspect="/etc/audit/rules.d/$key.rules"
                    # If the $key.rules file doesn't exist yet, create it with correct permissions
                    if [ ! -e "$files_to_inspect" ]
                    then
                            touch "$files_to_inspect"
                            chmod 0640 "$files_to_inspect"
                    fi
            fi
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do

            # Check if audit watch file system object rule for given path already present
            if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
            then
                    # Rule is found => verify yet if existing rule definition contains
                    # all of the required access type bits

                    # Escape slashes in path for use in sed pattern below
                    local esc_path=${path//$'/'/$'\/'}
                    # Define BRE whitespace class shortcut
                    local sp="[[:space:]]"
                    # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
                    current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
                    # Split required access bits string into characters array
                    # (to check bit's presence for one bit at a time)
                    for access_bit in $(echo "$required_access_bits" | grep -o .)
                    do
                            # For each from the required access bits (e.g. 'w', 'a') check
                            # if they are already present in current access bits for rule.
                            # If not, append that bit at the end
                            if ! grep -q "$access_bit" <<< "$current_access_bits"
                            then
                                    # Concatenate the existing mask with the missing bit
                                    current_access_bits="$current_access_bits$access_bit"
                            fi
                    done
                    # Propagate the updated rule's access bits (original + the required
                    # ones) back into the /etc/audit/audit.rules file for that rule
                    sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
            else
                    # Rule isn't present yet. Append it at the end of $audit_rules_file file
                    # with proper key

                    echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
            fi
    done
    }

    fix_audit_watch_rule "auditctl" "/etc/security/opasswd" "wa" "audit_rules_usergroup_modification"

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

Add the following to `/etc/audit/audit.rules`, setting ARCH to either b32 or b64 as appropriate for your system:

    # audit_rules_networkconfig_modification
    -a always,exit -F arch=ARCH -S sethostname -S setdomainname -k audit_rules_networkconfig_modification
    -w /etc/issue -p wa -k audit_rules_networkconfig_modification
    -w /etc/issue.net -p wa -k audit_rules_networkconfig_modification
    -w /etc/hosts -p wa -k audit_rules_networkconfig_modification
    -w /etc/sysconfig/network -p wa -k audit_rules_networkconfig_modification

Rationale:

The network environment should not be modified by anything other than administrator action. Any change to network parameters should be audited.

Severity: low

References: AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5

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"

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

    # Then perform the remediations for the watch rules

    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
            echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
            echo "Aborting."
            exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #       auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #       augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #       augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
            echo "Unknown audit rules loading tool: $1. Aborting."
            echo "Use either 'auditctl' or 'augenrules'!"
            exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
            files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
            # Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
            # Get pair -- filepath : matching_row into @matches array
            IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
            # Reset IFS back to default
            unset $IFS
            # For each of the matched entries
            for match in "${matches[@]}"
            do
                    # Extract filepath from the match
                    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
                    # Append that path into list of files for inspection
                    files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
            done
            # Case when particular audit rule isn't defined yet
            if [ ${#files_to_inspect[@]} -eq "0" ]
            then
                    # Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
                    files_to_inspect="/etc/audit/rules.d/$key.rules"
                    # If the $key.rules file doesn't exist yet, create it with correct permissions
                    if [ ! -e "$files_to_inspect" ]
                    then
                            touch "$files_to_inspect"
                            chmod 0640 "$files_to_inspect"
                    fi
            fi
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do

            # Check if audit watch file system object rule for given path already present
            if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
            then
                    # Rule is found => verify yet if existing rule definition contains
                    # all of the required access type bits

                    # Escape slashes in path for use in sed pattern below
                    local esc_path=${path//$'/'/$'\/'}
                    # Define BRE whitespace class shortcut
                    local sp="[[:space:]]"
                    # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
                    current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
                    # Split required access bits string into characters array
                    # (to check bit's presence for one bit at a time)
                    for access_bit in $(echo "$required_access_bits" | grep -o .)
                    do
                            # For each from the required access bits (e.g. 'w', 'a') check
                            # if they are already present in current access bits for rule.
                            # If not, append that bit at the end
                            if ! grep -q "$access_bit" <<< "$current_access_bits"
                            then
                                    # Concatenate the existing mask with the missing bit
                                    current_access_bits="$current_access_bits$access_bit"
                            fi
                    done
                    # Propagate the updated rule's access bits (original + the required
                    # ones) back into the /etc/audit/audit.rules file for that rule
                    sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
            else
                    # Rule isn't present yet. Append it at the end of $audit_rules_file file
                    # with proper key

                    echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
            fi
    done
    }

    fix_audit_watch_rule "auditctl" "/etc/issue" "wa" "audit_rules_networkconfig_modification"
    fix_audit_watch_rule "auditctl" "/etc/issue.net" "wa" "audit_rules_networkconfig_modification"

    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
            echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
            echo "Aborting."
            exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #       auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #       augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #       augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
            echo "Unknown audit rules loading tool: $1. Aborting."
            echo "Use either 'auditctl' or 'augenrules'!"
            exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
            files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
            # Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
            # Get pair -- filepath : matching_row into @matches array
            IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
            # Reset IFS back to default
            unset $IFS
            # For each of the matched entries
            for match in "${matches[@]}"
            do
                    # Extract filepath from the match
                    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
                    # Append that path into list of files for inspection
                    files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
            done
            # Case when particular audit rule isn't defined yet
            if [ ${#files_to_inspect[@]} -eq "0" ]
            then
                    # Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
                    files_to_inspect="/etc/audit/rules.d/$key.rules"
                    # If the $key.rules file doesn't exist yet, create it with correct permissions
                    if [ ! -e "$files_to_inspect" ]
                    then
                            touch "$files_to_inspect"
                            chmod 0640 "$files_to_inspect"
                    fi
            fi
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do

            # Check if audit watch file system object rule for given path already present
            if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
            then
                    # Rule is found => verify yet if existing rule definition contains
                    # all of the required access type bits

                    # Escape slashes in path for use in sed pattern below
                    local esc_path=${path//$'/'/$'\/'}
                    # Define BRE whitespace class shortcut
                    local sp="[[:space:]]"
                    # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
                    current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
                    # Split required access bits string into characters array
                    # (to check bit's presence for one bit at a time)
                    for access_bit in $(echo "$required_access_bits" | grep -o .)
                    do
                            # For each from the required access bits (e.g. 'w', 'a') check
                            # if they are already present in current access bits for rule.
                            # If not, append that bit at the end
                            if ! grep -q "$access_bit" <<< "$current_access_bits"
                            then
                                    # Concatenate the existing mask with the missing bit
                                    current_access_bits="$current_access_bits$access_bit"
                            fi
                    done
                    # Propagate the updated rule's access bits (original + the required
                    # ones) back into the /etc/audit/audit.rules file for that rule
                    sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
            else
                    # Rule isn't present yet. Append it at the end of $audit_rules_file file
                    # with proper key

                    echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
            fi
    done
    }

    fix_audit_watch_rule "auditctl" "/etc/hosts" "wa" "audit_rules_networkconfig_modification"
    fix_audit_watch_rule "auditctl" "/etc/sysconfig/network" "wa" "audit_rules_networkconfig_modification"

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

References: AC-6, AU-1(b), AU-9, IR-5, 166

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

#### System Audit Logs Must Be Owned By Root [ref]rule

To properly set the owner of `/var/log`, run the command:

    $ sudo chown root /var/log

Rationale:

Failure to give ownership of the audit log files to root allows the designated owner, and unauthorized users, potential access to sensitive information.

Severity: low

References: AC-6, AU-1(b), AU-9, IR-5, 166

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

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

Add the following to `/etc/audit/audit.rules`:

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

Rationale:

The system's mandatory access policy (SELinux) should not be arbitrarily changed by anything other than administrator action. All changes to MAC policy should be audited.

Severity: low

References: AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5

Remediation Shell script: (show)

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

    # Perform the remediation

    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"

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

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

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

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

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

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-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 126

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>=500 -F auid!=4294967295 -k *"
    	GROUP="chmod"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S chmod -S fchmod -S fchmodat -F auid>=500 -F auid!=4294967295 -k perm_mod"

    function 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"
    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. Add the following to `/etc/audit/audit.rules`:

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

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

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

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-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 126

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>=500 -F auid!=4294967295 -k *"
    	GROUP="chown"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S chown -S fchown -S fchownat -S lchown -F auid>=500 -F auid!=4294967295 -k perm_mod"

    function 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"
    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. Add the following to `/etc/audit/audit.rules`:

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

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

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

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-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 126

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>=500 -F auid!=4294967295 -k *"
    	GROUP="chmod"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S chmod -S fchmod -S fchmodat -F auid>=500 -F auid!=4294967295 -k perm_mod"

    function 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"
    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. Add the following to `/etc/audit/audit.rules`:

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

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

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

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-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 126

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>=500 -F auid!=4294967295 -k *"
    	GROUP="chmod"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S chmod -S fchmod -S fchmodat -F auid>=500 -F auid!=4294967295 -k perm_mod"

    function 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"
    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. Add the following to `/etc/audit/audit.rules`:

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

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

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

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-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 126

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>=500 -F auid!=4294967295 -k *"
    	GROUP="chown"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S chown -S fchown -S fchownat -S lchown -F auid>=500 -F auid!=4294967295 -k perm_mod"

    function 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"
    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. Add the following to `/etc/audit/audit.rules`:

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

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

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

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-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 126

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>=500 -F auid!=4294967295 -k *"
    	GROUP="chown"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S chown -S fchown -S fchownat -S lchown -F auid>=500 -F auid!=4294967295 -k perm_mod"

    function 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"
    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. Add the following to `/etc/audit/audit.rules`:

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

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

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

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-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 126

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>=500 -F auid!=4294967295 -k *"
    	GROUP="xattr"
    	FULL_RULE="-a always,exit -F arch=${ARCH} -S setxattr -S lsetxattr -S fsetxattr -S removexattr -S lremovexattr -S fremovexattr -F auid>=500 -F auid!=4294967295 -k perm_mod"

    function 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"
    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. Add the following to `/etc/audit/audit.rules`:

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

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

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

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-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 126

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>=500 -F auid!=4294967295 -k *"
    	GROUP="xattr"
    	FULL_RULE="-a always,exit -F arch=${ARCH} -S setxattr -S lsetxattr -S fsetxattr -S removexattr -S lremovexattr -S fremovexattr -F auid>=500 -F auid!=4294967295 -k perm_mod"

    function 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"
    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. Add the following to `/etc/audit/audit.rules`:

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

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

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

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-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 126

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>=500 -F auid!=4294967295 -k *"
    	GROUP="chown"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S chown -S fchown -S fchownat -S lchown -F auid>=500 -F auid!=4294967295 -k perm_mod"

    function 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"
    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. Add the following to `/etc/audit/audit.rules`:

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

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

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

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-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 126

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>=500 -F auid!=4294967295 -k *"
    	GROUP="xattr"
    	FULL_RULE="-a always,exit -F arch=${ARCH} -S setxattr -S lsetxattr -S fsetxattr -S removexattr -S lremovexattr -S fremovexattr -F auid>=500 -F auid!=4294967295 -k perm_mod"

    function 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"
    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. Add the following to `/etc/audit/audit.rules`:

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

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

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

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-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 126

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>=500 -F auid!=4294967295 -k *"
    	GROUP="xattr"
    	FULL_RULE="-a always,exit -F arch=${ARCH} -S setxattr -S lsetxattr -S fsetxattr -S removexattr -S lremovexattr -S fremovexattr -F auid>=500 -F auid!=4294967295 -k perm_mod"

    function 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"
    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. Add the following to `/etc/audit/audit.rules`:

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

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

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

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-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 126

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>=500 -F auid!=4294967295 -k *"
    	GROUP="xattr"
    	FULL_RULE="-a always,exit -F arch=${ARCH} -S setxattr -S lsetxattr -S fsetxattr -S removexattr -S lremovexattr -S fremovexattr -F auid>=500 -F auid!=4294967295 -k perm_mod"

    function 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"
    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. Add the following to `/etc/audit/audit.rules`:

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

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

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

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-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 126

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>=500 -F auid!=4294967295 -k *"
    	GROUP="xattr"
    	FULL_RULE="-a always,exit -F arch=${ARCH} -S setxattr -S lsetxattr -S fsetxattr -S removexattr -S lremovexattr -S fremovexattr -F auid>=500 -F auid!=4294967295 -k perm_mod"

    function 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"
    done

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

The audit system already collects login info for all users and root. To watch for attempted manual edits of files involved in storing login events, add the following to `/etc/audit/audit.rules`:

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

Rationale:

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

Severity: low

References: AC-3(10), AU-1(b), AU-12(a), AU-12(c), IR-5

Remediation Shell script: (show)

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

    # Perform the remediation

    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
            echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
            echo "Aborting."
            exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #       auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #       augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #       augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
            echo "Unknown audit rules loading tool: $1. Aborting."
            echo "Use either 'auditctl' or 'augenrules'!"
            exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
            files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
            # Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
            # Get pair -- filepath : matching_row into @matches array
            IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
            # Reset IFS back to default
            unset $IFS
            # For each of the matched entries
            for match in "${matches[@]}"
            do
                    # Extract filepath from the match
                    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
                    # Append that path into list of files for inspection
                    files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
            done
            # Case when particular audit rule isn't defined yet
            if [ ${#files_to_inspect[@]} -eq "0" ]
            then
                    # Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
                    files_to_inspect="/etc/audit/rules.d/$key.rules"
                    # If the $key.rules file doesn't exist yet, create it with correct permissions
                    if [ ! -e "$files_to_inspect" ]
                    then
                            touch "$files_to_inspect"
                            chmod 0640 "$files_to_inspect"
                    fi
            fi
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do

            # Check if audit watch file system object rule for given path already present
            if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
            then
                    # Rule is found => verify yet if existing rule definition contains
                    # all of the required access type bits

                    # Escape slashes in path for use in sed pattern below
                    local esc_path=${path//$'/'/$'\/'}
                    # Define BRE whitespace class shortcut
                    local sp="[[:space:]]"
                    # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
                    current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
                    # Split required access bits string into characters array
                    # (to check bit's presence for one bit at a time)
                    for access_bit in $(echo "$required_access_bits" | grep -o .)
                    do
                            # For each from the required access bits (e.g. 'w', 'a') check
                            # if they are already present in current access bits for rule.
                            # If not, append that bit at the end
                            if ! grep -q "$access_bit" <<< "$current_access_bits"
                            then
                                    # Concatenate the existing mask with the missing bit
                                    current_access_bits="$current_access_bits$access_bit"
                            fi
                    done
                    # Propagate the updated rule's access bits (original + the required
                    # ones) back into the /etc/audit/audit.rules file for that rule
                    sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
            else
                    # Rule isn't present yet. Append it at the end of $audit_rules_file file
                    # with proper key

                    echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
            fi
    done
    }

    fix_audit_watch_rule "auditctl" "/var/log/tallylog" "wa" "logins"
    fix_audit_watch_rule "auditctl" "/var/run/faillock/" "wa" "logins"

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

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

The audit system already collects process information for all users and root. To watch for attempted manual edits of files involved in storing such process information, add the following to `/etc/audit/audit.rules`:

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

Rationale:

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

Severity: low

References: AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5

Remediation Shell script: (show)

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

    # Perform the remediation

    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
            echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
            echo "Aborting."
            exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #       auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #       augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #       augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
            echo "Unknown audit rules loading tool: $1. Aborting."
            echo "Use either 'auditctl' or 'augenrules'!"
            exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
            files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
            # Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
            # Get pair -- filepath : matching_row into @matches array
            IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
            # Reset IFS back to default
            unset $IFS
            # For each of the matched entries
            for match in "${matches[@]}"
            do
                    # Extract filepath from the match
                    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
                    # Append that path into list of files for inspection
                    files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
            done
            # Case when particular audit rule isn't defined yet
            if [ ${#files_to_inspect[@]} -eq "0" ]
            then
                    # Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
                    files_to_inspect="/etc/audit/rules.d/$key.rules"
                    # If the $key.rules file doesn't exist yet, create it with correct permissions
                    if [ ! -e "$files_to_inspect" ]
                    then
                            touch "$files_to_inspect"
                            chmod 0640 "$files_to_inspect"
                    fi
            fi
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do

            # Check if audit watch file system object rule for given path already present
            if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
            then
                    # Rule is found => verify yet if existing rule definition contains
                    # all of the required access type bits

                    # Escape slashes in path for use in sed pattern below
                    local esc_path=${path//$'/'/$'\/'}
                    # Define BRE whitespace class shortcut
                    local sp="[[:space:]]"
                    # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
                    current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
                    # Split required access bits string into characters array
                    # (to check bit's presence for one bit at a time)
                    for access_bit in $(echo "$required_access_bits" | grep -o .)
                    do
                            # For each from the required access bits (e.g. 'w', 'a') check
                            # if they are already present in current access bits for rule.
                            # If not, append that bit at the end
                            if ! grep -q "$access_bit" <<< "$current_access_bits"
                            then
                                    # Concatenate the existing mask with the missing bit
                                    current_access_bits="$current_access_bits$access_bit"
                            fi
                    done
                    # Propagate the updated rule's access bits (original + the required
                    # ones) back into the /etc/audit/audit.rules file for that rule
                    sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
            else
                    # Rule isn't present yet. Append it at the end of $audit_rules_file file
                    # with proper key

                    echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
            fi
    done
    }

    fix_audit_watch_rule "auditctl" "/var/run/utmp" "wa" "session"
    fix_audit_watch_rule "auditctl" "/var/log/btmp" "wa" "session"

    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
            echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
            echo "Aborting."
            exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #       auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #       augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #       augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
            echo "Unknown audit rules loading tool: $1. Aborting."
            echo "Use either 'auditctl' or 'augenrules'!"
            exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
            files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
            # Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
            # Get pair -- filepath : matching_row into @matches array
            IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
            # Reset IFS back to default
            unset $IFS
            # For each of the matched entries
            for match in "${matches[@]}"
            do
                    # Extract filepath from the match
                    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
                    # Append that path into list of files for inspection
                    files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
            done
            # Case when particular audit rule isn't defined yet
            if [ ${#files_to_inspect[@]} -eq "0" ]
            then
                    # Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
                    files_to_inspect="/etc/audit/rules.d/$key.rules"
                    # If the $key.rules file doesn't exist yet, create it with correct permissions
                    if [ ! -e "$files_to_inspect" ]
                    then
                            touch "$files_to_inspect"
                            chmod 0640 "$files_to_inspect"
                    fi
            fi
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do

            # Check if audit watch file system object rule for given path already present
            if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
            then
                    # Rule is found => verify yet if existing rule definition contains
                    # all of the required access type bits

                    # Escape slashes in path for use in sed pattern below
                    local esc_path=${path//$'/'/$'\/'}
                    # Define BRE whitespace class shortcut
                    local sp="[[:space:]]"
                    # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
                    current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
                    # Split required access bits string into characters array
                    # (to check bit's presence for one bit at a time)
                    for access_bit in $(echo "$required_access_bits" | grep -o .)
                    do
                            # For each from the required access bits (e.g. 'w', 'a') check
                            # if they are already present in current access bits for rule.
                            # If not, append that bit at the end
                            if ! grep -q "$access_bit" <<< "$current_access_bits"
                            then
                                    # Concatenate the existing mask with the missing bit
                                    current_access_bits="$current_access_bits$access_bit"
                            fi
                    done
                    # Propagate the updated rule's access bits (original + the required
                    # ones) back into the /etc/audit/audit.rules file for that rule
                    sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
            else
                    # Rule isn't present yet. Append it at the end of $audit_rules_file file
                    # with proper key

                    echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
            fi
    done
    }

    fix_audit_watch_rule "auditctl" "/var/log/wtmp" "wa" "session"

#### Ensure auditd Collects Unauthorized Access Attempts to Files (unsuccessful) [ref]rule

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

    -a always,exit -F arch=b32 -S creat -S open -S openat -S open_by_handle_at -S truncate -S ftruncate -F exit=-EACCES -F auid>=500 -F auid!=4294967295 -k access
    -a always,exit -F arch=b32 -S creat -S open -S openat -S open_by_handle_at -S truncate -S ftruncate -F exit=-EPERM -F auid>=500 -F auid!=4294967295 -k access

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

    -a always,exit -F arch=b64 -S creat -S open -S openat -S open_by_handle_at -S truncate -S ftruncate -F exit=-EACCES -F auid>=500 -F auid!=4294967295 -k access
    -a always,exit -F arch=b64 -S creat -S open -S openat -S open_by_handle_at -S truncate -S ftruncate -F exit=-EPERM -F auid>=500 -F auid!=4294967295 -k access

Rationale:

Unsuccessful attempts to access files could be an indicator of malicious activity on a system. Auditing these events could serve as evidence of potential system compromise.

Severity: low

References: AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 126

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>=500 -F auid!=4294967295 -k *"
    	# Use escaped BRE regex to specify rule group
    	GROUP="\(creat\|open\|truncate\)"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S creat -S open -S openat -S open_by_handle_at -S truncate -S ftruncate -F exit=-EACCES -F auid>=500 -F auid!=4294967295 -k access"

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

    	# Then fix the -EPERM requirement
    	PATTERN="-a always,exit -F arch=$ARCH -S .* -F exit=-EPERM -F auid>=500 -F auid!=4294967295 -k *"
    	# No need to change content of $GROUP variable - it's the same as for -EACCES case above
    	FULL_RULE="-a always,exit -F arch=$ARCH -S creat -S open -S openat -S open_by_handle_at -S truncate -S ftruncate -F exit=-EPERM -F auid>=500 -F auid!=4294967295 -k access"

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

    done

#### Ensure auditd Collects Information on the Use of Privileged Commands [ref]rule

At a minimum the audit system should collect the execution of privileged commands for all users and root. To find the relevant setuid / setgid programs, run the following command for each local partition PART:

    $ sudo find PART -xdev -type f -perm -4000 -o -type f -perm -2000 2>/dev/null

Then, for each setuid / setgid program on the system, add a line of the following form to `/etc/audit/audit.rules`, where SETUID_PROG_PATH is the full path to each setuid / setgid program in the list:

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

Rationale:

Privileged programs are subject to escalation-of-privilege attacks, which attempt to subvert their normal role of providing some necessary but limited capability. As such, motivation exists to monitor these programs for unusual activity.

Severity: low

References: AC-3(10)), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AC-6(9), AU-12(a), AU-12(c), IR-5, 40

Remediation Shell script: (show)

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

    # Perform the remediation

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

#### Ensure auditd Collects Information on Exporting to Media (successful) [ref]rule

At a minimum the audit system should collect media exportation events for all users and root. Add the following to `/etc/audit/audit.rules`, setting ARCH to either b32 or b64 as appropriate for your system:

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

Rationale:

The unauthorized exportation of data to external media could result in an information leak where classified information, Privacy Act information, and intellectual property could be lost. An audit trail should be created each time a filesystem is mounted to help identify and guard against information loss.

Severity: low

References: AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 126

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>=500 -F auid!=4294967295 -k *"
    	GROUP="mount"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S mount -F auid>=500 -F auid!=4294967295 -k export"

    function 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"
    done

#### Ensure auditd Collects File Deletion Events by User [ref]rule

At a minimum the audit system should collect file deletion events for all users and root. Add the following to `/etc/audit/audit.rules`, setting ARCH to either b32 or b64 as appropriate for your system:

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

Rationale:

Auditing file deletions will create an audit trail for files that are removed from the system. The audit trail could aid in system troubleshooting, as well as, detecting malicious processes that attempt to delete log files to conceal their presence.

Severity: low

References: AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 126

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>=500 -F auid!=4294967295 -k delete"
    	# Use escaped BRE regex to specify rule group
    	GROUP="\(rmdir\|unlink\|rename\)"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S rmdir -S unlink -S unlinkat -S rename -S renameat -F auid>=500 -F auid!=4294967295 -k delete"

    function 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"
    done

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

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

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

Rationale:

The actions taken by system administrators should be audited to keep a record of what was executed on the system, as well as, for accountability purposes.

Severity: low

References: AC-2(7)(b), AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 126

Remediation Shell script: (show)

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

    # Perform the remediation

    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"

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

Add the following to `/etc/audit/audit.rules` in order to capture kernel module loading and unloading events, setting ARCH to either b32 or b64 as appropriate for your system:

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

Rationale:

The addition/removal of kernel modules can be used to alter the behavior of the kernel and potentially introduce malicious code into kernel space. It is important to have an audit trail of modules that have been introduced into the kernel.

Severity: low

References: AC-3(10), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 126

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"

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

    # Then perform the remediations for the watch rules

    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
            echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
            echo "Aborting."
            exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #       auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #       augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #       augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
            echo "Unknown audit rules loading tool: $1. Aborting."
            echo "Use either 'auditctl' or 'augenrules'!"
            exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
            files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
            # Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
            # Get pair -- filepath : matching_row into @matches array
            IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
            # Reset IFS back to default
            unset $IFS
            # For each of the matched entries
            for match in "${matches[@]}"
            do
                    # Extract filepath from the match
                    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
                    # Append that path into list of files for inspection
                    files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
            done
            # Case when particular audit rule isn't defined yet
            if [ ${#files_to_inspect[@]} -eq "0" ]
            then
                    # Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
                    files_to_inspect="/etc/audit/rules.d/$key.rules"
                    # If the $key.rules file doesn't exist yet, create it with correct permissions
                    if [ ! -e "$files_to_inspect" ]
                    then
                            touch "$files_to_inspect"
                            chmod 0640 "$files_to_inspect"
                    fi
            fi
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do

            # Check if audit watch file system object rule for given path already present
            if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
            then
                    # Rule is found => verify yet if existing rule definition contains
                    # all of the required access type bits

                    # Escape slashes in path for use in sed pattern below
                    local esc_path=${path//$'/'/$'\/'}
                    # Define BRE whitespace class shortcut
                    local sp="[[:space:]]"
                    # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
                    current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
                    # Split required access bits string into characters array
                    # (to check bit's presence for one bit at a time)
                    for access_bit in $(echo "$required_access_bits" | grep -o .)
                    do
                            # For each from the required access bits (e.g. 'w', 'a') check
                            # if they are already present in current access bits for rule.
                            # If not, append that bit at the end
                            if ! grep -q "$access_bit" <<< "$current_access_bits"
                            then
                                    # Concatenate the existing mask with the missing bit
                                    current_access_bits="$current_access_bits$access_bit"
                            fi
                    done
                    # Propagate the updated rule's access bits (original + the required
                    # ones) back into the /etc/audit/audit.rules file for that rule
                    sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
            else
                    # Rule isn't present yet. Append it at the end of $audit_rules_file file
                    # with proper key

                    echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
            fi
    done
    }

    fix_audit_watch_rule "auditctl" "/sbin/insmod" "x" "modules"
    fix_audit_watch_rule "auditctl" "/sbin/rmmod" "x" "modules"

    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
            echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
            echo "Aborting."
            exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #       auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #       augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #       augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
            echo "Unknown audit rules loading tool: $1. Aborting."
            echo "Use either 'auditctl' or 'augenrules'!"
            exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
            files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
            # Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
            # Get pair -- filepath : matching_row into @matches array
            IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
            # Reset IFS back to default
            unset $IFS
            # For each of the matched entries
            for match in "${matches[@]}"
            do
                    # Extract filepath from the match
                    rulesd_audit_file=$(echo $match | cut -f1 -d ':')
                    # Append that path into list of files for inspection
                    files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
            done
            # Case when particular audit rule isn't defined yet
            if [ ${#files_to_inspect[@]} -eq "0" ]
            then
                    # Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
                    files_to_inspect="/etc/audit/rules.d/$key.rules"
                    # If the $key.rules file doesn't exist yet, create it with correct permissions
                    if [ ! -e "$files_to_inspect" ]
                    then
                            touch "$files_to_inspect"
                            chmod 0640 "$files_to_inspect"
                    fi
            fi
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do

            # Check if audit watch file system object rule for given path already present
            if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
            then
                    # Rule is found => verify yet if existing rule definition contains
                    # all of the required access type bits

                    # Escape slashes in path for use in sed pattern below
                    local esc_path=${path//$'/'/$'\/'}
                    # Define BRE whitespace class shortcut
                    local sp="[[:space:]]"
                    # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
                    current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
                    # Split required access bits string into characters array
                    # (to check bit's presence for one bit at a time)
                    for access_bit in $(echo "$required_access_bits" | grep -o .)
                    do
                            # For each from the required access bits (e.g. 'w', 'a') check
                            # if they are already present in current access bits for rule.
                            # If not, append that bit at the end
                            if ! grep -q "$access_bit" <<< "$current_access_bits"
                            then
                                    # Concatenate the existing mask with the missing bit
                                    current_access_bits="$current_access_bits$access_bit"
                            fi
                    done
                    # Propagate the updated rule's access bits (original + the required
                    # ones) back into the /etc/audit/audit.rules file for that rule
                    sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
            else
                    # Rule isn't present yet. Append it at the end of $audit_rules_file file
                    # with proper key

                    echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
            fi
    done
    }

    fix_audit_watch_rule "auditctl" "/sbin/modprobe" "x" "modules"

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

Add the following to `/etc/audit/audit.rules` in order to make the configuration immutable:

    -e 2

With this setting, a reboot will be required to change any audit rules.Rationale:

Making the audit configuration immutable prevents accidental as well as malicious modification of the audit rules, although it may be problematic if legitimate changes are needed during system operation

Severity: low

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

Remediation Shell script: (show)

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

    readonly AUDIT_RULES='/etc/audit/audit.rules'

    # If '-e .*' setting present in audit.rules already, delete it since the
    # auditctl(8) manual page instructs it should be the last rule in configuration
    sed -i '/-e[[:space:]]\+.*/d' $AUDIT_RULES

    # Append '-e 2' requirement at the end of audit.rules
    echo '' >> $AUDIT_RULES
    echo '# Set the audit.rules configuration immutable per security requirements' >> $AUDIT_RULES
    echo '# Reboot is required to change audit rules once this setting is applied' >> $AUDIT_RULES
    echo '-e 2' >> $AUDIT_RULES

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

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

    ClientAliveInterval 900

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

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

Severity: low

References: AC-2(5), SA-8, 879, 1133

Remediation Shell script: (show)

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

    sshd_idle_timeout_value="900"

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

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

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

    $ sudo chkconfig --level 2345 ntpd on

Rationale:

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

Severity: medium

References: AU-8(1), 160

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 ntpd

Remediation Ansible snippet: (show)

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

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

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

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

    server ntpserver

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

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

Severity: medium

References: AU-8(1), 160

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

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

    server ntpserver

Rationale:

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

Severity: low

References: AU-8(1)

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.