contains 358 rules |
Services
[ref]groupThe best protection against vulnerable software is running less software. This section describes how to review
the software which Red Hat Enterprise Linux 7 installs on a system and disable software which is not needed. It
then enumerates the software packages installed on a default Red Hat Enterprise Linux 7 system and provides guidance about which
ones can be safely disabled.
Red Hat Enterprise Linux 7 provides a convenient minimal install option that essentially installs the bare necessities for a functional
system. When building Red Hat Enterprise Linux 7 systems, it is highly recommended to select the minimal packages and then build up
the system from there. |
contains 55 rules |
Obsolete Services
[ref]groupThis section discusses a number of network-visible
services which have historically caused problems for system
security, and for which disabling or severely limiting the service
has been the best available guidance for some time. As a result of
this, many of these services are not installed as part of Red Hat Enterprise Linux 7
by default.
Organizations which are running these services should
switch to more secure equivalents as soon as possible.
If it remains absolutely necessary to run one of
these services for legacy reasons, care should be taken to restrict
the service as much as possible, for instance by configuring host
firewall software such as firewalld to restrict access to the
vulnerable service to only those remote hosts which have a known
need to use it. |
contains 16 rules |
Rlogin, Rsh, and Rexec
[ref]groupThe Berkeley r-commands are legacy services which
allow cleartext remote access and have an insecure trust
model. |
contains 6 rules |
Uninstall rsh Package
[ref]ruleThe rsh package contains the client commands
for the rsh services Rationale:These legacy clients contain numerous security exposures and have
been replaced with the more secure SSH package. Even if the server is removed,
it is best to ensure the clients are also removed to prevent users from
inadvertently attempting to use these commands and therefore exposing
their credentials. Note that removing the rsh package removes
the clients for rsh ,rcp , and rlogin . References:
2.3.2, 3.1.13, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), A.8.2.3, A.13.1.1, A.13.2.1, A.13.2.3, A.14.1.2, A.14.1.3 Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
# Function to remove packages on RHEL, Fedora, Debian, and possibly other systems.
#
# Example Call(s):
#
# package_remove telnet-server
#
function package_remove {
# Load function arguments into local variables
local package="$1"
# Check sanity of the input
if [ $# -ne "1" ]
then
echo "Usage: package_remove 'package_name'"
echo "Aborting."
exit 1
fi
if which dnf ; then
if rpm -q --quiet "$package"; then
dnf remove -y "$package"
fi
elif which yum ; then
if rpm -q --quiet "$package"; then
yum remove -y "$package"
fi
elif which apt-get ; then
apt-get remove -y "$package"
else
echo "Failed to detect available packaging system, tried dnf, yum and apt-get!"
echo "Aborting."
exit 1
fi
}
package_remove rsh
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
- name: Ensure rsh is removed
package:
name="{{item}}"
state=absent
with_items:
- rsh
tags:
- package_rsh_removed
- unknown_severity
- disable_strategy
- low_complexity
- low_disruption
- CCE-27274-0
- NIST-800-171-3.1.13
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
include remove_rsh
class remove_rsh {
package { 'rsh':
ensure => 'purged',
}
}
Remediation Anaconda snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
package --remove=rsh
|
Disable rlogin Service
[ref]ruleThe rlogin service, which is available with
the rsh-server package and runs as a service through xinetd or separately
as a systemd socket, should be disabled.
If using xinetd, set disable to yes in /etc/xinetd.d/rlogin .
If using systemd,
The rlogin socket can be disabled with the following command:
$ sudo systemctl disable rlogin.socket Rationale:The rlogin service uses unencrypted network communications, which
means that data from the login session, including passwords and
all other information transmitted during the session, can be
stolen by eavesdroppers on the network. References:
2.2.17, 3.1.13, 3.4.7, CCI-001436, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), AC-17(8), CM-7, IA-5(1)(c) Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
grep -qi disable /etc/xinetd.d/rlogin && \
sed -i "s/disable.*/disable = yes/gI" /etc/xinetd.d/rlogin
#
# Disable rlogin.socket for all systemd targets
#
systemctl disable rlogin.socket
#
# Stop rlogin.socket if currently running
#
systemctl stop rlogin.socket
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
- name: Disable service rlogin
service:
name="{{item}}"
enabled="no"
state="stopped"
register: service_result
failed_when: "service_result|failed and ('Could not find the requested service' not in service_result.msg)"
with_items:
- rlogin
tags:
- service_rlogin_disabled
- high_severity
- disable_strategy
- low_complexity
- low_disruption
- CCE-27336-7
- NIST-800-53-AC-17(8)
- NIST-800-53-CM-7
- NIST-800-53-IA-5(1)(c)
- NIST-800-171-3.1.13
- NIST-800-171-3.4.7
|
Disable rexec Service
[ref]ruleThe rexec service, which is available with
the rsh-server package and runs as a service through xinetd or separately
as a systemd socket, should be disabled.
If using xinetd, set disable to yes in /etc/xinetd.d/rexec .
If using systemd,
The rexec socket can be disabled with the following command:
$ sudo systemctl disable rexec.socket Rationale:The rexec service uses unencrypted network communications, which
means that data from the login session, including passwords and
all other information transmitted during the session, can be
stolen by eavesdroppers on the network. References:
2.2.17, 3.1.13, 3.4.7, CCI-000068, CCI-001436, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), AC-17(8), CM-7 Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
grep -qi disable /etc/xinetd.d/rexec && \
sed -i "s/disable.*/disable = yes/gI" /etc/xinetd.d/rexec
#
# Disable rexec.socket for all systemd targets
#
systemctl disable rexec.socket
#
# Stop rexec.socket if currently running
#
systemctl stop rexec.socket
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
- name: Disable service rexec
service:
name="{{item}}"
enabled="no"
state="stopped"
register: service_result
failed_when: "service_result|failed and ('Could not find the requested service' not in service_result.msg)"
with_items:
- rexec
tags:
- service_rexec_disabled
- high_severity
- disable_strategy
- low_complexity
- low_disruption
- CCE-27408-4
- NIST-800-53-AC-17(8)
- NIST-800-53-CM-7
- NIST-800-171-3.1.13
- NIST-800-171-3.4.7
|
Disable rsh Service
[ref]ruleThe rsh service, which is available with
the rsh-server package and runs as a service through xinetd or separately
as a systemd socket, should be disabled.
If using xinetd, set disable to yes in /etc/xinetd.d/rsh .
If using systemd,
The rsh socket can be disabled with the following command:
$ sudo systemctl disable rsh.socket Rationale:The rsh service uses unencrypted network communications, which
means that data from the login session, including passwords and
all other information transmitted during the session, can be
stolen by eavesdroppers on the network. References:
2.2.17, 3.1.13, 3.4.7, CCI-000068, CCI-001436, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), AC-17(8), CM-7, IA-5(1)(c) Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
grep -qi disable /etc/xinetd.d/rsh && \
sed -i "s/disable.*/disable = yes/gI" /etc/xinetd.d/rsh
#
# Disable rsh.socket for all systemd targets
#
systemctl disable rsh.socket
#
# Stop rsh.socket if currently running
#
systemctl stop rsh.socket
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
- name: Disable service rsh
service:
name="{{item}}"
enabled="no"
state="stopped"
register: service_result
failed_when: "service_result|failed and ('Could not find the requested service' not in service_result.msg)"
with_items:
- rsh
tags:
- service_rsh_disabled
- high_severity
- disable_strategy
- low_complexity
- low_disruption
- CCE-27337-5
- NIST-800-53-AC-17(8)
- NIST-800-53-CM-7
- NIST-800-53-IA-5(1)(c)
- NIST-800-171-3.1.13
- NIST-800-171-3.4.7
|
Uninstall rsh-server Package
[ref]ruleThe rsh-server package can be uninstalled with
the following command:
$ sudo yum erase rsh-server Rationale:The rsh-server service provides unencrypted remote access service which does not
provide for the confidentiality and integrity of user passwords or the remote session and has very weak
authentication. If a privileged user were to login using this service, the privileged user password
could be compromised. The rsh-server package provides several obsolete and insecure
network services. Removing it decreases the risk of those services' accidental (or intentional)
activation. References:
SV-86591r1_rule, CCI-000381, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), A.8.2.3, A.13.1.1, A.13.2.1, A.13.2.3, A.14.1.2, A.14.1.3, AC-17(8), CM-7(a), SRG-OS-000095-GPOS-00049 Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
# Function to remove packages on RHEL, Fedora, Debian, and possibly other systems.
#
# Example Call(s):
#
# package_remove telnet-server
#
function package_remove {
# Load function arguments into local variables
local package="$1"
# Check sanity of the input
if [ $# -ne "1" ]
then
echo "Usage: package_remove 'package_name'"
echo "Aborting."
exit 1
fi
if which dnf ; then
if rpm -q --quiet "$package"; then
dnf remove -y "$package"
fi
elif which yum ; then
if rpm -q --quiet "$package"; then
yum remove -y "$package"
fi
elif which apt-get ; then
apt-get remove -y "$package"
else
echo "Failed to detect available packaging system, tried dnf, yum and apt-get!"
echo "Aborting."
exit 1
fi
}
package_remove rsh-server
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
- name: Ensure rsh-server is removed
package:
name="{{item}}"
state=absent
with_items:
- rsh-server
tags:
- package_rsh-server_removed
- high_severity
- disable_strategy
- low_complexity
- low_disruption
- CCE-27342-5
- NIST-800-53-AC-17(8)
- NIST-800-53-CM-7(a)
- DISA-STIG-RHEL-07-020000
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
include remove_rsh-server
class remove_rsh-server {
package { 'rsh-server':
ensure => 'purged',
}
}
Remediation Anaconda snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
package --remove=rsh-server
|
Remove Rsh Trust Files
[ref]ruleThe files /etc/hosts.equiv and ~/.rhosts (in
each user's home directory) list remote hosts and users that are trusted by the
local system when using the rshd daemon.
To remove these files, run the following command to delete them from any
location:
$ sudo rm /etc/hosts.equiv
$ rm ~/.rhosts Rationale:Trust files are convenient, but when
used in conjunction with the R-services, they can allow
unauthenticated access to a system. Remediation Shell script: (show)
find /home -maxdepth 2 -type f -name .rhosts -exec rm -f '{}' \;
if [ -f /etc/hosts.equiv ]; then
/bin/rm -f /etc/hosts.equiv
fi
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- block:
- name: "Detect shosts.equiv Files on the System"
find:
paths: /
recurse: yes
patterns: shosts.equiv
check_mode: no
register: shosts_equiv_locations
- name: "Remove Rsh Trust Files"
file:
path: "{{ item.path }}"
state: absent
with_items: "{{ shosts_equiv_locations.files }}"
when: shosts_equiv_locations
tags:
- no_rsh_trust_files
- high_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27406-8
- NIST-800-53-AC-17(8)
- NIST-800-53-CM-7
|
Telnet
[ref]groupThe telnet protocol does not provide confidentiality or integrity
for information transmitted on the network. This includes authentication
information such as passwords. Organizations which use telnet should be
actively working to migrate to a more secure protocol. |
contains 3 rules |
Remove telnet Clients
[ref]ruleThe telnet client allows users to start connections to other
systems via the telnet protocol. Rationale:The telnet protocol is insecure and unencrypted. The use
of an unencrypted transmission medium could allow an unauthorized user
to steal credentials. The ssh package provides an
encrypted session and stronger security and is included in Red Hat
Enterprise Linux. References:
2.3.4, 3.1.13, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), A.8.2.3, A.13.1.1, A.13.2.1, A.13.2.3, A.14.1.2, A.14.1.3 Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
# Function to remove packages on RHEL, Fedora, Debian, and possibly other systems.
#
# Example Call(s):
#
# package_remove telnet-server
#
function package_remove {
# Load function arguments into local variables
local package="$1"
# Check sanity of the input
if [ $# -ne "1" ]
then
echo "Usage: package_remove 'package_name'"
echo "Aborting."
exit 1
fi
if which dnf ; then
if rpm -q --quiet "$package"; then
dnf remove -y "$package"
fi
elif which yum ; then
if rpm -q --quiet "$package"; then
yum remove -y "$package"
fi
elif which apt-get ; then
apt-get remove -y "$package"
else
echo "Failed to detect available packaging system, tried dnf, yum and apt-get!"
echo "Aborting."
exit 1
fi
}
package_remove telnet
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
- name: Ensure telnet is removed
package:
name="{{item}}"
state=absent
with_items:
- telnet
tags:
- package_telnet_removed
- low_severity
- disable_strategy
- low_complexity
- low_disruption
- CCE-27305-2
- NIST-800-171-3.1.13
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
include remove_telnet
class remove_telnet {
package { 'telnet':
ensure => 'purged',
}
}
Remediation Anaconda snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
package --remove=telnet
|
Disable telnet Service
[ref]ruleThe telnet service configuration file /etc/xinetd.d/telnet
is not created automatically. If it was created manually, check the
/etc/xinetd.d/telnet file and ensure that disable = no
is changed to read disable = yes as follows below:
# description: The telnet server serves telnet sessions; it uses \\
# unencrypted username/password pairs for authentication.
service telnet
{
flags = REUSE
socket_type = stream
wait = no
user = root
server = /usr/sbin/in.telnetd
log_on_failure += USERID
disable = yes
}
If the /etc/xinetd.d/telnet file does not exist, make sure that
the activation of the telnet service on system boot is disabled
via the following command:
The rexec socket can be disabled with the following command:
$ sudo systemctl disable rexec.socket Rationale:The telnet protocol uses unencrypted network communication, which
means that data from the login session, including passwords and
all other information transmitted during the session, can be
stolen by eavesdroppers on the network. The telnet protocol is also
subject to man-in-the-middle attacks. References:
2.2.18, 3.1.13, 3.4.7, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), AC-17(8), CM-7, IA-5(1)(c) Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
grep -qi disable /etc/xinetd.d/telnet && \
sed -i "s/disable.*/disable = yes/gI" /etc/xinetd.d/telnet
#
# Disable telnet.socket for all systemd targets
#
systemctl disable telnet.socket
#
# Stop telnet.socket if currently running
#
systemctl stop telnet.socket
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
- name: Disable service telnet
service:
name="{{item}}"
enabled="no"
state="stopped"
register: service_result
failed_when: "service_result|failed and ('Could not find the requested service' not in service_result.msg)"
with_items:
- telnet
tags:
- service_telnet_disabled
- high_severity
- disable_strategy
- low_complexity
- low_disruption
- CCE-27401-9
- NIST-800-53-AC-17(8)
- NIST-800-53-CM-7
- NIST-800-53-IA-5(1)(c)
- NIST-800-171-3.1.13
- NIST-800-171-3.4.7
|
Uninstall telnet-server Package
[ref]ruleThe telnet-server package can be uninstalled with
the following command:
$ sudo yum erase telnet-server Rationale:It is detrimental for operating systems to provide, or install by default, functionality exceeding
requirements or mission objectives. These unnecessary capabilities are often overlooked and therefore
may remain unsecure. They increase the risk to the platform by providing additional attack vectors.
The telnet service provides an unencrypted remote access service which does not provide for the
confidentiality and integrity of user passwords or the remote session. If a privileged user were
to login using this service, the privileged user password could be compromised.
Removing the telnet-server package decreases the risk of the telnet service's accidental
(or intentional) activation. References:
SV-86701r1_rule, 2.1.1, CCI-000381, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), A.8.2.3, A.13.1.1, A.13.2.1, A.13.2.3, A.14.1.2, A.14.1.3, AC-17(8), CM-7(a), SRG-OS-000095-GPOS-00049 Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
# Function to remove packages on RHEL, Fedora, Debian, and possibly other systems.
#
# Example Call(s):
#
# package_remove telnet-server
#
function package_remove {
# Load function arguments into local variables
local package="$1"
# Check sanity of the input
if [ $# -ne "1" ]
then
echo "Usage: package_remove 'package_name'"
echo "Aborting."
exit 1
fi
if which dnf ; then
if rpm -q --quiet "$package"; then
dnf remove -y "$package"
fi
elif which yum ; then
if rpm -q --quiet "$package"; then
yum remove -y "$package"
fi
elif which apt-get ; then
apt-get remove -y "$package"
else
echo "Failed to detect available packaging system, tried dnf, yum and apt-get!"
echo "Aborting."
exit 1
fi
}
package_remove telnet-server
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
- name: Ensure telnet-server is removed
package:
name="{{item}}"
state=absent
with_items:
- telnet-server
tags:
- package_telnet-server_removed
- high_severity
- disable_strategy
- low_complexity
- low_disruption
- CCE-27165-0
- NIST-800-53-AC-17(8)
- NIST-800-53-CM-7(a)
- DISA-STIG-RHEL-07-021710
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
include remove_telnet-server
class remove_telnet-server {
package { 'telnet-server':
ensure => 'purged',
}
}
Remediation Anaconda snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
package --remove=telnet-server
|
The Network Information Service (NIS), also known as 'Yellow
Pages' (YP), and its successor NIS+ have been made obsolete by
Kerberos, LDAP, and other modern centralized authentication
services. NIS should not be used because it suffers from security
problems inherent in its design, such as inadequate protection of
important authentication information. |
contains 3 rules |
Remove NIS Client
[ref]ruleThe Network Information Service (NIS), formerly known as Yellow Pages,
is a client-server directory service protocol used to distribute system configuration
files. The NIS client (ypbind ) was used to bind a system to an NIS server
and receive the distributed configuration files. Rationale:The NIS service is inherently an insecure system that has been vulnerable
to DOS attacks, buffer overflows and has poor authentication for querying NIS maps.
NIS generally has been replaced by such protocols as Lightweight Directory Access
Protocol (LDAP). It is recommended that the service be removed. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
# Function to remove packages on RHEL, Fedora, Debian, and possibly other systems.
#
# Example Call(s):
#
# package_remove telnet-server
#
function package_remove {
# Load function arguments into local variables
local package="$1"
# Check sanity of the input
if [ $# -ne "1" ]
then
echo "Usage: package_remove 'package_name'"
echo "Aborting."
exit 1
fi
if which dnf ; then
if rpm -q --quiet "$package"; then
dnf remove -y "$package"
fi
elif which yum ; then
if rpm -q --quiet "$package"; then
yum remove -y "$package"
fi
elif which apt-get ; then
apt-get remove -y "$package"
else
echo "Failed to detect available packaging system, tried dnf, yum and apt-get!"
echo "Aborting."
exit 1
fi
}
package_remove ypbind
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
- name: Ensure ypbind is removed
package:
name="{{item}}"
state=absent
with_items:
- ypbind
tags:
- package_ypbind_removed
- unknown_severity
- disable_strategy
- low_complexity
- low_disruption
- CCE-27396-1
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
include remove_ypbind
class remove_ypbind {
package { 'ypbind':
ensure => 'purged',
}
}
Remediation Anaconda snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
package --remove=ypbind
|
Disable ypbind Service
[ref]ruleThe ypbind service, which allows the system to act as a client in
a NIS or NIS+ domain, should be disabled.
The ypbind service can be disabled with the following command:
$ sudo systemctl disable ypbind.service Rationale:Disabling the ypbind service ensures the system is not acting
as a client in a NIS or NIS+ domain. This service should be disabled
unless in use. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
# Function to enable/disable and start/stop services on RHEL and Fedora systems.
#
# Example Call(s):
#
# service_command enable bluetooth
# service_command disable bluetooth.service
#
# Using xinetd:
# service_command disable rsh.socket xinetd=rsh
#
function service_command {
# Load function arguments into local variables
local service_state=$1
local service=$2
local xinetd=$(echo $3 | cut -d'=' -f2)
# Check sanity of the input
if [ $# -lt "2" ]
then
echo "Usage: service_command 'enable/disable' 'service_name.service'"
echo
echo "To enable or disable xinetd services add \'xinetd=service_name\'"
echo "as the last argument"
echo "Aborting."
exit 1
fi
# If systemctl is installed, use systemctl command; otherwise, use the service/chkconfig commands
if [ -f "/usr/bin/systemctl" ] ; then
service_util="/usr/bin/systemctl"
else
service_util="/sbin/service"
chkconfig_util="/sbin/chkconfig"
fi
# If disable is not specified in arg1, set variables to enable services.
# Otherwise, variables are to be set to disable services.
if [ "$service_state" != 'disable' ] ; then
service_state="enable"
service_operation="start"
chkconfig_state="on"
else
service_state="disable"
service_operation="stop"
chkconfig_state="off"
fi
# If chkconfig_util is not empty, use chkconfig/service commands.
if [ "x$chkconfig_util" != x ] ; then
$service_util $service $service_operation
$chkconfig_util --level 0123456 $service $chkconfig_state
else
$service_util $service_operation $service
$service_util $service_state $service
# The service may not be running because it has been started and failed,
# so let's reset the state so OVAL checks pass.
# Service should be 'inactive', not 'failed' after reboot though.
$service_util reset-failed $service
fi
# Test if local variable xinetd is empty using non-bashism.
# If empty, then xinetd is not being used.
if [ "x$xinetd" != x ] ; then
grep -qi disable /etc/xinetd.d/$xinetd && \
if [ "$service_operation" = 'disable' ] ; then
sed -i "s/disable.*/disable = no/gI" /etc/xinetd.d/$xinetd
else
sed -i "s/disable.*/disable = yes/gI" /etc/xinetd.d/$xinetd
fi
fi
}
service_command disable ypbind
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
- name: Disable service ypbind
service:
name="{{item}}"
enabled="no"
state="stopped"
register: service_result
failed_when: "service_result|failed and ('Could not find the requested service' not in service_result.msg)"
with_items:
- ypbind
tags:
- service_ypbind_disabled
- medium_severity
- disable_strategy
- low_complexity
- low_disruption
- CCE-27385-4
- NIST-800-53-AC-17(8)
- NIST-800-53-CM-7
|
Uninstall ypserv Package
[ref]ruleThe ypserv package can be uninstalled with
the following command:
$ sudo yum erase ypserv Rationale:The NIS service provides an unencrypted authentication service which does not
provide for the confidentiality and integrity of user passwords or the remote session.
Removing the ypserv package decreases the risk of the accidental (or intentional)
activation of NIS or NIS+ services. References:
SV-86593r1_rule, 2.2.16, CCI-000381, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), AC-17(8), CM-7(a), SRG-OS-000095-GPOS-00049 Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
# Function to remove packages on RHEL, Fedora, Debian, and possibly other systems.
#
# Example Call(s):
#
# package_remove telnet-server
#
function package_remove {
# Load function arguments into local variables
local package="$1"
# Check sanity of the input
if [ $# -ne "1" ]
then
echo "Usage: package_remove 'package_name'"
echo "Aborting."
exit 1
fi
if which dnf ; then
if rpm -q --quiet "$package"; then
dnf remove -y "$package"
fi
elif which yum ; then
if rpm -q --quiet "$package"; then
yum remove -y "$package"
fi
elif which apt-get ; then
apt-get remove -y "$package"
else
echo "Failed to detect available packaging system, tried dnf, yum and apt-get!"
echo "Aborting."
exit 1
fi
}
package_remove ypserv
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
- name: Ensure ypserv is removed
package:
name="{{item}}"
state=absent
with_items:
- ypserv
tags:
- package_ypserv_removed
- high_severity
- disable_strategy
- low_complexity
- low_disruption
- CCE-27399-5
- NIST-800-53-AC-17(8)
- NIST-800-53-CM-7(a)
- DISA-STIG-RHEL-07-020010
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
include remove_ypserv
class remove_ypserv {
package { 'ypserv':
ensure => 'purged',
}
}
Remediation Anaconda snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
package --remove=ypserv
|
Xinetd
[ref]groupThe xinetd service acts as a dedicated listener for some
network services (mostly, obsolete ones) and can be used to provide access
controls and perform some logging. It has been largely obsoleted by other
features, and it is not installed by default. The older Inetd service
is not even available as part of Red Hat Enterprise Linux 7. |
contains 2 rules |
Disable xinetd Service
[ref]rule
The xinetd service can be disabled with the following command:
$ sudo systemctl disable xinetd.service
Rationale:The xinetd service provides a dedicated listener service for some programs,
which is no longer necessary for commonly-used network services. Disabling
it ensures that these uncommon services are not running, and also prevents
attacks against xinetd itself. References:
2.1.7, 3.4.7, CCI-000305, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), AC-17(8), CM-7 Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
# Function to enable/disable and start/stop services on RHEL and Fedora systems.
#
# Example Call(s):
#
# service_command enable bluetooth
# service_command disable bluetooth.service
#
# Using xinetd:
# service_command disable rsh.socket xinetd=rsh
#
function service_command {
# Load function arguments into local variables
local service_state=$1
local service=$2
local xinetd=$(echo $3 | cut -d'=' -f2)
# Check sanity of the input
if [ $# -lt "2" ]
then
echo "Usage: service_command 'enable/disable' 'service_name.service'"
echo
echo "To enable or disable xinetd services add \'xinetd=service_name\'"
echo "as the last argument"
echo "Aborting."
exit 1
fi
# If systemctl is installed, use systemctl command; otherwise, use the service/chkconfig commands
if [ -f "/usr/bin/systemctl" ] ; then
service_util="/usr/bin/systemctl"
else
service_util="/sbin/service"
chkconfig_util="/sbin/chkconfig"
fi
# If disable is not specified in arg1, set variables to enable services.
# Otherwise, variables are to be set to disable services.
if [ "$service_state" != 'disable' ] ; then
service_state="enable"
service_operation="start"
chkconfig_state="on"
else
service_state="disable"
service_operation="stop"
chkconfig_state="off"
fi
# If chkconfig_util is not empty, use chkconfig/service commands.
if [ "x$chkconfig_util" != x ] ; then
$service_util $service $service_operation
$chkconfig_util --level 0123456 $service $chkconfig_state
else
$service_util $service_operation $service
$service_util $service_state $service
# The service may not be running because it has been started and failed,
# so let's reset the state so OVAL checks pass.
# Service should be 'inactive', not 'failed' after reboot though.
$service_util reset-failed $service
fi
# Test if local variable xinetd is empty using non-bashism.
# If empty, then xinetd is not being used.
if [ "x$xinetd" != x ] ; then
grep -qi disable /etc/xinetd.d/$xinetd && \
if [ "$service_operation" = 'disable' ] ; then
sed -i "s/disable.*/disable = no/gI" /etc/xinetd.d/$xinetd
else
sed -i "s/disable.*/disable = yes/gI" /etc/xinetd.d/$xinetd
fi
fi
}
service_command disable xinetd
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
- name: Disable service xinetd
service:
name="{{item}}"
enabled="no"
state="stopped"
register: service_result
failed_when: "service_result|failed and ('Could not find the requested service' not in service_result.msg)"
with_items:
- xinetd
tags:
- service_xinetd_disabled
- medium_severity
- disable_strategy
- low_complexity
- low_disruption
- CCE-27443-1
- NIST-800-53-AC-17(8)
- NIST-800-53-CM-7
- NIST-800-171-3.4.7
|
Uninstall xinetd Package
[ref]ruleThe xinetd package can be uninstalled with the following command:
$ sudo yum erase xinetd Rationale:Removing the xinetd package decreases the risk of the
xinetd service's accidental (or intentional) activation. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
# Function to remove packages on RHEL, Fedora, Debian, and possibly other systems.
#
# Example Call(s):
#
# package_remove telnet-server
#
function package_remove {
# Load function arguments into local variables
local package="$1"
# Check sanity of the input
if [ $# -ne "1" ]
then
echo "Usage: package_remove 'package_name'"
echo "Aborting."
exit 1
fi
if which dnf ; then
if rpm -q --quiet "$package"; then
dnf remove -y "$package"
fi
elif which yum ; then
if rpm -q --quiet "$package"; then
yum remove -y "$package"
fi
elif which apt-get ; then
apt-get remove -y "$package"
else
echo "Failed to detect available packaging system, tried dnf, yum and apt-get!"
echo "Aborting."
exit 1
fi
}
package_remove xinetd
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
- name: Ensure xinetd is removed
package:
name="{{item}}"
state=absent
with_items:
- xinetd
tags:
- package_xinetd_removed
- unknown_severity
- disable_strategy
- low_complexity
- low_disruption
- CCE-27354-0
- NIST-800-53-AC-17(8)
- NIST-800-53-CM-7
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
include remove_xinetd
class remove_xinetd {
package { 'xinetd':
ensure => 'purged',
}
}
Remediation Anaconda snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
package --remove=xinetd
|
Chat/Messaging Services
[ref]groupThe talk software makes it possible for users to send and receive messages
across systems through a terminal session. |
contains 2 rules |
Uninstall talk Package
[ref]ruleThe talk package contains the client program for the
Internet talk protocol, which allows the user to chat with other users on
different systems. Talk is a communication program which copies lines from one
terminal to the terminal of another user.
The talk package can be removed with the following command:
$ sudo yum erase talk Rationale:The talk software presents a security risk as it uses unencrypted protocols
for communications. Removing the talk package decreases the
risk of the accidental (or intentional) activation of talk client program. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
# Function to remove packages on RHEL, Fedora, Debian, and possibly other systems.
#
# Example Call(s):
#
# package_remove telnet-server
#
function package_remove {
# Load function arguments into local variables
local package="$1"
# Check sanity of the input
if [ $# -ne "1" ]
then
echo "Usage: package_remove 'package_name'"
echo "Aborting."
exit 1
fi
if which dnf ; then
if rpm -q --quiet "$package"; then
dnf remove -y "$package"
fi
elif which yum ; then
if rpm -q --quiet "$package"; then
yum remove -y "$package"
fi
elif which apt-get ; then
apt-get remove -y "$package"
else
echo "Failed to detect available packaging system, tried dnf, yum and apt-get!"
echo "Aborting."
exit 1
fi
}
package_remove talk
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
- name: Ensure talk is removed
package:
name="{{item}}"
state=absent
with_items:
- talk
tags:
- package_talk_removed
- unknown_severity
- disable_strategy
- low_complexity
- low_disruption
- CCE-27432-4
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
include remove_talk
class remove_talk {
package { 'talk':
ensure => 'purged',
}
}
Remediation Anaconda snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
package --remove=talk
|
Uninstall talk-server Package
[ref]rule
The talk-server package can be removed with the following command:
$ sudo yum erase talk-server
Rationale:The talk software presents a security risk as it uses unencrypted protocols
for communications. Removing the talk-server package decreases the
risk of the accidental (or intentional) activation of talk services. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
# Function to remove packages on RHEL, Fedora, Debian, and possibly other systems.
#
# Example Call(s):
#
# package_remove telnet-server
#
function package_remove {
# Load function arguments into local variables
local package="$1"
# Check sanity of the input
if [ $# -ne "1" ]
then
echo "Usage: package_remove 'package_name'"
echo "Aborting."
exit 1
fi
if which dnf ; then
if rpm -q --quiet "$package"; then
dnf remove -y "$package"
fi
elif which yum ; then
if rpm -q --quiet "$package"; then
yum remove -y "$package"
fi
elif which apt-get ; then
apt-get remove -y "$package"
else
echo "Failed to detect available packaging system, tried dnf, yum and apt-get!"
echo "Aborting."
exit 1
fi
}
package_remove talk-server
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
- name: Ensure talk-server is removed
package:
name="{{item}}"
state=absent
with_items:
- talk-server
tags:
- package_talk-server_removed
- medium_severity
- disable_strategy
- low_complexity
- low_disruption
- CCE-27210-4
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
include remove_talk-server
class remove_talk-server {
package { 'talk-server':
ensure => 'purged',
}
}
Remediation Anaconda snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
package --remove=talk-server
|
Cron and At Daemons
[ref]groupThe cron and at services are used to allow commands to
be executed at a later time. The cron service is required by almost
all systems to perform necessary maintenance tasks, while at may or
may not be required on a given system. Both daemons should be
configured defensively. |
contains 3 rules |
Restrict at and cron to Authorized Users if Necessary
[ref]groupThe /etc/cron.allow and /etc/at.allow files contain lists of
users who are allowed to use cron and at to delay execution of
processes. If these files exist and if the corresponding files
/etc/cron.deny and /etc/at.deny do not exist, then only users
listed in the relevant allow files can run the crontab and at commands
to submit jobs to be run at scheduled intervals. On many systems, only the
system administrator needs the ability to schedule jobs. Note that even if a
given user is not listed in cron.allow , cron jobs can still be run as
that user. The cron.allow file controls only administrative access
to the crontab command for scheduling and modifying cron jobs.
To restrict at and cron to only authorized users:
- Remove the
cron.deny file:$ sudo rm /etc/cron.deny - Edit
/etc/cron.allow , adding one line for each user allowed to use
the crontab command to create cron jobs. - Remove the
at.deny file:$ sudo rm /etc/at.deny - Edit
/etc/at.allow , adding one line for each user allowed to use
the at command to create at jobs.
|
contains 2 rules |
Verify Group Who Owns /etc/cron.allow file
[ref]ruleIf /etc/cron.allow exists, it must be group-owned by root .
To properly set the group owner of /etc/cron.allow , run the command:
$ sudo chgrp root /etc/cron.allow Rationale:If the owner of the cron.allow file is not set to root, the possibility exists for an
unauthorized user to view or edit sensitive information. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | configure |
---|
chgrp root /etc/cron.allow
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | configure |
---|
- name: Find /etc/cron.allow file(s)
find:
paths: "{{ '/etc/cron.allow' | dirname }}"
patterns: "{{ '/etc/cron.allow' | basename }}"
register: files_found
tags:
- file_groupowner_cron_allow
- medium_severity
- configure_strategy
- low_complexity
- low_disruption
- CCE-80379-1
- NIST-800-53-AC-6
- DISA-STIG-RHEL-07-021120
- name: Set group ownership to root
file:
path: "{{ item.path }}"
group: root
with_items:
- "{{ files_found.files }}"
tags:
- file_groupowner_cron_allow
- medium_severity
- configure_strategy
- low_complexity
- low_disruption
- CCE-80379-1
- NIST-800-53-AC-6
- DISA-STIG-RHEL-07-021120
|
Verify User Who Owns /etc/cron.allow file
[ref]ruleIf /etc/cron.allow exists, it must be owned by root .
To properly set the owner of /etc/cron.allow , run the command:
$ sudo chown root /etc/cron.allow Rationale:If the owner of the cron.allow file is not set to root, the possibility exists for an
unauthorized user to view or edit sensitive information. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | configure |
---|
chown root /etc/cron.allow
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | configure |
---|
- name: Find /etc/cron.allow file(s)
find:
paths: "{{ '/etc/cron.allow' | dirname }}"
patterns: "{{ '/etc/cron.allow' | basename }}"
register: files_found
tags:
- file_owner_cron_allow
- medium_severity
- configure_strategy
- low_complexity
- low_disruption
- CCE-80378-3
- NIST-800-53-AC-6
- DISA-STIG-RHEL-07-021110
- name: Set user ownership to root
file:
path: "{{ item.path }}"
owner: root
with_items:
- "{{ files_found.files }}"
tags:
- file_owner_cron_allow
- medium_severity
- configure_strategy
- low_complexity
- low_disruption
- CCE-80378-3
- NIST-800-53-AC-6
- DISA-STIG-RHEL-07-021110
|
Enable cron Service
[ref]ruleThe crond service is used to execute commands at
preconfigured times. It is required by almost all systems to perform necessary
maintenance tasks, such as notifying root of system activity.
The crond service can be enabled with the following command:
$ sudo systemctl enable crond.service Rationale:Due to its usage for maintenance and security-supporting tasks,
enabling the cron daemon is essential. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
# Function to enable/disable and start/stop services on RHEL and Fedora systems.
#
# Example Call(s):
#
# service_command enable bluetooth
# service_command disable bluetooth.service
#
# Using xinetd:
# service_command disable rsh.socket xinetd=rsh
#
function service_command {
# Load function arguments into local variables
local service_state=$1
local service=$2
local xinetd=$(echo $3 | cut -d'=' -f2)
# Check sanity of the input
if [ $# -lt "2" ]
then
echo "Usage: service_command 'enable/disable' 'service_name.service'"
echo
echo "To enable or disable xinetd services add \'xinetd=service_name\'"
echo "as the last argument"
echo "Aborting."
exit 1
fi
# If systemctl is installed, use systemctl command; otherwise, use the service/chkconfig commands
if [ -f "/usr/bin/systemctl" ] ; then
service_util="/usr/bin/systemctl"
else
service_util="/sbin/service"
chkconfig_util="/sbin/chkconfig"
fi
# If disable is not specified in arg1, set variables to enable services.
# Otherwise, variables are to be set to disable services.
if [ "$service_state" != 'disable' ] ; then
service_state="enable"
service_operation="start"
chkconfig_state="on"
else
service_state="disable"
service_operation="stop"
chkconfig_state="off"
fi
# If chkconfig_util is not empty, use chkconfig/service commands.
if [ "x$chkconfig_util" != x ] ; then
$service_util $service $service_operation
$chkconfig_util --level 0123456 $service $chkconfig_state
else
$service_util $service_operation $service
$service_util $service_state $service
# The service may not be running because it has been started and failed,
# so let's reset the state so OVAL checks pass.
# Service should be 'inactive', not 'failed' after reboot though.
$service_util reset-failed $service
fi
# Test if local variable xinetd is empty using non-bashism.
# If empty, then xinetd is not being used.
if [ "x$xinetd" != x ] ; then
grep -qi disable /etc/xinetd.d/$xinetd && \
if [ "$service_operation" = 'disable' ] ; then
sed -i "s/disable.*/disable = no/gI" /etc/xinetd.d/$xinetd
else
sed -i "s/disable.*/disable = yes/gI" /etc/xinetd.d/$xinetd
fi
fi
}
service_command enable crond
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: Enable service crond
service:
name="{{item}}"
enabled="yes"
state="started"
with_items:
- crond
tags:
- service_crond_enabled
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-27323-5
- NIST-800-53-CM-7
|
Network Routing
[ref]groupA router is a very desirable target for a
potential adversary because they fulfill a variety of
infrastructure networking roles such as access to network segments,
gateways to other networks, filtering, etc. Therefore, if one is
required, the system acting as a router should be dedicated
to that purpose alone and be stored in a physically secure
location. The system's default routing software is Quagga, and
provided in an RPM package of the same name. |
contains 1 rule |
Disable Quagga if Possible
[ref]groupIf Quagga was installed and activated, but the system
does not need to act as a router, then it should be disabled
and removed. |
contains 1 rule |
Disable Quagga Service
[ref]rule
The zebra service can be disabled with the following command:
$ sudo systemctl disable zebra.service
Rationale:Routing protocol daemons are typically used on routers to exchange network
topology information with other routers. If routing daemons are used when not
required, system network information may be unnecessarily transmitted across
the network. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
# Function to enable/disable and start/stop services on RHEL and Fedora systems.
#
# Example Call(s):
#
# service_command enable bluetooth
# service_command disable bluetooth.service
#
# Using xinetd:
# service_command disable rsh.socket xinetd=rsh
#
function service_command {
# Load function arguments into local variables
local service_state=$1
local service=$2
local xinetd=$(echo $3 | cut -d'=' -f2)
# Check sanity of the input
if [ $# -lt "2" ]
then
echo "Usage: service_command 'enable/disable' 'service_name.service'"
echo
echo "To enable or disable xinetd services add \'xinetd=service_name\'"
echo "as the last argument"
echo "Aborting."
exit 1
fi
# If systemctl is installed, use systemctl command; otherwise, use the service/chkconfig commands
if [ -f "/usr/bin/systemctl" ] ; then
service_util="/usr/bin/systemctl"
else
service_util="/sbin/service"
chkconfig_util="/sbin/chkconfig"
fi
# If disable is not specified in arg1, set variables to enable services.
# Otherwise, variables are to be set to disable services.
if [ "$service_state" != 'disable' ] ; then
service_state="enable"
service_operation="start"
chkconfig_state="on"
else
service_state="disable"
service_operation="stop"
chkconfig_state="off"
fi
# If chkconfig_util is not empty, use chkconfig/service commands.
if [ "x$chkconfig_util" != x ] ; then
$service_util $service $service_operation
$chkconfig_util --level 0123456 $service $chkconfig_state
else
$service_util $service_operation $service
$service_util $service_state $service
# The service may not be running because it has been started and failed,
# so let's reset the state so OVAL checks pass.
# Service should be 'inactive', not 'failed' after reboot though.
$service_util reset-failed $service
fi
# Test if local variable xinetd is empty using non-bashism.
# If empty, then xinetd is not being used.
if [ "x$xinetd" != x ] ; then
grep -qi disable /etc/xinetd.d/$xinetd && \
if [ "$service_operation" = 'disable' ] ; then
sed -i "s/disable.*/disable = no/gI" /etc/xinetd.d/$xinetd
else
sed -i "s/disable.*/disable = yes/gI" /etc/xinetd.d/$xinetd
fi
fi
}
service_command disable zebra
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
- name: Disable service zebra
service:
name="{{item}}"
enabled="no"
state="stopped"
register: service_result
failed_when: "service_result|failed and ('Could not find the requested service' not in service_result.msg)"
with_items:
- zebra
tags:
- service_zebra_disabled
- medium_severity
- disable_strategy
- low_complexity
- low_disruption
- CCE-27191-6
- NIST-800-53-SC-32
|
LDAP is a popular directory service, that is, a
standardized way of looking up information from a central database.
Red Hat Enterprise Linux 7 includes software that enables a system to act as both
an LDAP client and server. |
contains 1 rule |
Configure OpenLDAP Clients
[ref]groupThis section provides information on which security settings are
important to configure in OpenLDAP clients by manually editing the appropriate
configuration files. Red Hat Enterprise Linux 7 provides an automated configuration tool called
authconfig and a graphical wrapper for authconfig called
system-config-authentication . However, these tools do not provide as
much control over configuration as manual editing of configuration files. The
authconfig tools do not allow you to specify locations of SSL certificate
files, which is useful when trying to use SSL cleanly across several protocols.
Installation and configuration of OpenLDAP on Red Hat Enterprise Linux 7 is available at
https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/System-Level_Authentication_Guide/openldap.html. |
contains 1 rule |
Configure LDAP Client to Use TLS For All Transactions
[ref]ruleThis check verifies that RHEL7 implements cryptography
to protect the integrity of remote LDAP authentication sessions.
To determine if LDAP is being used for authentication, use the following
command:
$ sudo grep -i useldapauth /etc/sysconfig/authconfig
If USELDAPAUTH=yes , then LDAP is being used. To check if LDAP is
configured to use TLS, use the following command:
$ sudo grep -i ssl /etc/pam_ldap.conf Rationale:Without cryptographic integrity protections, information can be
altered by unauthorized users without detection. The ssl directive specifies
whether to use TLS or not. If not specified it will default to no.
It should be set to start_tls rather than doing LDAP over SSL. Remediation Shell script: (show)
# Use LDAP for authentication
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysconfig/authconfig' 'USELDAPAUTH' 'yes' 'CCE-80291-8' '%s=%s'
# Configure client to use TLS for all authentications
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/nslcd.conf' 'ssl' 'start_tls' 'CCE-80291-8' '%s %s'
|
System Security Services Daemon
[ref]groupThe System Security Services Daemon (SSSD) is a system daemon that provides access
to different identity and authentication providers such as Red Hat's IdM, Microsoft's AD,
openLDAP, MIT Kerberos, etc. It uses a common framework that can provide caching and offline
support to systems utilizing SSSD. SSSD using caching to reduce load on authentication
servers permit offline authentication as well as store extended user data.
For more information, see
https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/System-Level_Authentication_Guide/SSSD.html |
contains 3 rules |
Configure SSSD's Memory Cache to Expire
[ref]ruleSSSD's memory cache should be configured to set to expire records after 1 day.
To configure SSSD to expire memory cache, set memcache_timeout to
86400 under the [nss] section in /etc/sssd/sssd.conf .
For example:
[nss]
memcache_timeout = 86400
Rationale:If cached authentication information is out-of-date, the validity of the
authentication information may be questionable. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
- name: "Configure SSSD's Memory Cache to Expire"
ini_file:
dest: /etc/sssd/sssd.conf
section: nss
option: memcache_timeout
value: 86400
create: yes
tags:
- sssd_memcache_timeout
- medium_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80364-3
- NIST-800-53-IA-5(10)
- NIST-800-53-IA-5(13)
|
Configure SSSD to Expire Offline Credentials
[ref]ruleSSSD should be configured to expire offline credentials after 1 day.
To configure SSSD to expire offline credentials, set
offline_credentials_expiration to 1 under the [pam]
section in /etc/sssd/sssd.conf . For example:
[pam]
offline_credentials_expiration = 1
Rationale:If cached authentication information is out-of-date, the validity of the
authentication information may be questionable. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
- name: "Configure SSD to Expire Offline Credentials"
ini_file:
dest: /etc/sssd/sssd.conf
section: pam
option: offline_credentials_expiration
value: 1
create: yes
tags:
- sssd_offline_cred_expiration
- medium_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80365-0
- NIST-800-53-IA-5(13)
|
Configure SSSD to Expire SSH Known Hosts
[ref]ruleSSSD should be configured to expire keys from known SSH hosts after 1 day.
To configure SSSD to known SSH hosts, set ssh_known_hosts_timeout
to 86400 under the [ssh] section in
/etc/sssd/sssd.conf . For example:
[ssh]
ssh_known_hosts_timeout = 86400
Rationale:If cached authentication information is out-of-date, the validity of the
authentication information may be questionable. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
- name: "Configure SSSD to Expire SSH Known Hosts"
ini_file:
dest: /etc/sssd/sssd.conf
section: ssh
option: ssh_known_hosts_timeout
value: 86400
create: yes
tags:
- sssd_ssh_known_hosts_timeout
- medium_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80366-8
- NIST-800-53-IA-5(13)
|
Network Time Protocol
[ref]groupThe Network Time Protocol is used to manage the system
clock over a network. Computer clocks are not very accurate, so
time will drift unpredictably on unmanaged systems. Central time
protocols can be used both to ensure that time is consistent among
a network of systems, and that their time is consistent with the
outside world.
If every system on a network reliably reports the same time, then it is much
easier to correlate log messages in case of an attack. In addition, a number of
cryptographic protocols (such as Kerberos) use timestamps to prevent certain
types of attacks. If your network does not have synchronized time, these
protocols may be unreliable or even unusable.
Depending on the specifics of the network, global time accuracy may be just as
important as local synchronization, or not very important at all. If your
network is connected to the Internet, using a public timeserver (or one
provided by your enterprise) provides globally accurate timestamps which may be
essential in investigating or responding to an attack which originated outside
of your network.
A typical network setup involves a small number of internal systems operating
as NTP servers, and the remainder obtaining time information from those
internal servers.
There is a choice between the daemons ntpd and chronyd , which
are available from the repositories in the ntp and chrony
packages respectively.
The default chronyd daemon can work well when external time references
are only intermittently accesible, can perform well even when the network is
congested for longer periods of time, can usually synchronize the clock faster
and with better time accuracy, and quickly adapts to sudden changes in the rate
of the clock, for example, due to changes in the temperature of the crystal
oscillator. Chronyd should be considered for all systems which are
frequently suspended or otherwise intermittently disconnected and reconnected
to a network. Mobile and virtual systems for example.
The ntpd NTP daemon fully supports NTP protocol version 4 (RFC 5905),
including broadcast, multicast, manycast clients and servers, and the orphan
mode. It also supports extra authentication schemes based on public-key
cryptography (RFC 5906). The NTP daemon (ntpd ) should be considered
for systems which are normally kept permanently on. Systems which are required
to use broadcast or multicast IP, or to perform authentication of packets with
the Autokey protocol, should consider using ntpd .
Refer to https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/System_Administrators_Guide/ch-Configuring_NTP_Using_the_chrony_Suite.html for more detailed comparison of features of chronyd
and ntpd daemon features respectively, and for further guidance how to
choose between the two NTP daemons.
The upstream manual pages at http://chrony.tuxfamily.org/manual.html for
chronyd and http://www.ntp.org for ntpd provide additional
information on the capabilities and configuration of each of the NTP daemons. |
contains 3 rules |
Specify Additional Remote NTP Servers
[ref]ruleDepending on specific functional requirements of a concrete
production environment, the Red Hat Enterprise Linux 7 Server system can be
configured to utilize the services of the chronyd NTP daemon (the
default), or services of the ntpd NTP daemon. Refer to
https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/System_Administrators_Guide/ch-Configuring_NTP_Using_the_chrony_Suite.html
for more detailed comparison of the features of both of the choices, and for
further guidance how to choose between the two NTP daemons.
Additional NTP servers can be specified for time synchronization. To do so,
perform the following:
- if the system is configured to use the
chronyd as the NTP daemon
(the default), edit the file /etc/chrony.conf as follows, - if the system is configured to use the
ntpd as the NTP daemon,
edit the file /etc/ntp.conf as documented below.
Add additional lines of the following form, substituting the IP address or
hostname of a remote NTP server for ntpserver:
server ntpserver Rationale:Specifying additional NTP servers increases the availability of
accurate time data, in the event that one of the specified servers becomes
unavailable. This is typical for a system acting as an NTP server for
other systems. Remediation Shell script: (show)
var_multiple_time_servers="0.rhel.pool.ntp.org,1.rhel.pool.ntp.org,2.rhel.pool.ntp.org,3.rhel.pool.ntp.org"
# Invoke the function without args, so its body is substituded right here.
# Function ensures that the ntp/chrony config file contains valid server entries
# $1: Path to the config file
# $2: Comma-separated list of servers
function rhel7_ensure_there_are_servers_in_ntp_compatible_config_file {
# If invoked with no arguments, exit. This is an intentional behavior.
[ $# -gt 1 ] || return 0
[ $# = 2 ] || die "$0 requires zero or exactly two arguments"
local _config_file="$1" _servers_list="$2"
if ! grep -q '#[[:space:]]*server' "$_config_file"; then
for server in $(echo "$_servers_list" | tr ',' '\n') ; do
printf '\nserver %s iburst' "$server" >> "$_config_file"
done
else
sed -i 's/#[ \t]*server/server/g' "$_config_file"
fi
}
rhel7_ensure_there_are_servers_in_ntp_compatible_config_file
config_file="/etc/ntp.conf"
/usr/sbin/pidof ntpd || config_file="/etc/chrony.conf"
[ "$(grep -c '^server' "$config_file")" -gt 1 ] || rhel7_ensure_there_are_servers_in_ntp_compatible_config_file "$config_file" "$var_multiple_time_servers"
|
Specify a Remote NTP Server
[ref]ruleDepending on specific functional requirements of a concrete
production environment, the Red Hat Enterprise Linux 7 Server system can be
configured to utilize the services of the chronyd NTP daemon (the
default), or services of the ntpd NTP daemon. Refer to
https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/System_Administrators_Guide/ch-Configuring_NTP_Using_the_chrony_Suite.html
for more detailed comparison of the features of both of the choices, and for
further guidance how to choose between the two NTP daemons.
To specify a remote NTP server for time synchronization, perform the following:
- if the system is configured to use the
chronyd as the NTP daemon (the
default), edit the file /etc/chrony.conf as follows, - if the system is configured to use the
ntpd as the NTP daemon,
edit the file /etc/ntp.conf as documented below.
Add or correct the following lines, substituting the IP or hostname of a remote
NTP server for ntpserver:
server ntpserver
This instructs the NTP software to contact that remote server to obtain time
data.Rationale:Synchronizing with an NTP server makes it possible to collate system
logs from multiple sources or correlate computer events with real time events. Remediation Shell script: (show)
var_multiple_time_servers="0.rhel.pool.ntp.org,1.rhel.pool.ntp.org,2.rhel.pool.ntp.org,3.rhel.pool.ntp.org"
# Invoke the function without args, so its body is substituded right here.
# Function ensures that the ntp/chrony config file contains valid server entries
# $1: Path to the config file
# $2: Comma-separated list of servers
function rhel7_ensure_there_are_servers_in_ntp_compatible_config_file {
# If invoked with no arguments, exit. This is an intentional behavior.
[ $# -gt 1 ] || return 0
[ $# = 2 ] || die "$0 requires zero or exactly two arguments"
local _config_file="$1" _servers_list="$2"
if ! grep -q '#[[:space:]]*server' "$_config_file"; then
for server in $(echo "$_servers_list" | tr ',' '\n') ; do
printf '\nserver %s iburst' "$server" >> "$_config_file"
done
else
sed -i 's/#[ \t]*server/server/g' "$_config_file"
fi
}
rhel7_ensure_there_are_servers_in_ntp_compatible_config_file
config_file="/etc/ntp.conf"
/usr/sbin/pidof ntpd || config_file="/etc/chrony.conf"
grep -q ^server "$config_file" || rhel7_ensure_there_are_servers_in_ntp_compatible_config_file "$config_file" "$var_multiple_time_servers"
|
Enable the NTP Daemon
[ref]rule
The chronyd service can be enabled with the following command:
$ sudo systemctl enable chronyd.service
Note: The chronyd daemon is enabled by default.
The ntpd service can be enabled with the following command:
$ sudo systemctl enable ntpd.service
Note: The ntpd daemon is not enabled by default. Though as mentioned
in the previous sections in certain environments the ntpd daemon might
be preferred to be used rather than the chronyd one. Refer to:
https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/System_Administrators_Guide/ch-Configuring_NTP_Using_the_chrony_Suite.html
for guidance which NTP daemon to choose depending on the environment used.Rationale:Enabling some of chronyd or ntpd services ensures
that the NTP daemon will be running and that the system will synchronize its
time to any servers specified. This is important whether the system is
configured to be a client (and synchronize only its own clock) or it is also
acting as an NTP server to other systems. Synchronizing time is essential for
authentication services such as Kerberos, but it is also important for
maintaining accurate logs and auditing possible security breaches.
The chronyd and ntpd NTP daemons offer all of the
functionality of ntpdate , which is now deprecated. Additional
information on this is available at
http://support.ntp.org/bin/view/Dev/DeprecatingNtpdate Remediation Shell script: (show)
if ! `rpm -q --quiet chrony` && ! `rpm -q --quiet ntp-`; then
# Function to install packages on RHEL, Fedora, Debian, and possibly other systems.
#
# Example Call(s):
#
# package_install aide
#
function package_install {
# Load function arguments into local variables
local package="$1"
# Check sanity of the input
if [ $# -ne "1" ]
then
echo "Usage: package_install 'package_name'"
echo "Aborting."
exit 1
fi
if which dnf ; then
if ! rpm -q --quiet "$package"; then
dnf install -y "$package"
fi
elif which yum ; then
if ! rpm -q --quiet "$package"; then
yum install -y "$package"
fi
elif which apt-get ; then
apt-get install -y "$package"
else
echo "Failed to detect available packaging system, tried dnf, yum and apt-get!"
echo "Aborting."
exit 1
fi
}
package_install chrony
service_command enable chronyd
elif `rpm -q --quiet chrony`; then
if ! [ `/usr/sbin/pidof ntpd` ] ; then
# Function to enable/disable and start/stop services on RHEL and Fedora systems.
#
# Example Call(s):
#
# service_command enable bluetooth
# service_command disable bluetooth.service
#
# Using xinetd:
# service_command disable rsh.socket xinetd=rsh
#
function service_command {
# Load function arguments into local variables
local service_state=$1
local service=$2
local xinetd=$(echo $3 | cut -d'=' -f2)
# Check sanity of the input
if [ $# -lt "2" ]
then
echo "Usage: service_command 'enable/disable' 'service_name.service'"
echo
echo "To enable or disable xinetd services add \'xinetd=service_name\'"
echo "as the last argument"
echo "Aborting."
exit 1
fi
# If systemctl is installed, use systemctl command; otherwise, use the service/chkconfig commands
if [ -f "/usr/bin/systemctl" ] ; then
service_util="/usr/bin/systemctl"
else
service_util="/sbin/service"
chkconfig_util="/sbin/chkconfig"
fi
# If disable is not specified in arg1, set variables to enable services.
# Otherwise, variables are to be set to disable services.
if [ "$service_state" != 'disable' ] ; then
service_state="enable"
service_operation="start"
chkconfig_state="on"
else
service_state="disable"
service_operation="stop"
chkconfig_state="off"
fi
# If chkconfig_util is not empty, use chkconfig/service commands.
if [ "x$chkconfig_util" != x ] ; then
$service_util $service $service_operation
$chkconfig_util --level 0123456 $service $chkconfig_state
else
$service_util $service_operation $service
$service_util $service_state $service
# The service may not be running because it has been started and failed,
# so let's reset the state so OVAL checks pass.
# Service should be 'inactive', not 'failed' after reboot though.
$service_util reset-failed $service
fi
# Test if local variable xinetd is empty using non-bashism.
# If empty, then xinetd is not being used.
if [ "x$xinetd" != x ] ; then
grep -qi disable /etc/xinetd.d/$xinetd && \
if [ "$service_operation" = 'disable' ] ; then
sed -i "s/disable.*/disable = no/gI" /etc/xinetd.d/$xinetd
else
sed -i "s/disable.*/disable = yes/gI" /etc/xinetd.d/$xinetd
fi
fi
}
service_command enable chronyd
fi
else
# Function to enable/disable and start/stop services on RHEL and Fedora systems.
#
# Example Call(s):
#
# service_command enable bluetooth
# service_command disable bluetooth.service
#
# Using xinetd:
# service_command disable rsh.socket xinetd=rsh
#
function service_command {
# Load function arguments into local variables
local service_state=$1
local service=$2
local xinetd=$(echo $3 | cut -d'=' -f2)
# Check sanity of the input
if [ $# -lt "2" ]
then
echo "Usage: service_command 'enable/disable' 'service_name.service'"
echo
echo "To enable or disable xinetd services add \'xinetd=service_name\'"
echo "as the last argument"
echo "Aborting."
exit 1
fi
# If systemctl is installed, use systemctl command; otherwise, use the service/chkconfig commands
if [ -f "/usr/bin/systemctl" ] ; then
service_util="/usr/bin/systemctl"
else
service_util="/sbin/service"
chkconfig_util="/sbin/chkconfig"
fi
# If disable is not specified in arg1, set variables to enable services.
# Otherwise, variables are to be set to disable services.
if [ "$service_state" != 'disable' ] ; then
service_state="enable"
service_operation="start"
chkconfig_state="on"
else
service_state="disable"
service_operation="stop"
chkconfig_state="off"
fi
# If chkconfig_util is not empty, use chkconfig/service commands.
if [ "x$chkconfig_util" != x ] ; then
$service_util $service $service_operation
$chkconfig_util --level 0123456 $service $chkconfig_state
else
$service_util $service_operation $service
$service_util $service_state $service
# The service may not be running because it has been started and failed,
# so let's reset the state so OVAL checks pass.
# Service should be 'inactive', not 'failed' after reboot though.
$service_util reset-failed $service
fi
# Test if local variable xinetd is empty using non-bashism.
# If empty, then xinetd is not being used.
if [ "x$xinetd" != x ] ; then
grep -qi disable /etc/xinetd.d/$xinetd && \
if [ "$service_operation" = 'disable' ] ; then
sed -i "s/disable.*/disable = no/gI" /etc/xinetd.d/$xinetd
else
sed -i "s/disable.*/disable = yes/gI" /etc/xinetd.d/$xinetd
fi
fi
}
service_command enable ntpd
fi
|
Base Services
[ref]groupThis section addresses the base services that are installed on a
Red Hat Enterprise Linux 7 default installation which are not covered in other
sections. Some of these services listen on the network and
should be treated with particular discretion. Other services are local
system utilities that may or may not be extraneous. In general, system services
should be disabled if not required. |
contains 1 rule |
Disable KDump Kernel Crash Analyzer (kdump)
[ref]ruleThe kdump service provides a kernel crash dump analyzer. It uses the kexec
system call to boot a secondary kernel ("capture" kernel) following a system
crash, which can load information from the crashed kernel for analysis.
The kdump service can be disabled with the following command:
$ sudo systemctl disable kdump.service Rationale:Kernel core dumps may contain the full contents of system memory at the time of the crash.
Kernel core dumps consume a considerable amount of disk space and may result in denial of
service by exhausting the available space on the target file system partition.
Unless the system is used for kernel development or testing, there
is little need to run the kdump service. References:
SV-86681r1_rule, CCI-000366, 164.308(a)(1)(ii)(D), 164.308(a)(3), 164.308(a)(4), 164.310(b), 164.310(c), 164.312(a), 164.312(e), AC-17(8), CM-7, CM-6(b), SRG-OS-000480-GPOS-00227 Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
# Function to enable/disable and start/stop services on RHEL and Fedora systems.
#
# Example Call(s):
#
# service_command enable bluetooth
# service_command disable bluetooth.service
#
# Using xinetd:
# service_command disable rsh.socket xinetd=rsh
#
function service_command {
# Load function arguments into local variables
local service_state=$1
local service=$2
local xinetd=$(echo $3 | cut -d'=' -f2)
# Check sanity of the input
if [ $# -lt "2" ]
then
echo "Usage: service_command 'enable/disable' 'service_name.service'"
echo
echo "To enable or disable xinetd services add \'xinetd=service_name\'"
echo "as the last argument"
echo "Aborting."
exit 1
fi
# If systemctl is installed, use systemctl command; otherwise, use the service/chkconfig commands
if [ -f "/usr/bin/systemctl" ] ; then
service_util="/usr/bin/systemctl"
else
service_util="/sbin/service"
chkconfig_util="/sbin/chkconfig"
fi
# If disable is not specified in arg1, set variables to enable services.
# Otherwise, variables are to be set to disable services.
if [ "$service_state" != 'disable' ] ; then
service_state="enable"
service_operation="start"
chkconfig_state="on"
else
service_state="disable"
service_operation="stop"
chkconfig_state="off"
fi
# If chkconfig_util is not empty, use chkconfig/service commands.
if [ "x$chkconfig_util" != x ] ; then
$service_util $service $service_operation
$chkconfig_util --level 0123456 $service $chkconfig_state
else
$service_util $service_operation $service
$service_util $service_state $service
# The service may not be running because it has been started and failed,
# so let's reset the state so OVAL checks pass.
# Service should be 'inactive', not 'failed' after reboot though.
$service_util reset-failed $service
fi
# Test if local variable xinetd is empty using non-bashism.
# If empty, then xinetd is not being used.
if [ "x$xinetd" != x ] ; then
grep -qi disable /etc/xinetd.d/$xinetd && \
if [ "$service_operation" = 'disable' ] ; then
sed -i "s/disable.*/disable = no/gI" /etc/xinetd.d/$xinetd
else
sed -i "s/disable.*/disable = yes/gI" /etc/xinetd.d/$xinetd
fi
fi
}
service_command disable kdump
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
- name: Disable service kdump
service:
name="{{item}}"
enabled="no"
state="stopped"
register: service_result
failed_when: "service_result|failed and ('Could not find the requested service' not in service_result.msg)"
with_items:
- kdump
tags:
- service_kdump_disabled
- medium_severity
- disable_strategy
- low_complexity
- low_disruption
- CCE-80258-7
- NIST-800-53-AC-17(8)
- NIST-800-53-CM-7
- NIST-800-53-CM-6(b)
- DISA-STIG-RHEL-07-021300
|
NFS and RPC
[ref]groupThe Network File System is a popular distributed filesystem for
the Unix environment, and is very widely deployed. This section discusses the
circumstances under which it is possible to disable NFS and its dependencies,
and then details steps which should be taken to secure
NFS's configuration. This section is relevant to systems operating as NFS
clients, as well as to those operating as NFS servers. |
contains 4 rules |
Configure NFS Servers
[ref]groupThe steps in this section are appropriate for systems which operate as NFS servers. |
contains 1 rule |
Use Kerberos Security on All Exports
[ref]ruleUsing Kerberos on all exported mounts prevents a malicious client or user from
impersonating a system user. To cryptography authenticate users to the NFS server,
add sec=krb5:krb5i:krb5p to each export in /etc/exports . Rationale:When an NFS server is configured to use AUTH_SYS a selected userid and groupid are used to handle
requests from the remote user. The userid and groupid could mistakenly or maliciously be set
incorrectly. The AUTH_GSS method of authentication uses certificates on the server and client
systems to more securely authenticate the remote mount request. |
Configure NFS Clients
[ref]groupThe steps in this section are appropriate for systems which operate as NFS clients. |
contains 3 rules |
Mount Remote Filesystems with Restrictive Options
[ref]groupEdit the file /etc/fstab . For each filesystem whose type
(column 3) is nfs or nfs4 , add the text
,nodev,nosuid to the list of mount options in column 4. If
appropriate, also add ,noexec .
See the section titled "Restrict Partition Mount Options" for a description of
the effects of these options. In general, execution of files mounted via NFS
should be considered risky because of the possibility that an adversary could
intercept the request and substitute a malicious file. Allowing setuid files to
be executed from remote servers is particularly risky, both for this reason and
because it requires the clients to extend root-level trust to the NFS
server. |
contains 3 rules |
Mount Remote Filesystems with Kerberos Security
[ref]rule
Add the sec=krb5:krb5i:krb5p option to the fourth column of
/etc/fstab for the line which controls mounting of
any NFS mounts.
Rationale:When an NFS server is configured to use AUTH_SYS a selected userid and groupid are used to handle
requests from the remote user. The userid and groupid could mistakenly or maliciously be set
incorrectly. The AUTH_GSS method of authentication uses certificates on the server and client
systems to more securely authenticate the remote mount request. |
Mount Remote Filesystems with nosuid
[ref]rule
Add the nosuid option to the fourth column of
/etc/fstab for the line which controls mounting of
any NFS mounts.
Rationale:NFS mounts should not present suid binaries to users. Only vendor-supplied suid executables
should be installed to their default location on the local filesystem. |
Mount Remote Filesystems with nodev
[ref]rule
Add the nodev option to the fourth column of
/etc/fstab for the line which controls mounting of
any NFS mounts.
Rationale:Legitimate device files should only exist in the /dev directory. NFS mounts
should not present device files to users. |
SSH Server
[ref]groupThe SSH protocol is recommended for remote login and
remote file transfer. SSH provides confidentiality and integrity
for data exchanged between two systems, as well as server
authentication, through the use of public key cryptography. The
implementation included with the system is called OpenSSH, and more
detailed documentation is available from its website,
http://www.openssh.org. Its server program
is called sshd and provided by the RPM package
openssh-server . |
contains 23 rules |
Configure OpenSSH Server if Necessary
[ref]groupIf the system needs to act as an SSH server, then
certain changes should be made to the OpenSSH daemon configuration
file /etc/ssh/sshd_config . The following recommendations can be
applied to this file. See the sshd_config(5) man page for more
detailed information. |
contains 20 rules |
Enable Use of Strict Mode Checking
[ref]ruleSSHs StrictModes option checks file and ownership permissions in
the user's home directory .ssh folder before accepting login. If world-
writable permissions are found, logon is rejected. To enable StrictModes in SSH,
add or correct the following line in the /etc/ssh/sshd_config file:
StrictModes yes Rationale:If other users have access to modify user-specific SSH configuration files, they
may be able to log into the system as another user. References:
SV-86887r2_rule, 3.1.12, CCI-000366, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), AC-6, SRG-OS-000480-GPOS-00227 Remediation Shell script: (show)
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/ssh/sshd_config' '^StrictModes' 'yes' 'CCE-80222-3' '%s %s'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: "Enable Use of Strict Mode Checking"
lineinfile:
create: yes
dest: /etc/ssh/sshd_config
regexp: (?i)^#?strictmodes
line: StrictModes yes
validate: sshd -t -f %s
#notify: restart sshd
tags:
- sshd_enable_strictmodes
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80222-3
- NIST-800-53-AC-6
- NIST-800-171-3.1.12
- DISA-STIG-RHEL-07-040450
|
Disable SSH Support for User Known Hosts
[ref]ruleSSH can allow system users user host-based authentication to connect
to systems if a cache of the remote systems public keys are available.
This should be disabled.
To ensure this behavior is disabled, add or correct the
following line in /etc/ssh/sshd_config :
IgnoreUserKnownHosts yes Rationale:Configuring this setting for the SSH daemon provides additional
assurance that remove login via SSH will require a password, even
in the event of misconfiguration elsewhere. References:
FIA_AFL.1, SV-86873r2_rule, 3.1.12, CCI-000366, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), CM-6(a), SRG-OS-000480-GPOS-00227 Remediation Shell script: (show)
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/ssh/sshd_config' '^IgnoreUserKnownHosts' 'yes' 'CCE-80372-6' '%s %s'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: "Disable SSH Support for User Known Hosts"
lineinfile:
create: yes
dest: /etc/ssh/sshd_config
regexp: ^IgnoreUserKnownHosts
line: IgnoreUserKnownHosts yes
validate: sshd -t -f %s
#notify: restart sshd
tags:
- sshd_disable_user_known_hosts
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80372-6
- NIST-800-53-CM-6(a)
- NIST-800-171-3.1.12
- DISA-STIG-RHEL-07-040380
|
Disable SSH Access via Empty Passwords
[ref]ruleTo explicitly disallow SSH login from accounts with
empty passwords, add or correct the following line in /etc/ssh/sshd_config :
PermitEmptyPasswords no
Any accounts with empty passwords should be disabled immediately, and PAM configuration
should prevent users from being able to assign themselves empty passwords.Rationale:Configuring this setting for the SSH daemon provides additional assurance that
remote login via SSH will require a password, even in the event of
misconfiguration elsewhere. References:
FIA_AFL.1, SV-86563r2_rule, 5.2.9, 5.5.6, 3.1.1, 3.1.5, CCI-000366, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), AC-3, AC-6, CM-6(b), SRG-OS-000480-GPOS-00229 Remediation Shell script: (show)
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/ssh/sshd_config' '^PermitEmptyPasswords' 'no' 'CCE-27471-2' '%s %s'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: Disable SSH Access via Empty Passwords
lineinfile:
create: yes
dest: /etc/ssh/sshd_config
regexp: ^PermitEmptyPasswords
line: PermitEmptyPasswords no
validate: sshd -t -f %s
tags:
- sshd_disable_empty_passwords
- high_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27471-2
- NIST-800-53-AC-3
- NIST-800-53-AC-6
- NIST-800-53-CM-6(b)
- NIST-800-171-3.1.1
- NIST-800-171-3.1.5
- CJIS-5.5.6
- DISA-STIG-RHEL-07-010300
|
Set SSH Client Alive Count
[ref]ruleTo ensure the SSH idle timeout occurs precisely when the ClientAliveCountMax is set,
edit /etc/ssh/sshd_config as follows:
ClientAliveCountMax 0 Rationale:This ensures a user login will be terminated as soon as the ClientAliveCountMax
is reached. References:
SV-86865r3_rule, 5.2.12, 5.5.6, 3.1.11, CCI-001133, CCI-002361, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), AC-2(5), SA-8, AC-12, SRG-OS-000163-GPOS-00072, SRG-OS-000279-GPOS-00109 Remediation Shell script: (show)
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/ssh/sshd_config' '^ClientAliveCountMax' '0' 'CCE-27082-7' '%s %s'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: Set SSH Client Alive Count
lineinfile:
create: yes
dest: /etc/ssh/sshd_config
regexp: ^ClientAliveCountMax
line: ClientAliveCountMax 0
validate: sshd -t -f %s
#notify: restart sshd
tags:
- sshd_set_keepalive
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27082-7
- NIST-800-53-AC-2(5)
- NIST-800-53-SA-8
- NIST-800-53-AC-12
- NIST-800-171-3.1.11
- CJIS-5.5.6
- DISA-STIG-RHEL-07-040340
|
Set SSH Idle Timeout Interval
[ref]ruleSSH allows administrators to set an idle timeout
interval.
After this interval has passed, the idle user will be
automatically logged out.
To set an idle timeout interval, edit the following line in /etc/ssh/sshd_config as
follows:
ClientAliveInterval interval
The timeout interval is given in seconds. To have a timeout
of 10 minutes, set interval to 600.
If a shorter timeout has already been set for the login shell, that value will
preempt any SSH setting made here. Keep in mind that some processes may stop SSH
from correctly detecting that the user is idle.Rationale:Terminating an idle ssh session within a short time period reduces the window of
opportunity for unauthorized personnel to take control of a management session
enabled on the console or console port that has been let unattended. References:
SV-86861r3_rule, 5.2.12, 5.5.6, 3.1.11, CCI-001133, CCI-002361, AC-2(5), SA-8(i), AC-12, Req-8.1.8, SRG-OS-000163-GPOS-00072, SRG-OS-000279-GPOS-00109 Remediation Shell script: (show)
sshd_idle_timeout_value="600"
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/ssh/sshd_config' '^ClientAliveInterval' $sshd_idle_timeout_value 'CCE-27433-2' '%s %s'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: XCCDF Value sshd_idle_timeout_value # promote to variable
set_fact:
sshd_idle_timeout_value: 600
tags:
- always
- name: Set SSH Idle Timeout Interval
lineinfile:
create: yes
dest: /etc/ssh/sshd_config
regexp: ^ClientAliveInterval
line: "ClientAliveInterval {{ sshd_idle_timeout_value }}"
validate: sshd -t -f %s
#notify: restart sshd
tags:
- sshd_set_idle_timeout
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27433-2
- NIST-800-53-AC-2(5)
- NIST-800-53-SA-8(i)
- NIST-800-53-AC-12
- NIST-800-171-3.1.11
- PCI-DSS-Req-8.1.8
- CJIS-5.5.6
- DISA-STIG-RHEL-07-040320
|
Enable SSH Warning Banner
[ref]ruleTo enable the warning banner and ensure it is consistent
across the system, add or correct the following line in /etc/ssh/sshd_config :
Banner /etc/issue
Another section contains information on how to create an
appropriate system-wide warning banner.Rationale:The warning message reinforces policy awareness during the logon process and
facilitates possible legal action against attackers. Alternatively, systems
whose ownership should not be obvious should ensure usage of a banner that does
not provide easy attribution. References:
FMT_MOF_EXT.1, SV-86849r3_rule, 5.2.16, 5.5.6, 3.1.9, CCI-000048, CCI-000050, CCI-001384, CCI-001385, CCI-001386, CCI-001387, CCI-001388, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), AC-8(a), AC-8(b), AC-8(c)(1), AC-8(c)(2), AC-8(c)(3), SRG-OS-000023-GPOS-00006, SRG-OS-000024-GPOS-00007, SRG-OS-000228-GPOS-00088 Remediation Shell script: (show)
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/ssh/sshd_config' '^Banner' '/etc/issue' 'CCE-27314-4' '%s %s'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: Enable SSH Warning Banner
lineinfile:
create: yes
dest: /etc/ssh/sshd_config
regexp: ^Banner
line: Banner /etc/issue
validate: sshd -t -f %s
tags:
- sshd_enable_warning_banner
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27314-4
- NIST-800-53-AC-8(a)
- NIST-800-53-AC-8(b)
- NIST-800-53-AC-8(c)(1)
- NIST-800-53-AC-8(c)(2)
- NIST-800-53-AC-8(c)(3)
- NIST-800-171-3.1.9
- CJIS-5.5.6
- DISA-STIG-RHEL-07-040170
|
Use Only FIPS 140-2 Validated MACs
[ref]ruleLimit the MACs to those hash algorithms which are FIPS-approved.
The following line in /etc/ssh/sshd_config demonstrates use
of FIPS-approved MACs:
MACs hmac-sha2-512,hmac-sha2-256
Only the following message authentication codes are FIPS 140-2 certified on RHEL 7:
- hmac-sha1
- hmac-sha2-256
- hmac-sha2-512
- hmac-sha1-etm@openssh.com
- hmac-sha2-256-etm@openssh.com
- hmac-sha2-512-etm@openssh.com
Any combination of the above MACs will pass this check. Official FIPS 140-2 paperwork for
RHEL7 can be found at http://csrc.nist.gov/groups/STM/cmvp/documents/140-1/140sp/140sp2630.pdf.Rationale:DoD Information Systems are required to use FIPS-approved cryptographic hash
functions. The only SSHv2 hash algorithms meeting this requirement is SHA2. References:
SV-86877r2_rule, 5.2.12, 3.1.13, 3.13.11, 3.13.8, CCI-001453, 164.308(b)(1), 164.308(b)(2), 164.312(e)(1), 164.312(e)(2)(i), 164.312(e)(2)(ii), 164.314(b)(2)(i), AC-17(2), IA-7, SC-13, SRG-OS-000250-GPOS-00093 Remediation Shell script: (show)
sshd_approved_macs="(N/A)"
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/ssh/sshd_config' '^MACs' "$sshd_approved_macs" 'CCE-27455-5' '%s %s'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: "Use Only Approved MACs"
lineinfile:
create: yes
dest: /etc/ssh/sshd_config
regexp: ^MACs
line: MACs hmac-sha2-512,hmac-sha2-256,hmac-sha1
validate: sshd -t -f %s
#notify: restart sshd
tags:
- sshd_use_approved_macs
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27455-5
- NIST-800-53-AC-17(2)
- NIST-800-53-IA-7
- NIST-800-53-SC-13
- NIST-800-171-3.1.13
- NIST-800-171-3.13.11
- NIST-800-171-3.13.8
- DISA-STIG-RHEL-07-040400
|
Do Not Allow SSH Environment Options
[ref]ruleTo ensure users are not able to override environment
options to the SSH daemon, add or correct the following line
in /etc/ssh/sshd_config :
PermitUserEnvironment no Rationale:SSH environment options potentially allow users to bypass
access restriction in some configurations. References:
SV-86581r2_rule, 5.2.10, 5.5.6, 3.1.12, CCI-000366, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), CM-6(b), SRG-OS-000480-GPOS-00229 Remediation Shell script: (show)
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/ssh/sshd_config' '^PermitUserEnvironment' 'no' 'CCE-27363-1' '%s %s'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: Do Not Allow SSH Environment Options
lineinfile:
create: yes
dest: /etc/ssh/sshd_config
regexp: ^PermitUserEnvironment
line: PermitUserEnvironment no
validate: sshd -t -f %s
tags:
- sshd_do_not_permit_user_env
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27363-1
- NIST-800-53-CM-6(b)
- NIST-800-171-3.1.12
- CJIS-5.5.6
- DISA-STIG-RHEL-07-010460
|
Disable Kerberos Authentication
[ref]ruleUnless needed, SSH should not permit extraneous or unnecessary
authentication mechanisms like Kerberos. To disable Kerberos authentication, add
or correct the following line in the /etc/ssh/sshd_config file:
KerberosAuthentication no Rationale:Kerberos authentication for SSH is often implemented using GSSAPI. If Kerberos
is enabled through SSH, the SSH daemon provides a means of access to the
system's Kerberos implementation. Vulnerabilities in the system's Kerberos
implementations may be subject to exploitation. References:
FIA_AFL.1, SV-86885r2_rule, 3.1.12, CCI-000368, CCI-000318, CCI-001812, CCI-001813, CCI-001814, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), CM-6(c), SRG-OS-000364-GPOS-00151 Remediation Shell script: (show)
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/ssh/sshd_config' '^KerberosAuthentication' 'no' 'CCE-80221-5' '%s %s'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: "Disable Kerberos Authentication"
lineinfile:
create: yes
dest: /etc/ssh/sshd_config
regexp: (?i)^#?kerberosauthentication
line: KerberosAuthentication no
validate: sshd -t -f %s
#notify: restart sshd
tags:
- sshd_disable_kerb_auth
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80221-5
- NIST-800-53-CM-6(c)
- NIST-800-171-3.1.12
- DISA-STIG-RHEL-07-040440
|
Allow Only SSH Protocol 2
[ref]ruleOnly SSH protocol version 2 connections should be
permitted. The default setting in
/etc/ssh/sshd_config is correct, and can be
verified by ensuring that the following
line appears:
Protocol 2 Rationale:SSH protocol version 1 is an insecure implementation of the SSH protocol and
has many well-known vulnerability exploits. Exploits of the SSH daemon could provide
immediate root access to the system. References:
SV-86875r3_rule, 5.2.2, 5.5.6, 3.1.13, 3.5.4, CCI-000197, CCI-000366, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), AC-17(8).1(ii), IA-5(1)(c), SRG-OS-000074-GPOS-00042, SRG-OS-000480-GPOS-00227 Remediation Shell script: (show)
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/ssh/sshd_config' '^Protocol' '2' 'CCE-27320-1' '%s %s'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: "Allow Only SSH Protocol 2"
lineinfile:
dest: /etc/ssh/sshd_config
regexp: "^Protocol [0-9]"
line: "Protocol 2"
validate: sshd -t -f %s
#notify: :reload ssh
tags:
- sshd_allow_only_protocol2
- high_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27320-1
- NIST-800-53-AC-17(8).1(ii)
- NIST-800-53-IA-5(1)(c)
- NIST-800-171-3.1.13
- NIST-800-171-3.5.4
- CJIS-5.5.6
- DISA-STIG-RHEL-07-040390
|
Disable SSH Support for .rhosts Files
[ref]ruleSSH can emulate the behavior of the obsolete rsh
command in allowing users to enable insecure access to their
accounts via .rhosts files.
To ensure this behavior is disabled, add or correct the
following line in /etc/ssh/sshd_config :
IgnoreRhosts yes Rationale:SSH trust relationships mean a compromise on one host
can allow an attacker to move trivially to other hosts. Remediation Shell script: (show)
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/ssh/sshd_config' '^IgnoreRhosts' 'yes' 'CCE-27377-1' '%s %s'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: Disable SSH Support for .rhosts Files
lineinfile:
create: yes
dest: /etc/ssh/sshd_config
regexp: ^IgnoreRhosts
line: IgnoreRhosts yes
validate: sshd -t -f %s
tags:
- sshd_disable_rhosts
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27377-1
- NIST-800-53-AC-3
- NIST-800-53-CM-6(a)
- NIST-800-171-3.1.12
- CJIS-5.5.6
- DISA-STIG-RHEL-07-040350
|
Disable SSH Support for Rhosts RSA Authentication
[ref]ruleSSH can allow authentication through the obsolete rsh
command through the use of the authenticating user's SSH keys. This should be disabled.
To ensure this behavior is disabled, add or correct the
following line in /etc/ssh/sshd_config :
RhostsRSAAuthentication no Rationale:Configuring this setting for the SSH daemon provides additional
assurance that remove login via SSH will require a password, even
in the event of misconfiguration elsewhere. References:
FIA_AFL.1, SV-86863r3_rule, 3.1.12, CCI-000366, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), CM-6(a), SRG-OS-000480-GPOS-00227 Remediation Shell script: (show)
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/ssh/sshd_config' '^RhostsRSAAuthentication' 'no' 'CCE-80373-4' '%s %s'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: Disable SSH Support for Rhosts RSA Authentication
lineinfile:
create: yes
dest: /etc/ssh/sshd_config
regexp: ^RhostsRSAAuthentication
line: RhostsRSAAuthentication no
validate: sshd -t -f %s
tags:
- sshd_disable_rhosts_rsa
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80373-4
- NIST-800-53-CM-6(a)
- NIST-800-171-3.1.12
- DISA-STIG-RHEL-07-040330
|
Enable Encrypted X11 Forwarding
[ref]ruleBy default, remote X11 connections are not encrypted when initiated
by users. SSH has the capability to encrypt remote X11 connections when SSH's
X11Forwarding option is enabled.
To enable X11 Forwarding, add or correct the
following line in /etc/ssh/sshd_config :
X11Forwarding yes Rationale:Open X displays allow an attacker to capture keystrokes and to execute commands
remotely. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: Enable Encrypted X11 Forwarding
lineinfile:
create: yes
dest: /etc/ssh/sshd_config
regexp: ^X11Forwarding
line: X11Forwarding yes
validate: sshd -t -f %s
tags:
- sshd_enable_x11_forwarding
- high_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80226-4
- NIST-800-53-CM-2(1)(b)
- NIST-800-171-3.1.13
- DISA-STIG-RHEL-07-040710
|
Use Only FIPS 140-2 Validated Ciphers
[ref]ruleLimit the ciphers to those algorithms which are FIPS-approved.
Counter (CTR) mode is also preferred over cipher-block chaining (CBC) mode.
The following line in /etc/ssh/sshd_config demonstrates use of
FIPS 140-2 validated ciphers:
Ciphers aes128-ctr,aes192-ctr,aes256-ctr
The following ciphers are FIPS 140-2 certified on RHEL 7:
- aes128-ctr
- aes192-ctr
- aes256-ctr
- aes128-cbc
- aes192-cbc
- aes256-cbc
- 3des-cbc
- rijndael-cbc@lysator.liu.se
Any combination of the above ciphers will pass this check. Official FIPS 140-2 paperwork for
RHEL7 can be found at http://csrc.nist.gov/groups/STM/cmvp/documents/140-1/140sp/140sp2630.pdf.Rationale:Unapproved mechanisms that are used for authentication to the cryptographic module are not verified and therefore
cannot be relied upon to provide confidentiality or integrity, and system data may be compromised.
Operating systems utilizing encryption are required to use FIPS-compliant mechanisms for authenticating to
cryptographic modules.
FIPS 140-2 is the current standard for validating that mechanisms used to access cryptographic modules
utilize authentication that meets industry and government requirements. For government systems, this allows
Security Levels 1, 2, 3, or 4 for use on Red Hat Enterprise Linux. References:
SV-86845r2_rule, 5.2.10, 5.5.6, 3.1.13, 3.13.11, 3.13.8, CCI-000068, CCI-000366, CCI-000803, 164.308(b)(1), 164.308(b)(2), 164.312(e)(1), 164.312(e)(2)(i), 164.312(e)(2)(ii), 164.314(b)(2)(i), AC-3, AC-17(2), AU-10(5), CM-6(b), IA-5(1)(c), IA-7, SRG-OS-000033-GPOS-00014, SRG-OS-000120-GPOS-00061, SRG-OS-000125-GPOS-00065, SRG-OS-000250-GPOS-00093, SRG-OS-000393-GPOS-00173 Remediation Shell script: (show)
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/ssh/sshd_config' '^Ciphers' 'aes128-ctr,aes192-ctr,aes256-ctr,aes128-cbc,3des-cbc,aes192-cbc,aes256-cbc' 'CCE-27295-5' '%s %s'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: Use Only Approved Ciphers
lineinfile:
create: yes
dest: /etc/ssh/sshd_config
regexp: ^Ciphers
line: Ciphers aes128-ctr,aes192-ctr,aes256-ctr,aes128-cbc,3des-cbc,aes192-cbc,aes256-cbc
validate: sshd -t -f %s
#notify: restart sshd
tags:
- sshd_use_approved_ciphers
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27295-5
- NIST-800-53-AC-3
- NIST-800-53-AC-17(2)
- NIST-800-53-AU-10(5)
- NIST-800-53-CM-6(b)
- NIST-800-53-IA-5(1)(c)
- NIST-800-53-IA-7
- NIST-800-171-3.1.13
- NIST-800-171-3.13.11
- NIST-800-171-3.13.8
- CJIS-5.5.6
- DISA-STIG-RHEL-07-040110
|
Disable Host-Based Authentication
[ref]ruleSSH's cryptographic host-based authentication is
more secure than .rhosts authentication. However, it is
not recommended that hosts unilaterally trust one another, even
within an organization.
To disable host-based authentication, add or correct the
following line in /etc/ssh/sshd_config :
HostbasedAuthentication no Rationale:SSH trust relationships mean a compromise on one host
can allow an attacker to move trivially to other hosts. References:
FIA_AFL.1, SV-86583r2_rule, 5.2.7, 5.5.6, 3.1.12, CCI-000366, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), AC-3, CM-6(b), SRG-OS-000480-GPOS-00229 Remediation Shell script: (show)
grep -q ^HostbasedAuthentication /etc/ssh/sshd_config && \
sed -i "s/HostbasedAuthentication.*/HostbasedAuthentication no/g" /etc/ssh/sshd_config
if ! [ $? -eq 0 ]; then
echo "HostbasedAuthentication no" >> /etc/ssh/sshd_config
fi
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: Disable Host-Based Authentication
lineinfile:
create: yes
dest: /etc/ssh/sshd_config
regexp: ^HostbasedAuthentication
line: HostbasedAuthentication no
tags:
- disable_host_auth
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27413-4
- NIST-800-53-AC-3
- NIST-800-53-CM-6(b)
- NIST-800-171-3.1.12
- CJIS-5.5.6
- DISA-STIG-RHEL-07-010470
|
Enable SSH Server firewalld Firewall exception
[ref]ruleBy default, inbound connections to SSH's port are allowed. If
the SSH server is being used but denied by the firewall, this exception should
be added to the firewall configuration.
To configure firewalld to allow access, run the following command(s):
firewall-cmd --permanent --add-service=ssh Rationale:If inbound SSH connections are expected, adding a firewall rule exception
will allow remote access through the SSH port. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | configure |
---|
- name: Ensure firewalld is installed
package:
name="{{item}}"
state=present
with_items:
- firewalld
tags:
- firewalld_sshd_port_enabled
- unknown_severity
- configure_strategy
- low_complexity
- low_disruption
- CCE-80361-9
- NIST-800-171-3.1.12
- name: XCCDF Value sshd_listening_port # promote to variable
set_fact:
sshd_listening_port: 22
tags:
- always
- name: Enable SSHD in firewalld (custom port)
firewalld:
port: "{{ sshd_listening_port }}/tcp"
permanent: yes
state: enabled
when: sshd_listening_port != 22
tags:
- firewalld_sshd_port_enabled
- unknown_severity
- configure_strategy
- low_complexity
- low_disruption
- CCE-80361-9
- NIST-800-171-3.1.12
- name: Enable SSHD in firewalld (default port)
firewalld:
service: ssh
permanent: yes
state: enabled
when: sshd_listening_port == 22
tags:
- firewalld_sshd_port_enabled
- unknown_severity
- configure_strategy
- low_complexity
- low_disruption
- CCE-80361-9
- NIST-800-171-3.1.12
|
Enable Use of Privilege Separation
[ref]ruleWhen enabled, SSH will create an unprivileged child process that
has the privilege of the authenticated user. To enable privilege separation in
SSH, add or correct the following line in the /etc/ssh/sshd_config file:
UsePrivilegeSeparation sandbox Rationale:SSH daemon privilege separation causes the SSH process to drop root privileges
when not needed which would decrease the impact of software vulnerabilities in
the unprivileged section. References:
SV-86889r2_rule, 3.1.12, CCI-000366, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), AC-6, SRG-OS-000480-GPOS-00227 Remediation Shell script: (show)
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/ssh/sshd_config' '^UsePrivilegeSeparation' 'sandbox' 'CCE-80223-1' '%s %s'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: "Enable use of Privilege Separation"
lineinfile:
create: yes
dest: /etc/ssh/sshd_config
regexp: (?i)^#?useprivilegeseparation
line: UsePrivilegeSeparation sandbox
validate: sshd -t -f %s
#notify: restart sshd
tags:
- sshd_use_priv_separation
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80223-1
- NIST-800-53-AC-6
- NIST-800-171-3.1.12
- DISA-STIG-RHEL-07-040460
|
Disable GSSAPI Authentication
[ref]ruleUnless needed, SSH should not permit extraneous or unnecessary
authentication mechanisms like GSSAPI. To disable GSSAPI authentication, add or
correct the following line in the /etc/ssh/sshd_config file:
GSSAPIAuthentication no Rationale:GSSAPI authentication is used to provide additional authentication mechanisms to
applications. Allowing GSSAPI authentication through SSH exposes the system's
GSSAPI to remote hosts, increasing the attack surface of the system. References:
FIA_AFL.1, SV-86883r2_rule, 3.1.12, CCI-000368, CCI-000318, CCI-001812, CCI-001813, CCI-001814, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), CM-6(c), SRG-OS-000364-GPOS-00151 Remediation Shell script: (show)
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/ssh/sshd_config' '^GSSAPIAuthentication' 'no' 'CCE-80220-7' '%s %s'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: "Disable GSSAPI Authentication"
lineinfile:
create: yes
dest: /etc/ssh/sshd_config
regexp: (?i)^#?gssapiauthentication
line: GSSAPIAuthentication no
validate: sshd -t -f %s
#notify: sshd -t -f %s
tags:
- sshd_disable_gssapi_auth
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80220-7
- NIST-800-53-CM-6(c)
- NIST-800-171-3.1.12
- DISA-STIG-RHEL-07-040430
|
Disable Compression Or Set Compression to delayed
[ref]ruleCompression is useful for slow network connections over long
distances but can cause performance issues on local LANs. If use of compression
is required, it should be enabled only after a user has authenticated; otherwise
, it should be disabled. To disable compression or delay compression until after
a user has successfully authenticated, add or correct the following line in the
/etc/ssh/sshd_config file:
Compression no or Compression delayed Rationale:If compression is allowed in an SSH connection prior to authentication,
vulnerabilities in the compression software could result in compromise of the
system from an unauthenticated connection, potentially wih root privileges. References:
SV-86891r2_rule, 3.1.12, CCI-000366, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), CM-6(b), SRG-OS-000480-GPOS-00227 Remediation Shell script: (show)
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/ssh/sshd_config' '^Compression' 'no' 'CCE-80224-9' '%s %s'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: "Disable Compression or Set Compression to delayed"
lineinfile:
create: yes
dest: /etc/ssh/sshd_config
regexp: (?i)^#?compression
line: Compression delayed
validate: sshd -t -f %s
#notify: restart sshd
tags:
- sshd_disable_compression
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80224-9
- NIST-800-53-CM-6(b)
- NIST-800-171-3.1.12
- DISA-STIG-RHEL-07-040470
|
Disable SSH Root Login
[ref]ruleThe root user should never be allowed to login to a
system directly over a network.
To disable root login via SSH, add or correct the following line
in /etc/ssh/sshd_config :
PermitRootLogin no Rationale:Even though the communications channel may be encrypted, an additional layer of
security is gained by extending the policy of not logging directly on as root.
In addition, logging in with a user-specific account provides individual
accountability of actions performed on the system and also helps to minimize
direct attack attempts on root's password. References:
FIA_AFL.1, SV-86871r2_rule, 5.2.8, 5.5.6, 3.1.1, 3.1.5, CCI-000366, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), AC-3, AC-6(2), IA-2(1), IA-2(5), SRG-OS-000480-GPOS-00227 Remediation Shell script: (show)
SSHD_CONFIG='/etc/ssh/sshd_config'
# Obtain line number of first uncommented case-insensitive occurrence of Match
# block directive (possibly prefixed with whitespace) present in $SSHD_CONFIG
FIRST_MATCH_BLOCK=$(sed -n '/^[[:space:]]*Match[^\n]*/I{=;q}' $SSHD_CONFIG)
# Obtain line number of first uncommented case-insensitive occurence of
# PermitRootLogin directive (possibly prefixed with whitespace) present in
# $SSHD_CONFIG
FIRST_PERMIT_ROOT_LOGIN=$(sed -n '/^[[:space:]]*PermitRootLogin[^\n]*/I{=;q}' $SSHD_CONFIG)
# Case: Match block directive not present in $SSHD_CONFIG
if [ -z "$FIRST_MATCH_BLOCK" ]
then
# Case: PermitRootLogin directive not present in $SSHD_CONFIG yet
if [ -z "$FIRST_PERMIT_ROOT_LOGIN" ]
then
# Append 'PermitRootLogin no' at the end of $SSHD_CONFIG
echo -e "\nPermitRootLogin no" >> $SSHD_CONFIG
# Case: PermitRootLogin directive present in $SSHD_CONFIG already
else
# Replace first uncommented case-insensitive occurrence
# of PermitRootLogin directive
sed -i "$FIRST_PERMIT_ROOT_LOGIN s/^[[:space:]]*PermitRootLogin.*$/PermitRootLogin no/I" $SSHD_CONFIG
fi
# Case: Match block directive present in $SSHD_CONFIG
else
# Case: PermitRootLogin directive not present in $SSHD_CONFIG yet
if [ -z "$FIRST_PERMIT_ROOT_LOGIN" ]
then
# Prepend 'PermitRootLogin no' before first uncommented
# case-insensitive occurrence of Match block directive
sed -i "$FIRST_MATCH_BLOCK s/^\([[:space:]]*Match[^\n]*\)/PermitRootLogin no\n\1/I" $SSHD_CONFIG
# Case: PermitRootLogin directive present in $SSHD_CONFIG and placed
# before first Match block directive
elif [ "$FIRST_PERMIT_ROOT_LOGIN" -lt "$FIRST_MATCH_BLOCK" ]
then
# Replace first uncommented case-insensitive occurrence
# of PermitRootLogin directive
sed -i "$FIRST_PERMIT_ROOT_LOGIN s/^[[:space:]]*PermitRootLogin.*$/PermitRootLogin no/I" $SSHD_CONFIG
# Case: PermitRootLogin directive present in $SSHD_CONFIG and placed
# after first Match block directive
else
# Prepend 'PermitRootLogin no' before first uncommented
# case-insensitive occurrence of Match block directive
sed -i "$FIRST_MATCH_BLOCK s/^\([[:space:]]*Match[^\n]*\)/PermitRootLogin no\n\1/I" $SSHD_CONFIG
fi
fi
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: "Disable SSH Root Login"
lineinfile:
create: yes
dest: "/etc/ssh/sshd_config"
regexp: "^PermitRootLogin"
line: "PermitRootLogin no"
insertafter: '(?i)^#?authentication'
validate: sshd -t -f %s
#notify: restart sshd
tags:
- sshd_disable_root_login
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27445-6
- NIST-800-53-AC-3
- NIST-800-53-AC-6(2)
- NIST-800-53-IA-2(1)
- NIST-800-53-IA-2(5)
- NIST-800-171-3.1.1
- NIST-800-171-3.1.5
- CJIS-5.5.6
- DISA-STIG-RHEL-07-040370
|
Enable the OpenSSH Service
[ref]ruleThe SSH server service, sshd, is commonly needed.
The sshd service can be enabled with the following command:
$ sudo systemctl enable sshd.service Rationale:Without protection of the transmitted information, confidentiality, and
integrity may be compromised because unprotected communications can be
intercepted and either read or altered.
This checklist item applies to both internal and external networks and all types
of information system components from which information can be transmitted (e.g., servers,
mobile devices, notebook computers, printers, copiers, scanners, etc). Communication paths
outside the physical protection of a controlled boundary are exposed to the possibility
of interception and modification. References:
SV-86859r2_rule, 3.1.13, 3.5.4, 3.13.8, CCI-002418, CCI-002420, CCI-002421, CCI-002422, SC-8, SRG-OS-000423-GPOS-00187, SRG-OS-000423-GPOS-00188, SRG-OS-000423-GPOS-00189, SRG-OS000423-GPOS-00190 Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
# Function to enable/disable and start/stop services on RHEL and Fedora systems.
#
# Example Call(s):
#
# service_command enable bluetooth
# service_command disable bluetooth.service
#
# Using xinetd:
# service_command disable rsh.socket xinetd=rsh
#
function service_command {
# Load function arguments into local variables
local service_state=$1
local service=$2
local xinetd=$(echo $3 | cut -d'=' -f2)
# Check sanity of the input
if [ $# -lt "2" ]
then
echo "Usage: service_command 'enable/disable' 'service_name.service'"
echo
echo "To enable or disable xinetd services add \'xinetd=service_name\'"
echo "as the last argument"
echo "Aborting."
exit 1
fi
# If systemctl is installed, use systemctl command; otherwise, use the service/chkconfig commands
if [ -f "/usr/bin/systemctl" ] ; then
service_util="/usr/bin/systemctl"
else
service_util="/sbin/service"
chkconfig_util="/sbin/chkconfig"
fi
# If disable is not specified in arg1, set variables to enable services.
# Otherwise, variables are to be set to disable services.
if [ "$service_state" != 'disable' ] ; then
service_state="enable"
service_operation="start"
chkconfig_state="on"
else
service_state="disable"
service_operation="stop"
chkconfig_state="off"
fi
# If chkconfig_util is not empty, use chkconfig/service commands.
if [ "x$chkconfig_util" != x ] ; then
$service_util $service $service_operation
$chkconfig_util --level 0123456 $service $chkconfig_state
else
$service_util $service_operation $service
$service_util $service_state $service
# The service may not be running because it has been started and failed,
# so let's reset the state so OVAL checks pass.
# Service should be 'inactive', not 'failed' after reboot though.
$service_util reset-failed $service
fi
# Test if local variable xinetd is empty using non-bashism.
# If empty, then xinetd is not being used.
if [ "x$xinetd" != x ] ; then
grep -qi disable /etc/xinetd.d/$xinetd && \
if [ "$service_operation" = 'disable' ] ; then
sed -i "s/disable.*/disable = no/gI" /etc/xinetd.d/$xinetd
else
sed -i "s/disable.*/disable = yes/gI" /etc/xinetd.d/$xinetd
fi
fi
}
service_command enable sshd
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: Enable service sshd
service:
name="{{item}}"
enabled="yes"
state="started"
with_items:
- sshd
tags:
- service_sshd_enabled
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-80216-5
- NIST-800-53-SC-8
- NIST-800-171-3.1.13
- NIST-800-171-3.5.4
- NIST-800-171-3.13.8
- DISA-STIG-RHEL-07-040310
|
Verify Permissions on SSH Server Public *.pub Key Files
[ref]rule
To properly set the permissions of /etc/ssh/*.pub , run the command:
$ sudo chmod 0644 /etc/ssh/*.pub
Rationale:If a public host key file is modified by an unauthorized user, the SSH service
may be compromised. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | configure |
---|
chmod 0644 /etc/ssh/*.pub
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | configure |
---|
- name: Find /etc/ssh/*.pub file(s)
find:
paths: "{{ '/etc/ssh/*.pub' | dirname }}"
patterns: "{{ '/etc/ssh/*.pub' | basename }}"
register: files_found
tags:
- file_permissions_sshd_pub_key
- medium_severity
- configure_strategy
- low_complexity
- low_disruption
- CCE-27311-0
- NIST-800-53-AC-6
- NIST-800-171-3.1.13
- NIST-800-171-3.13.10
- DISA-STIG-RHEL-07-040410
- name: Set permissions
file:
path: "{{ item.path }}"
mode: 0644
with_items:
- "{{ files_found.files }}"
tags:
- file_permissions_sshd_pub_key
- medium_severity
- configure_strategy
- low_complexity
- low_disruption
- CCE-27311-0
- NIST-800-53-AC-6
- NIST-800-171-3.1.13
- NIST-800-171-3.13.10
- DISA-STIG-RHEL-07-040410
Remediation Puppet snippet: (show)
include ssh_public_key_perms
class ssh_public_key_perms {
exec { 'sshd_pub_key':
command => "chmod 0644 /etc/ssh/*.pub",
path => '/bin:/usr/bin'
}
}
|
Verify Permissions on SSH Server Private *_key Key Files
[ref]rule
To properly set the permissions of /etc/ssh/*_key , run the command:
$ sudo chmod 0640 /etc/ssh/*_key
Rationale:If an unauthorized user obtains the private SSH host key file, the host could be
impersonated. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | configure |
---|
chmod 0600 /etc/ssh/*_key
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | configure |
---|
- name: Find /etc/ssh/*_key file(s)
find:
paths: "{{ '/etc/ssh/*_key' | dirname }}"
patterns: "{{ '/etc/ssh/*_key' | basename }}"
register: files_found
tags:
- file_permissions_sshd_private_key
- medium_severity
- configure_strategy
- low_complexity
- low_disruption
- CCE-27485-2
- NIST-800-53-AC-6
- NIST-800-171-3.1.13
- NIST-800-171-3.13.10
- DISA-STIG-RHEL-07-040420
- name: Set permissions
file:
path: "{{ item.path }}"
mode: 0600
with_items:
- "{{ files_found.files }}"
tags:
- file_permissions_sshd_private_key
- medium_severity
- configure_strategy
- low_complexity
- low_disruption
- CCE-27485-2
- NIST-800-53-AC-6
- NIST-800-171-3.1.13
- NIST-800-171-3.13.10
- DISA-STIG-RHEL-07-040420
Remediation Puppet snippet: (show)
include ssh_private_key_perms
class ssh_private_key_perms {
exec { 'sshd_priv_key':
command => "chmod 0640 /etc/ssh/*_key",
path => '/bin:/usr/bin'
}
}
|
System Settings
[ref]groupContains rules that check correct system settings. |
contains 303 rules |
Installing and Maintaining Software
[ref]groupThe following sections contain information on
security-relevant choices during the initial operating system
installation process and the setup of software
updates. |
contains 47 rules |
Disk Partitioning
[ref]groupTo ensure separation and protection of data, there
are top-level system directories which should be placed on their
own physical partition or logical volume. The installer's default
partitioning scheme creates separate logical volumes for
/ , /boot , and swap .
- If starting with any of the default layouts, check the box to
"Review and modify partitioning." This allows for the easy creation
of additional logical volumes inside the volume group already
created, though it may require making
/ 's logical volume smaller to
create space. In general, using logical volumes is preferable to
using partitions because they can be more easily adjusted
later. - If creating a custom layout, create the partitions mentioned in
the previous paragraph (which the installer will require anyway),
as well as separate ones described in the following sections.
If a system has already been installed, and the default
partitioning scheme was used, it is possible but nontrivial to
modify it to create separate logical volumes for the directories
listed above. The Logical Volume Manager (LVM) makes this possible.
See the LVM HOWTO at http://tldp.org/HOWTO/LVM-HOWTO/
for more detailed information on LVM. |
contains 1 rule |
Encrypt Partitions
[ref]ruleRed Hat Enterprise Linux 7 natively supports partition encryption through the
Linux Unified Key Setup-on-disk-format (LUKS) technology. The easiest way to
encrypt a partition is during installation time.
For manual installations, select the Encrypt checkbox during
partition creation to encrypt the partition. When this
option is selected the system will prompt for a passphrase to use in
decrypting the partition. The passphrase will subsequently need to be entered manually
every time the system boots.
For automated/unattended installations, it is possible to use Kickstart by adding
the --encrypted and --passphrase= options to the definition of each partition to be
encrypted. For example, the following line would encrypt the root partition:
part / --fstype=ext4 --size=100 --onpart=hda1 --encrypted --passphrase=PASSPHRASE
Any PASSPHRASE is stored in the Kickstart in plaintext, and the Kickstart must then be protected accordingly.
Omitting the --passphrase= option from the partition definition will cause the
installer to pause and interactively ask for the passphrase during installation.
By default, the Anaconda installer uses aes-xts-plain64 cipher
with a minimum 512 bit key size which should be compatible with FIPS enabled.
Detailed information on encrypting partitions using LUKS or LUKS ciphers can be found on
the Red Hat Documentation web site:
https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/Security_Guide/sec-Encryption.htmlRationale:The risk of a system's physical compromise, particularly mobile systems such as
laptops, places its data at risk of compromise. Encrypting this data mitigates
the risk of its loss if the system is lost. References:
3.13.16, CCI-001199, CCI-002476, 164.308(a)(1)(ii)(D), 164.308(b)(1), 164.310(d), 164.312(a)(1), 164.312(a)(2)(iii), 164.312(a)(2)(iv), 164.312(b), 164.312(c), 164.314(b)(2)(i), 164.312(d), A.8.2.3, SC-13, SC-28(1), SRG-OS-000405-GPOS-00184, SRG-OS-000185-GPOS-00079 |
System and Software Integrity
[ref]groupSystem and software integrity can be gained by installing antivirus, increasing
system encryption strength with FIPS, verifying installed software, enabling SELinux,
installing an Intrusion Prevention System, etc. However, installing or enabling integrity
checking tools cannot prevent intrusions, but they can detect that an intrusion
may have occurred. Requirements for integrity checking may be highly dependent on
the environment in which the system will be used. Snapshot-based approaches such
as AIDE may induce considerable overhead in the presence of frequent software updates. |
contains 15 rules |
Operating System Vendor Support and Certification
[ref]groupThe assurance of a vendor to provide operating system support and maintenance
for their product is an important criterion to ensure product stability and
security over the life of the product. A certified product that follows the
necessary standards and government certification requirements guarantees that
known software vulnerabilities will be remediated, and proper guidance for
protecting and securing the operating system will be given. |
contains 1 rule |
The Installed Operating System Is Vendor Supported and Certified
[ref]ruleThe installed operating system must be maintained and certified by a vendor.
Red Hat Enterprise Linux is supported by Red Hat, Inc. As the Red Hat Enterprise
Linux vendor, Red Hat, Inc. is responsible for providing security patches as well
as meeting and maintaining goverment certifications and standards. Rationale:An operating system is considered "supported" if the vendor continues to provide
security patches for the product as well as maintain government certification requirements.
With an unsupported release, it will not be possible to resolve security issue discovered in
the system software as well as meet government certifications. |
Federal Information Processing Standard (FIPS)
[ref]groupThe Federal Information Processing Standard (FIPS) is a computer security standard which
is developed by the U.S. Government and industry working groups to validate the quality
of cryptographic modules. The FIPS standard provides four security levels to ensure
adequate coverage of different industries, implementation of cryptographic modules, and
organizational sizes and requirements.
FIPS 140-2 is the current standard for validating that mechanisms used to access cryptographic modules
utilize authentication that meets industry and government requirements. For government systems, this allows
Security Levels 1, 2, 3, or 4 for use on Red Hat Enterprise Linux.
See http://csrc.nist.gov/publications/PubsFIPS.html for more information. |
contains 2 rules |
Install the dracut-fips Package
[ref]ruleTo enable FIPS, the system requires that the dracut-fips
package be installed.
The dracut-fips package can be installed with the following command:
$ sudo yum install dracut-fips Rationale:Use of weak or untested encryption algorithms undermines the purposes of utilizing encryption to
protect data. The operating system must implement cryptographic modules adhering to the higher
standards approved by the federal government since this provides assurance they have been tested
and validated. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
# Function to install packages on RHEL, Fedora, Debian, and possibly other systems.
#
# Example Call(s):
#
# package_install aide
#
function package_install {
# Load function arguments into local variables
local package="$1"
# Check sanity of the input
if [ $# -ne "1" ]
then
echo "Usage: package_install 'package_name'"
echo "Aborting."
exit 1
fi
if which dnf ; then
if ! rpm -q --quiet "$package"; then
dnf install -y "$package"
fi
elif which yum ; then
if ! rpm -q --quiet "$package"; then
yum install -y "$package"
fi
elif which apt-get ; then
apt-get install -y "$package"
else
echo "Failed to detect available packaging system, tried dnf, yum and apt-get!"
echo "Aborting."
exit 1
fi
}
package_install dracut-fips
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: Ensure dracut-fips is installed
package:
name="{{item}}"
state=present
with_items:
- dracut-fips
tags:
- package_dracut-fips_installed
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-80358-5
- NIST-800-53-AC-17(2)
- NIST-800-171-3.13.11
- NIST-800-171-3.13.8
- CJIS-5.10.1.2
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
include install_dracut-fips
class install_dracut-fips {
package { 'dracut-fips':
ensure => 'installed',
}
}
Remediation Anaconda snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
package --add=dracut-fips
|
Enable FIPS Mode in GRUB2
[ref]ruleTo ensure FIPS mode is enabled, rebuild initramfs by running the following command:
dracut -f
After the dracut command has been run, add the argument fips=1 to the default
GRUB 2 command line for the Linux operating system in
/etc/default/grub , in the manner below:
GRUB_CMDLINE_LINUX="crashkernel=auto rd.lvm.lv=VolGroup/LogVol06 rd.lvm.lv=VolGroup/lv_swap rhgb quiet rd.shell=0 fips=1"
Finally, rebuild the grub.cfg file by using the
grub2-mkconfig -o command as follows:
Rationale:Use of weak or untested encryption algorithms undermines the purposes of utilizing encryption to
protect data. The operating system must implement cryptographic modules adhering to the higher
standards approved by the federal government since this provides assurance they have been tested
and validated. Remediation Shell script: (show)
# if prelink package is installed disable it, else ignore
if rpm -q prelink 2>&1 > /dev/null; then
if grep -q '^PRELINKING=' /etc/sysconfig/prelink ; then
sed -i 's/^PRELINKING=.*/# Set PRELINKING to "no" per security requirements\nPRELINKING=no/' /etc/sysconfig/prelink
else
echo -e "\n# Set PRELINKING to 'no' per security requirements" >> /etc/sysconfig/prelink
echo "PRELINKING=no" >> /etc/sysconfig/prelink
fi
prelink -u -a
fi
# Function to install packages on RHEL, Fedora, Debian, and possibly other systems.
#
# Example Call(s):
#
# package_install aide
#
function package_install {
# Load function arguments into local variables
local package="$1"
# Check sanity of the input
if [ $# -ne "1" ]
then
echo "Usage: package_install 'package_name'"
echo "Aborting."
exit 1
fi
if which dnf ; then
if ! rpm -q --quiet "$package"; then
dnf install -y "$package"
fi
elif which yum ; then
if ! rpm -q --quiet "$package"; then
yum install -y "$package"
fi
elif which apt-get ; then
apt-get install -y "$package"
else
echo "Failed to detect available packaging system, tried dnf, yum and apt-get!"
echo "Aborting."
exit 1
fi
}
package_install dracut-fips
dracut -f
# Correct the form of default kernel command line in grub
if grep -q '^GRUB_CMDLINE_LINUX=.*fips=.*"' /etc/default/grub; then
# modify the GRUB command-line if a fips= arg already exists
sed -i 's/\(^GRUB_CMDLINE_LINUX=".*\)fips=[^[:space:]]*\(.*"\)/\1 fips=1 \2/' /etc/default/grub
else
# no existing fips=arg is present, append it
sed -i 's/\(^GRUB_CMDLINE_LINUX=".*\)"/\1 fips=1"/' /etc/default/grub
fi
# Get the UUID of the device mounted at /boot.
BOOT_UUID=$(findmnt --noheadings --output uuid --target /boot)
if grep -q '^GRUB_CMDLINE_LINUX=".*boot=.*"' /etc/default/grub; then
# modify the GRUB command-line if a boot= arg already exists
sed -i 's/\(^GRUB_CMDLINE_LINUX=".*\)boot=[^[:space:]]*\(.*"\)/\1 boot=UUID='"${BOOT_UUID} \2/" /etc/default/ grub
else
# no existing boot=arg is present, append it
sed -i 's/\(^GRUB_CMDLINE_LINUX=".*\)"/\1 boot=UUID='${BOOT_UUID}'"/' /etc/default/grub
fi
# Correct the form of kernel command line for each installed kernel in the bootloader
/sbin/grubby --update-kernel=ALL --args="fips=1 boot=UUID=${BOOT_UUID}"
|
Endpoint Protection Software
[ref]groupEndpoint protection security software that is not provided or supported
by Red Hat can be installed to provide complementary or duplicative
security capabilities to those provided by the base platform. Add-on
software may not be appropriate for some specialized systems. |
contains 2 rules |
Install Virus Scanning Software
[ref]ruleInstall virus scanning software, which uses signatures to search for the
presence of viruses on the filesystem.
Ensure virus definition files are no older than 7 days, or their last release.
Configure the virus scanning software to perform scans dynamically on all
accessed files. If this is not possible, configure the
system to scan all altered files on the system on a daily
basis. If the system processes inbound SMTP mail, configure the virus scanner
to scan all received mail. Rationale:Virus scanning software can be used to detect if a system has been compromised by
computer viruses, as well as to limit their spread to other systems. |
Install Intrusion Detection Software
[ref]ruleThe 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. Rationale:Host-based intrusion detection tools provide a system-level defense when an
intruder gains access to a system or network. |
Software Integrity Checking
[ref]groupBoth the AIDE (Advanced Intrusion Detection Environment)
software and the RPM package management system provide
mechanisms for verifying the integrity of installed software.
AIDE uses snapshots of file metadata (such as hashes) and compares these
to current system files in order to detect changes.
The RPM package management system can conduct integrity
checks by comparing information in its metadata database with
files installed on the system. |
contains 9 rules |
Verify Integrity with RPM
[ref]groupThe RPM package management system includes the ability
to verify the integrity of installed packages by comparing the
installed files with information about the files taken from the
package metadata stored in the RPM database. Although an attacker
could corrupt the RPM database (analogous to attacking the AIDE
database as described above), this check can still reveal
modification of important files. To list which files on the system differ from what is expected by the RPM database:
$ rpm -qVa
See the man page for rpm to see a complete explanation of each column. |
contains 2 rules |
Verify and Correct File Permissions with RPM
[ref]ruleThe RPM package management system can check file access permissions
of installed software packages, including many that are important
to system security.
Verify that the file permissions of system files
and commands match vendor values. Check the file permissions
with the following command:
$ sudo rpm -Va | grep '^.M'
Output indicates files that do not match vendor defaults.
After locating a file with incorrect permissions,
run the following command to determine which package owns it:
$ rpm -qf FILENAME
Next, run the following command to reset its permissions to
the correct values:
$ sudo rpm --setperms PACKAGENAME 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. References:
SV-86473r2_rule, 1.2.6, 6.1.3, 6.1.4, 6.1.5, 6.1.6, 6.1.7, 6.1.8, 6.1.9, 6.2.3, 5.10.4.1, 3.3.8, 3.4.1, CCI-001494, CCI-001496, 164.308(a)(1)(ii)(D), 164.312(b), 164.312(c)(1), 164.312(c)(2), 164.312(e)(2)(i), AC-6, AU-9(1), AU-9(3), CM-6(d), CM-6(3), Req-11.5, SRG-OS-000257-GPOS-00098, SRG-OS-000278-GPOS-00108 Remediation Shell script: (show)
Complexity: | high |
---|
Disruption: | medium |
---|
Strategy: | restrict |
---|
# Declare array to hold list of RPM packages we need to correct permissions for
declare -a SETPERMS_RPM_LIST
# Create a list of files on the system having permissions different from what
# is expected by the RPM database
FILES_WITH_INCORRECT_PERMS=($(rpm -Va --nofiledigest | grep '^.M' | cut -d ' ' -f4-))
# For each file path from that list:
# * Determine the RPM package the file path is shipped by,
# * Include it into SETPERMS_RPM_LIST array
for FILE_PATH in "${FILES_WITH_INCORRECT_PERMS[@]}"
do
RPM_PACKAGE=$(rpm -qf "$FILE_PATH")
SETPERMS_RPM_LIST=("${SETPERMS_RPM_LIST[@]}" "$RPM_PACKAGE")
done
# Remove duplicate mention of same RPM in $SETPERMS_RPM_LIST (if any)
SETPERMS_RPM_LIST=( $(echo "${SETPERMS_RPM_LIST[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ') )
# For each of the RPM packages left in the list -- reset its permissions to the
# correct values
for RPM_PACKAGE in "${SETPERMS_RPM_LIST[@]}"
do
rpm --setperms "${RPM_PACKAGE}"
done
Remediation Ansible snippet: (show)
Complexity: | high |
---|
Disruption: | medium |
---|
Strategy: | restrict |
---|
- name: "Read list of files with incorrect permissions"
shell: "rpm -Va | grep '^.M' | cut -d ' ' -f5- | sed -r 's;^.*\\s+(.+);\\1;g'"
register: files_with_incorrect_permissions
failed_when: False
changed_when: False
check_mode: no
tags:
- rpm_verify_permissions
- high_severity
- restrict_strategy
- high_complexity
- medium_disruption
- CCE-27209-6
- NIST-800-53-AC-6
- NIST-800-53-AU-9(1)
- NIST-800-53-AU-9(3)
- NIST-800-53-CM-6(d)
- NIST-800-53-CM-6(3)
- NIST-800-171-3.3.8
- NIST-800-171-3.4.1
- PCI-DSS-Req-11.5
- CJIS-5.10.4.1
- DISA-STIG-RHEL-07-010010
- name: "Correct file permissions with RPM"
shell: "rpm --setperms $(rpm -qf '{{item}}')"
with_items: "{{ files_with_incorrect_permissions.stdout_lines }}"
when: files_with_incorrect_permissions.stdout_lines | length > 0
tags:
- rpm_verify_permissions
- high_severity
- restrict_strategy
- high_complexity
- medium_disruption
- CCE-27209-6
- NIST-800-53-AC-6
- NIST-800-53-AU-9(1)
- NIST-800-53-AU-9(3)
- NIST-800-53-CM-6(d)
- NIST-800-53-CM-6(3)
- NIST-800-171-3.3.8
- NIST-800-171-3.4.1
- PCI-DSS-Req-11.5
- CJIS-5.10.4.1
- DISA-STIG-RHEL-07-010010
|
Verify File Hashes with RPM
[ref]ruleWithout cryptographic integrity protections, system
executables and files can be altered by unauthorized users without
detection.
The RPM package management system can check the hashes of
installed software packages, including many that are important to system
security.
To verify that the cryptographic hash of system files and commands match vendor
values, run the following command to list which files on the system
have hashes that differ from what is expected by the RPM database:
$ rpm -Va | grep '^..5'
A "c" in the second column indicates that a file is a configuration file, which
may appropriately be expected to change. If the file was not expected to
change, investigate the cause of the change using audit logs or other means.
The package can then be reinstalled to restore the file.
Run the following command to determine which package owns the file:
$ rpm -qf FILENAME
The package can be reinstalled from a yum repository using the command:
$ sudo yum reinstall PACKAGENAME
Alternatively, the package can be reinstalled from trusted media using the command:
$ sudo rpm -Uvh PACKAGENAME Rationale:The hashes of important files like system executables should match the
information given by the RPM database. Executables with erroneous hashes could
be a sign of nefarious activity on the system. References:
SV-86479r2_rule, 1.2.6, 5.10.4.1, 3.3.8, 3.4.1, CCI-000663, 164.308(a)(1)(ii)(D), 164.312(b), 164.312(c)(1), 164.312(c)(2), 164.312(e)(2)(i), CM-6(d), CM-6(3), SI-7(1), Req-11.5, SRG-OS-000480-GPOS-00227 Remediation Ansible snippet: (show)
Complexity: | high |
---|
Disruption: | medium |
---|
- name: "Set fact: Package manager reinstall command (dnf)"
set_fact:
package_manager_reinstall_cmd: dnf reinstall -y
when: ansible_distribution == "Fedora"
tags:
- rpm_verify_hashes
- high_severity
- unknown_strategy
- high_complexity
- medium_disruption
- CCE-27157-7
- NIST-800-53-CM-6(d)
- NIST-800-53-CM-6(3)
- NIST-800-53-SI-7(1)
- NIST-800-171-3.3.8
- NIST-800-171-3.4.1
- PCI-DSS-Req-11.5
- CJIS-5.10.4.1
- DISA-STIG-RHEL-07-010020
- name: "Set fact: Package manager reinstall command (yum)"
set_fact:
package_manager_reinstall_cmd: yum reinstall -y
when: ansible_distribution == "RedHat" or ansible_distribution == "OracleLinux"
tags:
- rpm_verify_hashes
- high_severity
- unknown_strategy
- high_complexity
- medium_disruption
- CCE-27157-7
- NIST-800-53-CM-6(d)
- NIST-800-53-CM-6(3)
- NIST-800-53-SI-7(1)
- NIST-800-171-3.3.8
- NIST-800-171-3.4.1
- PCI-DSS-Req-11.5
- CJIS-5.10.4.1
- DISA-STIG-RHEL-07-010020
- name: "Read files with incorrect hash"
shell: "rpm -Va | grep -E '^..5.* /(bin|sbin|lib|lib64|usr)/' | sed -r 's;^.*\\s+(.+);\\1;g'"
register: files_with_incorrect_hash
changed_when: False
when: package_manager_reinstall_cmd is defined
check_mode: no
tags:
- rpm_verify_hashes
- high_severity
- unknown_strategy
- high_complexity
- medium_disruption
- CCE-27157-7
- NIST-800-53-CM-6(d)
- NIST-800-53-CM-6(3)
- NIST-800-53-SI-7(1)
- NIST-800-171-3.3.8
- NIST-800-171-3.4.1
- PCI-DSS-Req-11.5
- CJIS-5.10.4.1
- DISA-STIG-RHEL-07-010020
- name: "Reinstall packages of files with incorrect hash"
shell: "{{package_manager_reinstall_cmd}} $(rpm -qf '{{item}}')"
with_items: "{{ files_with_incorrect_hash.stdout_lines }}"
when: package_manager_reinstall_cmd is defined and (files_with_incorrect_hash.stdout_lines | length > 0)
tags:
- rpm_verify_hashes
- high_severity
- unknown_strategy
- high_complexity
- medium_disruption
- CCE-27157-7
- NIST-800-53-CM-6(d)
- NIST-800-53-CM-6(3)
- NIST-800-53-SI-7(1)
- NIST-800-171-3.3.8
- NIST-800-171-3.4.1
- PCI-DSS-Req-11.5
- CJIS-5.10.4.1
- DISA-STIG-RHEL-07-010020
|
Verify Integrity with AIDE
[ref]groupAIDE conducts integrity checks by comparing information about
files with previously-gathered information. Ideally, the AIDE database is
created immediately after initial system configuration, and then again after any
software update. AIDE is highly configurable, with further configuration
information located in /usr/share/doc/aide-VERSION . |
contains 7 rules |
Install AIDE
[ref]ruleInstall 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. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
# Function to install packages on RHEL, Fedora, Debian, and possibly other systems.
#
# Example Call(s):
#
# package_install aide
#
function package_install {
# Load function arguments into local variables
local package="$1"
# Check sanity of the input
if [ $# -ne "1" ]
then
echo "Usage: package_install 'package_name'"
echo "Aborting."
exit 1
fi
if which dnf ; then
if ! rpm -q --quiet "$package"; then
dnf install -y "$package"
fi
elif which yum ; then
if ! rpm -q --quiet "$package"; then
yum install -y "$package"
fi
elif which apt-get ; then
apt-get install -y "$package"
else
echo "Failed to detect available packaging system, tried dnf, yum and apt-get!"
echo "Aborting."
exit 1
fi
}
package_install aide
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: Ensure aide is installed
package:
name="{{item}}"
state=present
with_items:
- aide
tags:
- package_aide_installed
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-27096-7
- NIST-800-53-CM-3(d)
- NIST-800-53-CM-3(e)
- NIST-800-53-CM-6(d)
- NIST-800-53-CM-6(3)
- NIST-800-53-SC-28
- NIST-800-53-SI-7
- PCI-DSS-Req-11.5
- CJIS-5.10.1.3
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
include install_aide
class install_aide {
package { 'aide':
ensure => 'installed',
}
}
Remediation Anaconda snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
package --add=aide
|
Configure AIDE to Verify Extended Attributes
[ref]ruleBy default, the xattrs option is added to the FIPSR ruleset in AIDE.
If using a custom ruleset or the xattrs option is missing, add xattrs
to the appropriate ruleset.
For example, add xattrs to the following line in /etc/aide.conf :
FIPSR = p+i+n+u+g+s+m+c+acl+selinux+xattrs+sha256
AIDE rules can be configured in multiple ways; this is merely one example that is already
configured by default.Rationale:Extended attributes in file systems are used to contain arbitrary data and file metadata
with security implications. Remediation Shell script: (show)
# Function to install packages on RHEL, Fedora, Debian, and possibly other systems.
#
# Example Call(s):
#
# package_install aide
#
function package_install {
# Load function arguments into local variables
local package="$1"
# Check sanity of the input
if [ $# -ne "1" ]
then
echo "Usage: package_install 'package_name'"
echo "Aborting."
exit 1
fi
if which dnf ; then
if ! rpm -q --quiet "$package"; then
dnf install -y "$package"
fi
elif which yum ; then
if ! rpm -q --quiet "$package"; then
yum install -y "$package"
fi
elif which apt-get ; then
apt-get install -y "$package"
else
echo "Failed to detect available packaging system, tried dnf, yum and apt-get!"
echo "Aborting."
exit 1
fi
}
package_install aide
aide_conf="/etc/aide.conf"
groups=$(grep "^[A-Z]\+" $aide_conf | grep -v "^ALLXTRAHASHES" | cut -f1 -d '=' | tr -d ' ' | sort -u)
for group in $groups
do
config=$(grep "^$group\s*=" $aide_conf | cut -f2 -d '=' | tr -d ' ')
if ! [[ $config = *xattrs* ]]
then
if [[ -z $config ]]
then
config="xattrs"
else
config=$config"+xattrs"
fi
fi
sed -i "s/^$group\s*=.*/$group = $config/g" $aide_conf
done
|
Configure AIDE to Verify Access Control Lists (ACLs)
[ref]ruleBy default, the acl option is added to the FIPSR ruleset in AIDE.
If using a custom ruleset or the acl option is missing, add acl
to the appropriate ruleset.
For example, add acl to the following line in /etc/aide.conf :
FIPSR = p+i+n+u+g+s+m+c+acl+selinux+xattrs+sha256
AIDE rules can be configured in multiple ways; this is merely one example that is already
configured by default.Rationale:ACLs can provide permissions beyond those permitted through the file mode and must be
verified by the file integrity tools. Remediation Shell script: (show)
# Function to install packages on RHEL, Fedora, Debian, and possibly other systems.
#
# Example Call(s):
#
# package_install aide
#
function package_install {
# Load function arguments into local variables
local package="$1"
# Check sanity of the input
if [ $# -ne "1" ]
then
echo "Usage: package_install 'package_name'"
echo "Aborting."
exit 1
fi
if which dnf ; then
if ! rpm -q --quiet "$package"; then
dnf install -y "$package"
fi
elif which yum ; then
if ! rpm -q --quiet "$package"; then
yum install -y "$package"
fi
elif which apt-get ; then
apt-get install -y "$package"
else
echo "Failed to detect available packaging system, tried dnf, yum and apt-get!"
echo "Aborting."
exit 1
fi
}
package_install aide
aide_conf="/etc/aide.conf"
groups=$(grep "^[A-Z]\+" $aide_conf | grep -v "^ALLXTRAHASHES" | cut -f1 -d '=' | tr -d ' ' | sort -u)
for group in $groups
do
config=$(grep "^$group\s*=" $aide_conf | cut -f2 -d '=' | tr -d ' ')
if ! [[ $config = *acl* ]]
then
if [[ -z $config ]]
then
config="acl"
else
config=$config"+acl"
fi
fi
sed -i "s/^$group\s*=.*/$group = $config/g" $aide_conf
done
|
Configure AIDE to Use FIPS 140-2 for Validating Hashes
[ref]ruleBy default, the sha512 option is added to the NORMAL ruleset in AIDE.
If using a custom ruleset or the sha512 option is missing, add sha512
to the appropriate ruleset.
For example, add sha512 to the following line in /etc/aide.conf :
NORMAL = FIPSR+sha512
AIDE rules can be configured in multiple ways; this is merely one example that is already
configured by default.Rationale:File integrity tools use cryptographic hashes for verifying file contents and directories
have not been altered. These hashes must be FIPS 140-2 approved cryptographic hashes. Remediation Shell script: (show)
# Function to install packages on RHEL, Fedora, Debian, and possibly other systems.
#
# Example Call(s):
#
# package_install aide
#
function package_install {
# Load function arguments into local variables
local package="$1"
# Check sanity of the input
if [ $# -ne "1" ]
then
echo "Usage: package_install 'package_name'"
echo "Aborting."
exit 1
fi
if which dnf ; then
if ! rpm -q --quiet "$package"; then
dnf install -y "$package"
fi
elif which yum ; then
if ! rpm -q --quiet "$package"; then
yum install -y "$package"
fi
elif which apt-get ; then
apt-get install -y "$package"
else
echo "Failed to detect available packaging system, tried dnf, yum and apt-get!"
echo "Aborting."
exit 1
fi
}
package_install aide
aide_conf="/etc/aide.conf"
forbidden_hashes=(sha1 rmd160 sha256 whirlpool tiger haval gost crc32)
groups=$(grep "^[A-Z]\+" $aide_conf | cut -f1 -d ' ' | tr -d ' ' | sort -u)
for group in $groups
do
config=$(grep "^$group\s*=" $aide_conf | cut -f2 -d '=' | tr -d ' ')
if ! [[ $config = *sha512* ]]
then
config=$config"+sha512"
fi
for hash in ${forbidden_hashes[@]}
do
config=$(echo $config | sed "s/$hash//")
done
config=$(echo $config | sed "s/^\+*//")
config=$(echo $config | sed "s/\+\++/+/")
config=$(echo $config | sed "s/\+$//")
sed -i "s/^$group\s*=.*/$group = $config/g" $aide_conf
done
|
Configure Notification of Post-AIDE Scan Details
[ref]ruleAIDE should notify appropriate personnel of the details of a scan after the scan has been run.
If AIDE has already been configured for periodic execution in /etc/crontab , append the
following line to the existing AIDE line:
| /bin/mail -s "$(hostname) - AIDE Integrity Check" root@localhost
Otherwise, add the following line to /etc/crontab :
05 4 * * * root /usr/sbin/aide --check | /bin/mail -s "$(hostname) - AIDE Integrity Check" root@localhost
AIDE can be executed periodically through other means; this is merely one example.Rationale:Unauthorized changes to the baseline configuration could make the system vulnerable
to various attacks or allow unauthorized access to the operating system. Changes to
operating system configurations can have unintended side effects, some of which may
be relevant to security.
Detecting such changes and providing an automated response can help avoid unintended,
negative consequences that could ultimately affect the security state of the operating
system. The operating system's Information Management Officer (IMO)/Information System
Security Officer (ISSO) and System Administrators (SAs) must be notified via email and/or
monitoring system trap when there is an unauthorized modification of a configuration item. Remediation Shell script: (show)
# Function to install packages on RHEL, Fedora, Debian, and possibly other systems.
#
# Example Call(s):
#
# package_install aide
#
function package_install {
# Load function arguments into local variables
local package="$1"
# Check sanity of the input
if [ $# -ne "1" ]
then
echo "Usage: package_install 'package_name'"
echo "Aborting."
exit 1
fi
if which dnf ; then
if ! rpm -q --quiet "$package"; then
dnf install -y "$package"
fi
elif which yum ; then
if ! rpm -q --quiet "$package"; then
yum install -y "$package"
fi
elif which apt-get ; then
apt-get install -y "$package"
else
echo "Failed to detect available packaging system, tried dnf, yum and apt-get!"
echo "Aborting."
exit 1
fi
}
package_install aide
CRONTAB=/etc/crontab
CRONDIRS='/etc/cron.d /etc/cron.daily /etc/cron.weekly /etc/cron.monthly'
if [ -f /var/spool/cron/root ]; then
VARSPOOL=/var/spool/cron/root
fi
if ! grep -qR '^.*\/usr\/sbin\/aide\s*\-\-check.*|.*\/bin\/mail\s*-s\s*".*"\s*root@.*$' $CRONTAB $VARSPOOL $CRONDIRS; then
echo '0 5 * * * root /usr/sbin/aide --check | /bin/mail -s "$(hostname) - AIDE Integrity Check" root@localhost' >> $CRONTAB
fi
|
Configure Periodic Execution of AIDE
[ref]ruleAt a minimum, AIDE should be configured to run a weekly scan. At most, AIDE should be run daily.
To implement a daily execution of AIDE at 4:05am using cron, add the following line to /etc/crontab :
05 4 * * * root /usr/sbin/aide --check
To implement a weekly execution of AIDE at 4:05am using cron, add the following line to /etc/crontab :
05 4 * * 0 root /usr/sbin/aide --check
AIDE can be executed periodically through other means; this is merely one example.
The usage of cron's special time codes, such as @daily and
@weekly is acceptable.Rationale:By default, AIDE does not install itself for periodic execution. Periodically
running AIDE is necessary to reveal unexpected changes in installed files.
Unauthorized changes to the baseline configuration could make the system vulnerable
to various attacks or allow unauthorized access to the operating system. Changes to
operating system configurations can have unintended side effects, some of which may
be relevant to security.
Detecting such changes and providing an automated response can help avoid unintended,
negative consequences that could ultimately affect the security state of the operating
system. The operating system's Information Management Officer (IMO)/Information System
Security Officer (ISSO) and System Administrators (SAs) must be notified via email and/or
monitoring system trap when there is an unauthorized modification of a configuration item. References:
SV-86597r1_rule, 1.3.2, 5.10.1.3, CCI-001744, CM-3(d), CM-3(e), CM-3(5), CM-6(d), CM-6(3), SC-28, SI-7, Req-11.5, SRG-OS-000363-GPOS-00150 Remediation Shell script: (show)
# Function to install packages on RHEL, Fedora, Debian, and possibly other systems.
#
# Example Call(s):
#
# package_install aide
#
function package_install {
# Load function arguments into local variables
local package="$1"
# Check sanity of the input
if [ $# -ne "1" ]
then
echo "Usage: package_install 'package_name'"
echo "Aborting."
exit 1
fi
if which dnf ; then
if ! rpm -q --quiet "$package"; then
dnf install -y "$package"
fi
elif which yum ; then
if ! rpm -q --quiet "$package"; then
yum install -y "$package"
fi
elif which apt-get ; then
apt-get install -y "$package"
else
echo "Failed to detect available packaging system, tried dnf, yum and apt-get!"
echo "Aborting."
exit 1
fi
}
package_install aide
if ! grep -q "/usr/sbin/aide --check" /etc/crontab ; then
echo "05 4 * * * root /usr/sbin/aide --check" >> /etc/crontab
fi
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: "Ensure AIDE is installed"
package:
name="{{item}}"
state=present
with_items:
- aide
tags:
- aide_periodic_cron_checking
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-26952-2
- NIST-800-53-CM-3(d)
- NIST-800-53-CM-3(e)
- NIST-800-53-CM-3(5)
- NIST-800-53-CM-6(d)
- NIST-800-53-CM-6(3)
- NIST-800-53-SC-28
- NIST-800-53-SI-7
- PCI-DSS-Req-11.5
- CJIS-5.10.1.3
- DISA-STIG-RHEL-07-020030
- name: "Configure Periodic Execution of AIDE"
cron:
name: "run AIDE check"
minute: 05
hour: 04
weekday: 0
user: root
job: "/usr/sbin/aide --check"
tags:
- aide_periodic_cron_checking
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-26952-2
- NIST-800-53-CM-3(d)
- NIST-800-53-CM-3(e)
- NIST-800-53-CM-3(5)
- NIST-800-53-CM-6(d)
- NIST-800-53-CM-6(3)
- NIST-800-53-SC-28
- NIST-800-53-SI-7
- PCI-DSS-Req-11.5
- CJIS-5.10.1.3
- DISA-STIG-RHEL-07-020030
|
Build and Test AIDE Database
[ref]ruleRun 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. Remediation Shell script: (show)
# Function to install packages on RHEL, Fedora, Debian, and possibly other systems.
#
# Example Call(s):
#
# package_install aide
#
function package_install {
# Load function arguments into local variables
local package="$1"
# Check sanity of the input
if [ $# -ne "1" ]
then
echo "Usage: package_install 'package_name'"
echo "Aborting."
exit 1
fi
if which dnf ; then
if ! rpm -q --quiet "$package"; then
dnf install -y "$package"
fi
elif which yum ; then
if ! rpm -q --quiet "$package"; then
yum install -y "$package"
fi
elif which apt-get ; then
apt-get install -y "$package"
else
echo "Failed to detect available packaging system, tried dnf, yum and apt-get!"
echo "Aborting."
exit 1
fi
}
package_install aide
/usr/sbin/aide --init
/bin/cp -p /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: "Ensure AIDE is installed"
package:
name="{{item}}"
state=present
with_items:
- aide
tags:
- aide_build_database
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27220-3
- NIST-800-53-CM-3(d)
- NIST-800-53-CM-3(e)
- NIST-800-53-CM-6(d)
- NIST-800-53-CM-6(3)
- NIST-800-53-SC-28
- NIST-800-53-SI-7
- PCI-DSS-Req-11.5
- CJIS-5.10.1.3
- name: "Build and Test AIDE Database"
shell: /usr/sbin/aide --init
tags:
- aide_build_database
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27220-3
- NIST-800-53-CM-3(d)
- NIST-800-53-CM-3(e)
- NIST-800-53-CM-6(d)
- NIST-800-53-CM-6(3)
- NIST-800-53-SC-28
- NIST-800-53-SI-7
- PCI-DSS-Req-11.5
- CJIS-5.10.1.3
- name: Stage AIDE Database"
copy:
src: /var/lib/aide/aide.db.new.gz
dest: /var/lib/aide/aide.db.gz
backup: yes
remote_src: yes
tags:
- aide_build_database
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27220-3
- NIST-800-53-CM-3(d)
- NIST-800-53-CM-3(e)
- NIST-800-53-CM-6(d)
- NIST-800-53-CM-6(3)
- NIST-800-53-SC-28
- NIST-800-53-SI-7
- PCI-DSS-Req-11.5
- CJIS-5.10.1.3
|
Disable Prelinking
[ref]ruleThe 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. Remediation Shell script: (show)
if rpm --quiet -q prelink; then
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
/usr/sbin/prelink -ua
fi
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: Does prelink file exist
stat:
path=/etc/sysconfig/prelink
register: prelink_exists
tags:
- disable_prelink
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27078-5
- NIST-800-53-CM-6(d)
- NIST-800-53-CM-6(3)
- NIST-800-53-SC-28
- NIST-800-53-SI-7
- NIST-800-171-3.13.11
- PCI-DSS-Req-11.5
- CJIS-5.10.1.3
- name: disable prelinking
lineinfile:
path: /etc/sysconfig/prelink
regexp: '^PRELINKING='
line: 'PRELINKING=no'
when: prelink_exists.stat.exists == True
tags:
- disable_prelink
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27078-5
- NIST-800-53-CM-6(d)
- NIST-800-53-CM-6(3)
- NIST-800-53-SC-28
- NIST-800-53-SI-7
- NIST-800-171-3.13.11
- PCI-DSS-Req-11.5
- CJIS-5.10.1.3
|
Updating Software
[ref]groupThe yum command line tool is used to install and
update software packages. The system also provides a graphical
software update tool in the System menu, in the Administration submenu,
called Software Update.
Red Hat Enterprise Linux systems contain an installed software catalog called
the RPM database, which records metadata of installed packages. Consistently using
yum or the graphical Software Update for all software installation
allows for insight into the current inventory of installed software on the system.
Oracle Linux system contain an installed software catalog called
the RPM database, which records metadata of installed packages. Consistently using
yum or the graphical Software Update for all software installation
allows for insight into the current inventory of installed software on the system. |
contains 7 rules |
Ensure gpgcheck Enabled For All Yum Package Repositories
[ref]ruleTo ensure signature checking is not disabled for
any repos, remove any lines from files in /etc/yum.repos.d of the form:
gpgcheck=0 Rationale:Verifying the authenticity of the software prior to installation
validates the integrity of the patch or upgrade received from
a vendor. This ensures the software has not been tampered with and
that it has been provided by a trusted vendor. Self-signed
certificates are disallowed by this requirement. Certificates
used to verify the software must be from an approved Certificate
Authority (CA). References:
FAU_GEN.1.1.c, 5.10.4.1, 3.4.8, CCI-001749, 164.308(a)(1)(ii)(D), 164.312(b), 164.312(c)(1), 164.312(c)(2), 164.312(e)(2)(i), CM-5(3), SI-7, MA-1(b), Req-6.2, 366 Remediation Shell script: (show)
sed -i 's/gpgcheck=.*/gpgcheck=1/g' /etc/yum.repos.d/*
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
#
- name: Find All Yum Repositories
find:
paths: "/etc/yum.repos.d/"
patterns: "*.repo"
register: yum_find
- name: Ensure gpgcheck Enabled For All Yum Package Repositories
with_items: "{{ yum_find.files }}"
lineinfile:
create: yes
dest: "{{ item.path }}"
regexp: '^gpgcheck'
line: 'gpgcheck=1'
tags:
- ensure_gpgcheck_never_disabled
- high_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-26876-3
- NIST-800-53-CM-5(3)
- NIST-800-53-SI-7
- NIST-800-53-MA-1(b)
- NIST-800-171-3.4.8
- PCI-DSS-Req-6.2
- CJIS-5.10.4.1
|
Ensure Software Patches Installed
[ref]rule
If the system is joined to the Red Hat Network, a Red Hat Satellite Server,
or a yum server, run the following command to install updates:
$ sudo yum update
If the system is not configured to use one of these sources, updates (in the form of RPM packages)
can be manually downloaded from the Red Hat Network and installed using rpm .
NOTE: U.S. Defense systems are required to be patched within 30 days or sooner as local policy
dictates.Rationale:Installing software updates is a fundamental mitigation against
the exploitation of publicly-known vulnerabilities. If the most
recent security patches and updates are not installed, unauthorized
users may take advantage of weaknesses in the unpatched software. The
lack of prompt attention to patching could result in a system compromise. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | high |
---|
Reboot: | true |
---|
Strategy: | patch |
---|
yum -y update
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | high |
---|
Reboot: | true |
---|
Strategy: | patch |
---|
- name: "Security patches are up to date"
package:
name: "*"
state: "latest"
tags:
- security_patches_up_to_date
- high_severity
- patch_strategy
- low_complexity
- high_disruption
- CCE-26895-3
- NIST-800-53-SI-2
- NIST-800-53-SI-2(c)
- NIST-800-53-MA-1(b)
- PCI-DSS-Req-6.2
- CJIS-5.10.4.1
- DISA-STIG-RHEL-07-020260
|
Ensure Red Hat GPG Key Installed
[ref]ruleTo ensure the system can cryptographically verify base software
packages come from Red Hat (and to connect to the Red Hat Network to
receive them), the Red Hat GPG key must properly be installed.
To install the Red Hat GPG key, run:
$ sudo subscription-manager register
If the system is not connected to the Internet or an RHN Satellite,
then install the Red Hat GPG key from trusted media such as
the Red Hat installation CD-ROM or DVD. Assuming the disc is mounted
in /media/cdrom , use the following command as the root user to import
it into the keyring:
$ sudo rpm --import /media/cdrom/RPM-GPG-KEY Rationale:Changes to software components can have significant effects on the
overall security of the operating system. This requirement ensures
the software has not been tampered with and that it has been provided
by a trusted vendor. The Red Hat GPG key is necessary to
cryptographically verify packages are from Red Hat. References:
FAU_GEN.1.1.c, 1.2.3, 5.10.4.1, 3.4.8, CCI-001749, 164.308(a)(1)(ii)(D), 164.312(b), 164.312(c)(1), 164.312(c)(2), 164.312(e)(2)(i), CM-5(3), SI-7, MA-1(b), Req-6.2, 366 Remediation Shell script: (show)
# The two fingerprints below are retrieved from https://access.redhat.com/security/team/key
readonly REDHAT_RELEASE_2_FINGERPRINT="567E 347A D004 4ADE 55BA 8A5F 199E 2F91 FD43 1D51"
readonly REDHAT_AUXILIARY_FINGERPRINT="43A6 E49C 4A38 F4BE 9ABF 2A53 4568 9C88 2FA6 58E0"
# Location of the key we would like to import (once it's integrity verified)
readonly REDHAT_RELEASE_KEY="/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release"
RPM_GPG_DIR_PERMS=$(stat -c %a "$(dirname "$REDHAT_RELEASE_KEY")")
# Verify /etc/pki/rpm-gpg directory permissions are safe
if [ "${RPM_GPG_DIR_PERMS}" -le "755" ]
then
# If they are safe, try to obtain fingerprints from the key file
# (to ensure there won't be e.g. CRC error).
IFS=$'\n' GPG_OUT=($(gpg --with-fingerprint "${REDHAT_RELEASE_KEY}" | grep 'Key fingerprint ='))
GPG_RESULT=$?
# Reset IFS back to default
unset IFS
# No CRC error, safe to proceed
if [ "${GPG_RESULT}" -eq "0" ]
then
tr -s ' ' <<< "${GPG_OUT}" | grep -vE "${REDHAT_RELEASE_2_FINGERPRINT}|${REDHAT_AUXILIARY_FINGERPRINT}" || {
# If file doesn't contains any keys with unknown fingerprint, import it
rpm --import "${REDHAT_RELEASE_KEY}"
}
fi
fi
Remediation Ansible snippet: (show)
Complexity: | medium |
---|
Disruption: | medium |
---|
Strategy: | restrict |
---|
- name: "Read permission of GPG key directory"
stat:
path: /etc/pki/rpm-gpg/
register: gpg_key_directory_permission
check_mode: no
tags:
- ensure_redhat_gpgkey_installed
- high_severity
- restrict_strategy
- medium_complexity
- medium_disruption
- CCE-26957-1
- NIST-800-53-CM-5(3)
- NIST-800-53-SI-7
- NIST-800-53-MA-1(b)
- NIST-800-171-3.4.8
- PCI-DSS-Req-6.2
- CJIS-5.10.4.1
# It should fail if it doesn't find any fingerprints in file - maybe file was not parsed well.
- name: Read signatures in GPG key
shell: gpg --with-fingerprint '/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release' | grep 'Key fingerprint =' | tr -s ' ' | sed 's;.*= ;;g'
changed_when: False
register: gpg_fingerprints
check_mode: no
tags:
- ensure_redhat_gpgkey_installed
- high_severity
- restrict_strategy
- medium_complexity
- medium_disruption
- CCE-26957-1
- NIST-800-53-CM-5(3)
- NIST-800-53-SI-7
- NIST-800-53-MA-1(b)
- NIST-800-171-3.4.8
- PCI-DSS-Req-6.2
- CJIS-5.10.4.1
- name: Set Fact - Valid fingerprints
set_fact:
gpg_valid_fingerprints: ("567E 347A D004 4ADE 55BA 8A5F 199E 2F91 FD43 1D51" "43A6 E49C 4A38 F4BE 9ABF 2A53 4568 9C88 2FA6 58E0")
tags:
- ensure_redhat_gpgkey_installed
- high_severity
- restrict_strategy
- medium_complexity
- medium_disruption
- CCE-26957-1
- NIST-800-53-CM-5(3)
- NIST-800-53-SI-7
- NIST-800-53-MA-1(b)
- NIST-800-171-3.4.8
- PCI-DSS-Req-6.2
- CJIS-5.10.4.1
- name: Import RedHat GPG key
rpm_key:
state: present
key: /etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release
when:
(gpg_key_directory_permission.stat.mode <= '0755')
and (( gpg_fingerprints.stdout_lines | difference(gpg_valid_fingerprints)) | length == 0)
and (gpg_fingerprints.stdout_lines | length > 0)
and (ansible_distribution == "RedHat")
tags:
- ensure_redhat_gpgkey_installed
- high_severity
- restrict_strategy
- medium_complexity
- medium_disruption
- CCE-26957-1
- NIST-800-53-CM-5(3)
- NIST-800-53-SI-7
- NIST-800-53-MA-1(b)
- NIST-800-171-3.4.8
- PCI-DSS-Req-6.2
- CJIS-5.10.4.1
|
Ensure gpgcheck Enabled for Repository Metadata
[ref]ruleVerify the operating system prevents the installation of patches, service packs, device
drivers, or operating system components of local packages without verification of the
repository metadata.
Check that yum verifies the repository metadata prior to install with the
following command. This should be configured by setting repo_gpgcheck to 1
in /etc/yum.conf . Rationale:Changes to any software components can have significant effects to the overall security
of the operating system. This requirement ensures the software has not been tampered and
has been provided by a trusted vendor.
Accordingly, patches, service packs, device drivers, or operating system components must
be signed with a certificate recognized and approved by the organization.
Verifying the authenticity of the software prior to installation validates the integrity
of the patch or upgrade received from a vendor. This ensures the software has not been
tampered with and that it has been provided by a trusted vendor. Self-signed certificates
are disallowed by this requirement. The operating system should not have to verify the software
again.
NOTE: For U.S. Military systems, this requirement does not mandate DoD certificates for
this purpose; however, the certificate used to verify the software must be from an
approved Certificate Authority. |
Ensure YUM Removes Previous Package Versions
[ref]ruleYum should be configured to remove previous software components after
previous versions have been installed. To configure yum to remove the
previous software components after updating, set the clean_requirements_on_remove
to 1 in /etc/yum.conf . Rationale:Previous versions of software components that are not removed from the information
system after updates have been installed may be exploited by some adversaries. Remediation Shell script: (show)
if grep --silent ^clean_requirements_on_remove /etc/yum.conf ; then
sed -i "s/^clean_requirements_on_remove.*/clean_requirements_on_remove=1/g" /etc/yum.conf
else
echo -e "\n# Set clean_requirements_on_remove to 1 per security requirements" >> /etc/yum.conf
echo "clean_requirements_on_remove=1" >> /etc/yum.conf
fi
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: "Ensure YUM Removes Previous Package Versions"
lineinfile:
dest: /etc/yum.conf
regexp: ^#?clean_requirements_on_remove
line: clean_requirements_on_remove=1
insertafter: '\[main\]'
tags:
- clean_components_post_updating
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80346-0
- NIST-800-53-SI-2(6)
- NIST-800-171-3.4.8
- DISA-STIG-RHEL-07-020200
|
Ensure gpgcheck Enabled In Main Yum Configuration
[ref]ruleThe gpgcheck option controls whether
RPM packages' signatures are always checked prior to installation.
To configure yum to check package signatures before installing
them, ensure the following line appears in /etc/yum.conf in
the [main] section:
gpgcheck=1 Rationale:Changes to any software components can have significant effects on the overall security
of the operating system. This requirement ensures the software has not been tampered with
and that it has been provided by a trusted vendor.
Accordingly, patches, service packs, device drivers, or operating system components must
be signed with a certificate recognized and approved by the organization.
Verifying the authenticity of the software prior to installation
validates the integrity of the patch or upgrade received from
a vendor. This ensures the software has not been tampered with and
that it has been provided by a trusted vendor. Self-signed
certificates are disallowed by this requirement. Certificates
used to verify the software must be from an approved Certificate
Authority (CA). References:
FAU_GEN.1.1.c, SV-86601r1_rule, 1.2.2, 5.10.4.1, 3.4.8, CCI-001749, 164.308(a)(1)(ii)(D), 164.312(b), 164.312(c)(1), 164.312(c)(2), 164.312(e)(2)(i), CM-5(3), SI-7, MA-1(b), Req-6.2, SRG-OS-000366-GPOS-00153 Remediation Shell script: (show)
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/yum.conf' '^gpgcheck' '1' 'CCE-26989-4'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
- name: Check existence of yum on Fedora
stat:
path: /etc/yum.conf
register: yum_config_file
check_mode: no
when: ansible_distribution == "Fedora"
# Old versions of Fedora use yum
- name: Ensure GPG check is globally activated (yum)
ini_file:
dest: "{{item}}"
section: main
option: gpgcheck
value: 1
create: False
with_items: "/etc/yum.conf"
when: ansible_distribution == "RedHat" or ansible_distribution == "CentOS" or yum_config_file.stat.exists
tags:
- ensure_gpgcheck_globally_activated
- high_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-26989-4
- NIST-800-53-CM-5(3)
- NIST-800-53-SI-7
- NIST-800-53-MA-1(b)
- NIST-800-171-3.4.8
- PCI-DSS-Req-6.2
- CJIS-5.10.4.1
- DISA-STIG-RHEL-07-020050
- name: Ensure GPG check is globally activated (dnf)
ini_file:
dest: "{{item}}"
section: main
option: gpgcheck
value: 1
create: False
with_items: "/etc/dnf/dnf.conf"
when: ansible_distribution == "Fedora"
tags:
- ensure_gpgcheck_globally_activated
- high_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-26989-4
- NIST-800-53-CM-5(3)
- NIST-800-53-SI-7
- NIST-800-53-MA-1(b)
- NIST-800-171-3.4.8
- PCI-DSS-Req-6.2
- CJIS-5.10.4.1
- DISA-STIG-RHEL-07-020050
|
Ensure gpgcheck Enabled for Local Packages
[ref]ruleYum should be configured to verify the signature(s) of local packages
prior to installation. To configure yum to verify signatures of local
packages, set the localpkg_gpgcheck to 1 in /etc/yum.conf . Rationale:Changes to any software components can have significant effects to the overall security
of the operating system. This requirement ensures the software has not been tampered and
has been provided by a trusted vendor.
Accordingly, patches, service packs, device drivers, or operating system components must
be signed with a certificate recognized and approved by the organization. References:
FAU_GEN.1.1.c, SV-86603r1_rule, 3.4.8, CCI-001749, 164.308(a)(1)(ii)(D), 164.312(b), 164.312(c)(1), 164.312(c)(2), 164.312(e)(2)(i), CM-5(3), SRG-OS-000366-GPOS-00153 Remediation Shell script: (show)
if grep --silent ^localpkg_gpgcheck /etc/yum.conf ; then
sed -i "s/^localpkg_gpgcheck.*/localpkg_gpgcheck=1/g" /etc/yum.conf
else
echo -e "\n# Set localpkg_gpgcheck to 1 per security requirements" >> /etc/yum.conf
echo "localpkg_gpgcheck=1" >> /etc/yum.conf
fi
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
- name: Check existence of yum on Fedora
stat:
path: /etc/yum.conf
register: yum_config_file
check_mode: no
when: ansible_distribution == "Fedora"
# Old versions of Fedora use yum
- name: Ensure GPG check Enabled for Local Packages (Yum)
ini_file:
dest: "{{item}}"
section: main
option: localpkg_gpgcheck
value: 1
create: True
with_items: "/etc/yum.conf"
when: ansible_distribution == "RedHat" or ansible_distribution == "CentOS" or yum_config_file.stat.exists
tags:
- ensure_gpgcheck_local_packages
- high_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80347-8
- NIST-800-53-CM-5(3)
- NIST-800-171-3.4.8
- DISA-STIG-RHEL-07-020060
- name: Ensure GPG check Enabled for Local Packages (DNF)
ini_file:
dest: "{{item}}"
section: main
option: localpkg_gpgcheck
value: 1
create: True
with_items: "/etc/dnf/dnf.conf"
when: ansible_distribution == "Fedora"
tags:
- ensure_gpgcheck_local_packages
- high_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80347-8
- NIST-800-53-CM-5(3)
- NIST-800-171-3.4.8
- DISA-STIG-RHEL-07-020060
|
GNOME Desktop Environment
[ref]groupGNOME is a graphical desktop environment bundled with many Linux distributions that
allow users to easily interact with the operating system graphically rather than
textually. The GNOME Graphical Display Manager (GDM) provides login, logout, and user
switching contexts as well as display server management.
GNOME is developed by the GNOME Project and is considered the default
Red Hat Graphical environment.
For more information on GNOME and the GNOME Project, see https://www.gnome.org |
contains 24 rules |
Configure GNOME Screen Locking
[ref]groupIn the default GNOME3 desktop, the screen can be locked
by selecting the user name in the far right corner of the main panel and
selecting Lock.
The following sections detail commands to enforce idle activation of the screensaver,
screen locking, a blank-screen screensaver, and an idle activation time.
Because users should be trained to lock the screen when they
step away from the computer, the automatic locking feature is only
meant as a backup.
The root account can be screen-locked; however, the root account should
never be used to log into an X Windows environment and should only
be used to for direct login via console in emergency circumstances.
For more information about enforcing preferences in the GNOME3 environment using the DConf
configuration system, see http://wiki.gnome.org/dconf and
the man page dconf(1) . For Red Hat specific information on configuring DConf
settings, see https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/Desktop_Migration_and_Administration_Guide/part-Configuration_and_Administration.html |
contains 8 rules |
Ensure Users Cannot Change GNOME3 Session Idle Settings
[ref]ruleIf not already configured, ensure that users cannot change GNOME3 session idle settings
by adding /org/gnome/desktop/session/idle-delay
to /etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification.
For example:
/org/gnome/desktop/session/idle-delay
After the settings have been set, run dconf update .Rationale:A session time-out lock is a temporary action taken when a user stops work and moves away from the immediate
physical vicinity of the information system but does not logout because of the temporary nature of the absence.
Rather than relying on the user to manually lock their operating system session prior to vacating the vicinity,
GNOME desktops can be configured to identify when a user's session has idled and take action to initiate the
session lock. As such, users should not be allowed to change session settings. Remediation Shell script: (show)
function include_dconf_settings {
:
}
# Function to configure DConf settings for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_settings 'org/gnome/login-screen' 'banner-message-enable' 'true' 'local.d' '10-banner'
#
function dconf_settings {
local _path=$1 _key=$2 _value=$3 _db=$4 _settingFile=$5
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: dconf_settings 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_settingsfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
SETTINGSFILES=($(grep -r "\[${_path}]" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1))
DCONFFILE="/etc/dconf/db/${_db}/${_settingFile}"
DBDIR="/etc/dconf/db/${_db}"
mkdir -p "${DBDIR}"
if [[ -z "${SETTINGSFILES[@]}" ]]
then
[ ! -z ${DCONFFILE} ] || $(echo "" >> ${DCONFFILE})
echo "[${_path}]" >> ${DCONFFILE}
echo "${_key}=${_value}" >> ${DCONFFILE}
else
if grep -q "${_key}" ${SETTINGSFILES[@]}
then
sed -i "s/${_key}\s*=\s*.*/${_key}=${_value}/g" ${SETTINGSFILES[@]}
else
sed -i "\|\[${_path}]|a\\${_key}=${_value}" ${SETTINGSFILES[@]}
fi
fi
}
# Function to configure DConf locks for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_lock 'org/gnome/login-screen' 'banner-message-enable' 'local.d' 'banner'
#
function dconf_lock {
local _key=$1 _setting=$2 _db=$3 _lockFile=$4
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: dconf_lock 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_lockfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/${_key}/${_setting}$" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/${_db}/locks"
mkdir -p "${LOCKSFOLDER}"
if [[ -z "${LOCKFILES}" ]]
then
echo "/${_key}/${_setting}" >> "/etc/dconf/db/${_db}/locks/${_lockFile}"
fi
}
include_dconf_settings
dconf_lock 'org/gnome/desktop/session' 'idle-delay' 'local.d' '00-security-settings-lock'
|
Set GNOME3 Screensaver Lock Delay After Activation Period
[ref]ruleTo activate the locking delay of the screensaver in the GNOME3 desktop when
the screensaver is activated, add or set lock-delay to uint32 (N/A) in
/etc/dconf/db/local.d/00-security-settings . For example:
[org/gnome/desktop/screensaver]
lock-delay=uint32 (N/A)
Once the setting has been added, add a lock to
/etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification.
For example:
/org/gnome/desktop/screensaver/lock-delay
After the settings have been set, run dconf update .Rationale:A session lock is a temporary action taken when a user stops work and moves away from the immediate physical vicinity
of the information system but does not want to logout because of the temporary nature of the absense. Remediation Shell script: (show)
var_screensaver_lock_delay="(N/A)"
function include_dconf_settings {
:
}
# Function to configure DConf settings for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_settings 'org/gnome/login-screen' 'banner-message-enable' 'true' 'local.d' '10-banner'
#
function dconf_settings {
local _path=$1 _key=$2 _value=$3 _db=$4 _settingFile=$5
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: dconf_settings 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_settingsfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
SETTINGSFILES=($(grep -r "\[${_path}]" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1))
DCONFFILE="/etc/dconf/db/${_db}/${_settingFile}"
DBDIR="/etc/dconf/db/${_db}"
mkdir -p "${DBDIR}"
if [[ -z "${SETTINGSFILES[@]}" ]]
then
[ ! -z ${DCONFFILE} ] || $(echo "" >> ${DCONFFILE})
echo "[${_path}]" >> ${DCONFFILE}
echo "${_key}=${_value}" >> ${DCONFFILE}
else
if grep -q "${_key}" ${SETTINGSFILES[@]}
then
sed -i "s/${_key}\s*=\s*.*/${_key}=${_value}/g" ${SETTINGSFILES[@]}
else
sed -i "\|\[${_path}]|a\\${_key}=${_value}" ${SETTINGSFILES[@]}
fi
fi
}
# Function to configure DConf locks for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_lock 'org/gnome/login-screen' 'banner-message-enable' 'local.d' 'banner'
#
function dconf_lock {
local _key=$1 _setting=$2 _db=$3 _lockFile=$4
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: dconf_lock 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_lockfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/${_key}/${_setting}$" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/${_db}/locks"
mkdir -p "${LOCKSFOLDER}"
if [[ -z "${LOCKFILES}" ]]
then
echo "/${_key}/${_setting}" >> "/etc/dconf/db/${_db}/locks/${_lockFile}"
fi
}
include_dconf_settings
dconf_settings 'org/gnome/desktop/screensaver' 'lock-delay' "uint32 ${var_screensaver_lock_delay}" 'local.d' '00-security-settings'
dconf_lock 'org/gnome/desktop/screensaver' 'lock-delay' 'local.d' '00-security-settings-lock'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
- name: "Set GNOME3 Screensaver Lock Delay After Activation Period"
ini_file:
dest: "/etc/dconf/db/local.d/00-security-settings"
section: "org/gnome/desktop/screensaver"
option: lock-delay
value: uint32 5
create: yes
tags:
- dconf_gnome_screensaver_lock_delay
- medium_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80370-0
- NIST-800-53-AC-11(a)
- NIST-800-171-3.1.10
- PCI-DSS-Req-8.1.8
- DISA-STIG-RHEL-07-010110
- name: "Prevent user modification of GNOME lock-delay"
lineinfile:
path: /etc/dconf/db/local.d/locks/00-security-settings-lock
regexp: '^/org/gnome/desktop/screensaver/lock-delay'
line: '/org/gnome/desktop/screensaver/lock-delay'
create: yes
tags:
- dconf_gnome_screensaver_lock_delay
- medium_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80370-0
- NIST-800-53-AC-11(a)
- NIST-800-171-3.1.10
- PCI-DSS-Req-8.1.8
- DISA-STIG-RHEL-07-010110
|
Disable Full User Name on Splash Shield
[ref]ruleBy default when the screen is locked, the splash shield will show the user's
full name. This should be disabled to prevent casual observers from seeing
who has access to the system. This can be disabled by adding or setting
show-full-name-in-top-bar to false in
/etc/dconf/db/local.d/00-security-settings . For example:
[org/gnome/desktop/screensaver]
show-full-name-in-top-bar=false
Once the settings have been added, add a lock to
/etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification.
For example:
/org/gnome/desktop/screensaver/show-full-name-in-top-bar
After the settings have been set, run dconf update .Rationale:Setting the splash screen to not reveal the logged in user's name
conceals who has access to the system from passersby. Remediation Shell script: (show)
function include_dconf_settings {
:
}
# Function to configure DConf settings for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_settings 'org/gnome/login-screen' 'banner-message-enable' 'true' 'local.d' '10-banner'
#
function dconf_settings {
local _path=$1 _key=$2 _value=$3 _db=$4 _settingFile=$5
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: dconf_settings 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_settingsfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
SETTINGSFILES=($(grep -r "\[${_path}]" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1))
DCONFFILE="/etc/dconf/db/${_db}/${_settingFile}"
DBDIR="/etc/dconf/db/${_db}"
mkdir -p "${DBDIR}"
if [[ -z "${SETTINGSFILES[@]}" ]]
then
[ ! -z ${DCONFFILE} ] || $(echo "" >> ${DCONFFILE})
echo "[${_path}]" >> ${DCONFFILE}
echo "${_key}=${_value}" >> ${DCONFFILE}
else
if grep -q "${_key}" ${SETTINGSFILES[@]}
then
sed -i "s/${_key}\s*=\s*.*/${_key}=${_value}/g" ${SETTINGSFILES[@]}
else
sed -i "\|\[${_path}]|a\\${_key}=${_value}" ${SETTINGSFILES[@]}
fi
fi
}
# Function to configure DConf locks for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_lock 'org/gnome/login-screen' 'banner-message-enable' 'local.d' 'banner'
#
function dconf_lock {
local _key=$1 _setting=$2 _db=$3 _lockFile=$4
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: dconf_lock 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_lockfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/${_key}/${_setting}$" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/${_db}/locks"
mkdir -p "${LOCKSFOLDER}"
if [[ -z "${LOCKFILES}" ]]
then
echo "/${_key}/${_setting}" >> "/etc/dconf/db/${_db}/locks/${_lockFile}"
fi
}
include_dconf_settings
dconf_settings 'org/gnome/desktop/screensaver' 'show-full-name-in-top-bar' 'false' 'local.d' '00-security-settings'
dconf_lock 'org/gnome/desktop/screensaver' 'show-full-name-in-top-bar' 'local.d' '00-security-settings-lock'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
- name: "Disable Full Username on Splash Screen"
ini_file:
dest: "/etc/dconf/db/local.d/00-security-settings"
section: "org/gnome/desktop/screensaver"
option: show-full-name-in-top-bar
value: "false"
create: yes
tags:
- dconf_gnome_screensaver_user_info
- unknown_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80114-2
- name: "Prevent user modification of GNOME show-full-name-in-top-bar"
lineinfile:
path: /etc/dconf/db/local.d/locks/00-security-settings-lock
regexp: '^/org/gnome/desktop/screensaver/show-full-name-in-top-bar'
line: '/org/gnome/desktop/screensaver/show-full-name-in-top-bar'
create: yes
tags:
- dconf_gnome_screensaver_user_info
- unknown_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80114-2
|
Ensure Users Cannot Change GNOME3 Screensaver Settings
[ref]ruleIf not already configured, ensure that users cannot change GNOME3 screensaver lock settings
by adding /org/gnome/desktop/screensaver/lock-delay
to /etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification.
For example:
/org/gnome/desktop/screensaver/lock-delay
After the settings have been set, run dconf update .Rationale:A session time-out lock is a temporary action taken when a user stops work and moves away from the immediate
physical vicinity of the information system but does not logout because of the temporary nature of the absence.
Rather than relying on the user to manually lock their operating system session prior to vacating the vicinity,
GNOME desktops can be configured to identify when a user's session has idled and take action to initiate the
session lock. As such, users should not be allowed to change session settings. Remediation Shell script: (show)
function include_dconf_settings {
:
}
# Function to configure DConf settings for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_settings 'org/gnome/login-screen' 'banner-message-enable' 'true' 'local.d' '10-banner'
#
function dconf_settings {
local _path=$1 _key=$2 _value=$3 _db=$4 _settingFile=$5
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: dconf_settings 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_settingsfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
SETTINGSFILES=($(grep -r "\[${_path}]" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1))
DCONFFILE="/etc/dconf/db/${_db}/${_settingFile}"
DBDIR="/etc/dconf/db/${_db}"
mkdir -p "${DBDIR}"
if [[ -z "${SETTINGSFILES[@]}" ]]
then
[ ! -z ${DCONFFILE} ] || $(echo "" >> ${DCONFFILE})
echo "[${_path}]" >> ${DCONFFILE}
echo "${_key}=${_value}" >> ${DCONFFILE}
else
if grep -q "${_key}" ${SETTINGSFILES[@]}
then
sed -i "s/${_key}\s*=\s*.*/${_key}=${_value}/g" ${SETTINGSFILES[@]}
else
sed -i "\|\[${_path}]|a\\${_key}=${_value}" ${SETTINGSFILES[@]}
fi
fi
}
# Function to configure DConf locks for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_lock 'org/gnome/login-screen' 'banner-message-enable' 'local.d' 'banner'
#
function dconf_lock {
local _key=$1 _setting=$2 _db=$3 _lockFile=$4
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: dconf_lock 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_lockfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/${_key}/${_setting}$" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/${_db}/locks"
mkdir -p "${LOCKSFOLDER}"
if [[ -z "${LOCKFILES}" ]]
then
echo "/${_key}/${_setting}" >> "/etc/dconf/db/${_db}/locks/${_lockFile}"
fi
}
include_dconf_settings
dconf_lock 'org/gnome/desktop/screensaver' 'lock-delay' 'local.d' '00-security-settings-lock'
|
Enable GNOME3 Screensaver Idle Activation
[ref]ruleTo activate the screensaver in the GNOME3 desktop after a period of inactivity,
add or set idle-activation-enabled to true in
/etc/dconf/db/local.d/00-security-settings . For example:
[org/gnome/desktop/screensaver]
idle_activation_enabled=true
Once the setting has been added, add a lock to
/etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification.
For example:
/org/gnome/desktop/screensaver/idle-activation-enabled
After the settings have been set, run dconf update .Rationale:A session time-out lock is a temporary action taken when a user stops work and moves away from the immediate
physical vicinity of the information system but does not logout because of the temporary nature of the absence.
Rather than relying on the user to manually lock their operating system session prior to vacating the vicinity,
GNOME desktops can be configured to identify when a user's session has idled and take action to initiate the
session lock.
Enabling idle activation of the screensaver ensures the screensaver will
be activated after the idle delay. Applications requiring continuous,
real-time screen display (such as network management products) require the
login session does not have administrator rights and the display station is located in a
controlled-access area. Remediation Shell script: (show)
function include_dconf_settings {
:
}
# Function to configure DConf settings for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_settings 'org/gnome/login-screen' 'banner-message-enable' 'true' 'local.d' '10-banner'
#
function dconf_settings {
local _path=$1 _key=$2 _value=$3 _db=$4 _settingFile=$5
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: dconf_settings 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_settingsfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
SETTINGSFILES=($(grep -r "\[${_path}]" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1))
DCONFFILE="/etc/dconf/db/${_db}/${_settingFile}"
DBDIR="/etc/dconf/db/${_db}"
mkdir -p "${DBDIR}"
if [[ -z "${SETTINGSFILES[@]}" ]]
then
[ ! -z ${DCONFFILE} ] || $(echo "" >> ${DCONFFILE})
echo "[${_path}]" >> ${DCONFFILE}
echo "${_key}=${_value}" >> ${DCONFFILE}
else
if grep -q "${_key}" ${SETTINGSFILES[@]}
then
sed -i "s/${_key}\s*=\s*.*/${_key}=${_value}/g" ${SETTINGSFILES[@]}
else
sed -i "\|\[${_path}]|a\\${_key}=${_value}" ${SETTINGSFILES[@]}
fi
fi
}
# Function to configure DConf locks for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_lock 'org/gnome/login-screen' 'banner-message-enable' 'local.d' 'banner'
#
function dconf_lock {
local _key=$1 _setting=$2 _db=$3 _lockFile=$4
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: dconf_lock 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_lockfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/${_key}/${_setting}$" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/${_db}/locks"
mkdir -p "${LOCKSFOLDER}"
if [[ -z "${LOCKFILES}" ]]
then
echo "/${_key}/${_setting}" >> "/etc/dconf/db/${_db}/locks/${_lockFile}"
fi
}
include_dconf_settings
dconf_settings 'org/gnome/desktop/screensaver' 'idle-activation-enabled' 'true' 'local.d' '00-security-settings'
dconf_lock 'org/gnome/desktop/screensaver' 'idle-activation-enabled' 'local.d' '00-security-settings-lock'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
- name: "Enable GNOME3 Screensaver Idle Activation"
ini_file:
dest: "/etc/dconf/db/local.d/00-security-settings"
section: "org/gnome/desktop/screensaver"
option: idle_activation_enabled
value: "true"
create: yes
tags:
- dconf_gnome_screensaver_idle_activation_enabled
- medium_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80111-8
- NIST-800-53-AC-11(a)
- NIST-800-171-3.1.10
- PCI-DSS-Req-8.1.8
- CJIS-5.5.5
- DISA-STIG-RHEL-07-010100
- name: "Prevent user modification of GNOME idle_activation_enabled"
lineinfile:
path: /etc/dconf/db/local.d/locks/00-security-settings-lock
regexp: '^/org/gnome/desktop/screensaver/idle-activation-enabled'
line: '/org/gnome/desktop/screensaver/idle-activation-enabled'
create: yes
tags:
- dconf_gnome_screensaver_idle_activation_enabled
- medium_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80111-8
- NIST-800-53-AC-11(a)
- NIST-800-171-3.1.10
- PCI-DSS-Req-8.1.8
- CJIS-5.5.5
- DISA-STIG-RHEL-07-010100
|
Implement Blank Screensaver
[ref]ruleTo set the screensaver mode in the GNOME3 desktop to a blank screen,
add or set picture-uri to string '' in
/etc/dconf/db/local.d/00-security-settings . For example:
[org/gnome/desktop/screensaver]
picture-uri=string ''
Once the settings have been added, add a lock to
/etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification.
For example:
/org/gnome/desktop/screensaver/picture-uri
After the settings have been set, run dconf update .Rationale:Setting the screensaver mode to blank-only conceals the
contents of the display from passersby. Remediation Shell script: (show)
function include_dconf_settings {
:
}
# Function to configure DConf settings for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_settings 'org/gnome/login-screen' 'banner-message-enable' 'true' 'local.d' '10-banner'
#
function dconf_settings {
local _path=$1 _key=$2 _value=$3 _db=$4 _settingFile=$5
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: dconf_settings 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_settingsfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
SETTINGSFILES=($(grep -r "\[${_path}]" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1))
DCONFFILE="/etc/dconf/db/${_db}/${_settingFile}"
DBDIR="/etc/dconf/db/${_db}"
mkdir -p "${DBDIR}"
if [[ -z "${SETTINGSFILES[@]}" ]]
then
[ ! -z ${DCONFFILE} ] || $(echo "" >> ${DCONFFILE})
echo "[${_path}]" >> ${DCONFFILE}
echo "${_key}=${_value}" >> ${DCONFFILE}
else
if grep -q "${_key}" ${SETTINGSFILES[@]}
then
sed -i "s/${_key}\s*=\s*.*/${_key}=${_value}/g" ${SETTINGSFILES[@]}
else
sed -i "\|\[${_path}]|a\\${_key}=${_value}" ${SETTINGSFILES[@]}
fi
fi
}
# Function to configure DConf locks for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_lock 'org/gnome/login-screen' 'banner-message-enable' 'local.d' 'banner'
#
function dconf_lock {
local _key=$1 _setting=$2 _db=$3 _lockFile=$4
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: dconf_lock 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_lockfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/${_key}/${_setting}$" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/${_db}/locks"
mkdir -p "${LOCKSFOLDER}"
if [[ -z "${LOCKFILES}" ]]
then
echo "/${_key}/${_setting}" >> "/etc/dconf/db/${_db}/locks/${_lockFile}"
fi
}
include_dconf_settings
dconf_settings 'org/gnome/desktop/screensaver' 'picture-uri' "string ''" 'local.d' '00-security-settings'
dconf_lock 'org/gnome/desktop/screensaver' 'picture-uri' 'local.d' '00-security-settings-lock'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
- name: "Implement Blank Screensaver"
ini_file:
dest: "/etc/dconf/db/local.d/00-security-settings"
section: "org/gnome/desktop/screensaver"
option: picture-uri
value: string ''
create: yes
tags:
- dconf_gnome_screensaver_mode_blank
- unknown_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80113-4
- NIST-800-53-AC-11(b)
- NIST-800-171-3.1.10
- PCI-DSS-Req-8.1.8
- CJIS-5.5.5
- name: "Prevent user modification of GNOME picture-uri"
lineinfile:
path: /etc/dconf/db/local.d/locks/00-security-settings-lock
regexp: '^/org/gnome/desktop/screensaver/picture-uri'
line: '/org/gnome/desktop/screensaver/picture-uri'
create: yes
tags:
- dconf_gnome_screensaver_mode_blank
- unknown_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80113-4
- NIST-800-53-AC-11(b)
- NIST-800-171-3.1.10
- PCI-DSS-Req-8.1.8
- CJIS-5.5.5
|
Set GNOME3 Screensaver Inactivity Timeout
[ref]ruleThe idle time-out value for inactivity in the GNOME3 desktop is configured via the idle-delay
setting must be set under an appropriate configuration file(s) in the /etc/dconf/db/local.d directory
and locked in /etc/dconf/db/local.d/locks directory to prevent user modification.
For example, to configure the system for a 15 minute delay, add the following to
/etc/dconf/db/local.d/00-security-settings :
[org/gnome/desktop/session]
idle-delay='uint32 900'
Once the setting has been added, add a lock to
/etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification.
For example:
/org/gnome/desktop/session/idle-delay
After the settings have been set, run dconf update .Rationale:A session time-out lock is a temporary action taken when a user stops work and moves away from
the immediate physical vicinity of the information system but does not logout because of the
temporary nature of the absence. Rather than relying on the user to manually lock their operating
system session prior to vacating the vicinity, GNOME3 can be configured to identify when
a user's session has idled and take action to initiate a session lock. Remediation Shell script: (show)
inactivity_timeout_value="900"
function include_dconf_settings {
:
}
# Function to configure DConf settings for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_settings 'org/gnome/login-screen' 'banner-message-enable' 'true' 'local.d' '10-banner'
#
function dconf_settings {
local _path=$1 _key=$2 _value=$3 _db=$4 _settingFile=$5
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: dconf_settings 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_settingsfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
SETTINGSFILES=($(grep -r "\[${_path}]" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1))
DCONFFILE="/etc/dconf/db/${_db}/${_settingFile}"
DBDIR="/etc/dconf/db/${_db}"
mkdir -p "${DBDIR}"
if [[ -z "${SETTINGSFILES[@]}" ]]
then
[ ! -z ${DCONFFILE} ] || $(echo "" >> ${DCONFFILE})
echo "[${_path}]" >> ${DCONFFILE}
echo "${_key}=${_value}" >> ${DCONFFILE}
else
if grep -q "${_key}" ${SETTINGSFILES[@]}
then
sed -i "s/${_key}\s*=\s*.*/${_key}=${_value}/g" ${SETTINGSFILES[@]}
else
sed -i "\|\[${_path}]|a\\${_key}=${_value}" ${SETTINGSFILES[@]}
fi
fi
}
# Function to configure DConf locks for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_lock 'org/gnome/login-screen' 'banner-message-enable' 'local.d' 'banner'
#
function dconf_lock {
local _key=$1 _setting=$2 _db=$3 _lockFile=$4
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: dconf_lock 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_lockfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/${_key}/${_setting}$" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/${_db}/locks"
mkdir -p "${LOCKSFOLDER}"
if [[ -z "${LOCKFILES}" ]]
then
echo "/${_key}/${_setting}" >> "/etc/dconf/db/${_db}/locks/${_lockFile}"
fi
}
include_dconf_settings
dconf_settings 'org/gnome/desktop/session' 'idle-delay' "uint32 ${inactivity_timeout_value}" 'local.d' '00-security-settings'
dconf_lock 'org/gnome/desktop/session' 'idle-delay' 'local.d' '00-security-settings-lock'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
- name: XCCDF Value inactivity_timeout_value # promote to variable
set_fact:
inactivity_timeout_value: 900
tags:
- always
- name: "Set GNOME3 Screensaver Inactivity Timeout"
ini_file:
dest: "/etc/dconf/db/local.d/00-security-settings"
section: "org/gnome/desktop/screensaver"
option: idle-delay
value: "{{ inactivity_timeout_value }}"
create: yes
tags:
- dconf_gnome_screensaver_idle_delay
- medium_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80110-0
- NIST-800-53-AC-11(a)
- NIST-800-171-3.1.10
- PCI-DSS-Req-8.1.8
- CJIS-5.5.5
- DISA-STIG-RHEL-07-010070
- name: "Prevent user modification of GNOME idle-delay"
lineinfile:
path: /etc/dconf/db/local.d/locks/00-security-settings-lock
regexp: '^/org/gnome/desktop/screensaver/idle-delay'
line: '/org/gnome/desktop/screensaver/idle-delay'
create: yes
tags:
- dconf_gnome_screensaver_idle_delay
- medium_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80110-0
- NIST-800-53-AC-11(a)
- NIST-800-171-3.1.10
- PCI-DSS-Req-8.1.8
- CJIS-5.5.5
- DISA-STIG-RHEL-07-010070
|
Enable GNOME3 Screensaver Lock After Idle Period
[ref]ruleTo activate locking of the screensaver in the GNOME3 desktop when it is activated,
add or set lock-enabled to true in
/etc/dconf/db/local.d/00-security-settings . For example:
[org/gnome/desktop/screensaver]
lock-enabled=true
Once the settings have been added, add a lock to
/etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification.
For example:
/org/gnome/desktop/screensaver/lock-enabled
After the settings have been set, run dconf update .Rationale:A session lock is a temporary action taken when a user stops work and moves away from the immediate physical vicinity
of the information system but does not want to logout because of the temporary nature of the absense. Remediation Shell script: (show)
function include_dconf_settings {
:
}
# Function to configure DConf settings for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_settings 'org/gnome/login-screen' 'banner-message-enable' 'true' 'local.d' '10-banner'
#
function dconf_settings {
local _path=$1 _key=$2 _value=$3 _db=$4 _settingFile=$5
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: dconf_settings 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_settingsfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
SETTINGSFILES=($(grep -r "\[${_path}]" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1))
DCONFFILE="/etc/dconf/db/${_db}/${_settingFile}"
DBDIR="/etc/dconf/db/${_db}"
mkdir -p "${DBDIR}"
if [[ -z "${SETTINGSFILES[@]}" ]]
then
[ ! -z ${DCONFFILE} ] || $(echo "" >> ${DCONFFILE})
echo "[${_path}]" >> ${DCONFFILE}
echo "${_key}=${_value}" >> ${DCONFFILE}
else
if grep -q "${_key}" ${SETTINGSFILES[@]}
then
sed -i "s/${_key}\s*=\s*.*/${_key}=${_value}/g" ${SETTINGSFILES[@]}
else
sed -i "\|\[${_path}]|a\\${_key}=${_value}" ${SETTINGSFILES[@]}
fi
fi
}
# Function to configure DConf locks for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_lock 'org/gnome/login-screen' 'banner-message-enable' 'local.d' 'banner'
#
function dconf_lock {
local _key=$1 _setting=$2 _db=$3 _lockFile=$4
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: dconf_lock 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_lockfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/${_key}/${_setting}$" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/${_db}/locks"
mkdir -p "${LOCKSFOLDER}"
if [[ -z "${LOCKFILES}" ]]
then
echo "/${_key}/${_setting}" >> "/etc/dconf/db/${_db}/locks/${_lockFile}"
fi
}
include_dconf_settings
dconf_settings 'org/gnome/desktop/screensaver' 'lock-enabled' 'true' 'local.d' '00-security-settings'
dconf_lock 'org/gnome/desktop/screensaver' 'lock-enabled' 'local.d' '00-security-settings-lock'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
- name: "Enable GNOME3 Screensaver Lock After Idle Period"
ini_file:
dest: "/etc/dconf/db/local.d/00-security-settings"
section: "org/gnome/desktop/screensaver"
option: lock-enabled
value: "true"
create: yes
tags:
- dconf_gnome_screensaver_lock_enabled
- medium_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80112-6
- NIST-800-53-AC-11(b)
- NIST-800-171-3.1.10
- PCI-DSS-Req-8.1.8
- CJIS-5.5.5
- DISA-STIG-RHEL-07-010060
- name: "Prevent user modification of GNOME lock-enabled"
lineinfile:
path: /etc/dconf/db/local.d/locks/00-security-settings-lock
regexp: '^/org/gnome/desktop/screensaver/lock-enabled'
line: '/org/gnome/desktop/screensaver/lock-enabled'
create: yes
tags:
- dconf_gnome_screensaver_lock_enabled
- medium_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80112-6
- NIST-800-53-AC-11(b)
- NIST-800-171-3.1.10
- PCI-DSS-Req-8.1.8
- CJIS-5.5.5
- DISA-STIG-RHEL-07-010060
|
GNOME Media Settings
[ref]groupGNOME media settings that apply to the graphical interface. |
contains 2 rules |
Disable All GNOME3 Thumbnailers
[ref]ruleThe system's default desktop environment, GNOME3, uses
a number of different thumbnailer programs to generate thumbnails
for any new or modified content in an opened folder. To disable the
execution of these thumbnail applications, add or set disable-all
to true in /etc/dconf/db/local.d/00-security-settings .
For example:
[org/gnome/desktop/thumbnailers]
disable-all=true
Once the settings have been added, add a lock to
/etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification.
For example:
/org/gnome/desktop/thumbnailers/disable-all
After the settings have been set, run dconf update .
This effectively prevents an attacker from gaining access to a
system through a flaw in GNOME3's Nautilus thumbnail creators.Rationale:An attacker with knowledge of a flaw in a GNOME3 thumbnailer application could craft a malicious
file to exploit this flaw. Assuming the attacker could place the malicious file on the local filesystem
(via a web upload for example) and assuming a user browses the same location using Nautilus, the
malicious file would exploit the thumbnailer with the potential for malicious code execution. It
is best to disable these thumbnailer applications unless they are explicitly required. Remediation Shell script: (show)
function include_dconf_settings {
:
}
# Function to configure DConf settings for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_settings 'org/gnome/login-screen' 'banner-message-enable' 'true' 'local.d' '10-banner'
#
function dconf_settings {
local _path=$1 _key=$2 _value=$3 _db=$4 _settingFile=$5
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: dconf_settings 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_settingsfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
SETTINGSFILES=($(grep -r "\[${_path}]" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1))
DCONFFILE="/etc/dconf/db/${_db}/${_settingFile}"
DBDIR="/etc/dconf/db/${_db}"
mkdir -p "${DBDIR}"
if [[ -z "${SETTINGSFILES[@]}" ]]
then
[ ! -z ${DCONFFILE} ] || $(echo "" >> ${DCONFFILE})
echo "[${_path}]" >> ${DCONFFILE}
echo "${_key}=${_value}" >> ${DCONFFILE}
else
if grep -q "${_key}" ${SETTINGSFILES[@]}
then
sed -i "s/${_key}\s*=\s*.*/${_key}=${_value}/g" ${SETTINGSFILES[@]}
else
sed -i "\|\[${_path}]|a\\${_key}=${_value}" ${SETTINGSFILES[@]}
fi
fi
}
# Function to configure DConf locks for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_lock 'org/gnome/login-screen' 'banner-message-enable' 'local.d' 'banner'
#
function dconf_lock {
local _key=$1 _setting=$2 _db=$3 _lockFile=$4
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: dconf_lock 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_lockfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/${_key}/${_setting}$" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/${_db}/locks"
mkdir -p "${LOCKSFOLDER}"
if [[ -z "${LOCKFILES}" ]]
then
echo "/${_key}/${_setting}" >> "/etc/dconf/db/${_db}/locks/${_lockFile}"
fi
}
include_dconf_settings
dconf_settings 'org/gnome/desktop/thumbnailers' 'disable-all' 'true' 'local.d' '00-security-settings'
dconf_lock 'org/gnome/desktop/thumbnailers' 'disable-all' 'local.d' '00-security-settings-lock'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
- name: "Disable All GNOME3 Thumbnailers"
ini_file:
dest: /etc/dconf/db/local.d/00-security-settings
section: org/gnome/desktop/thumbnailers
option: disable-all
value: "true"
create: yes
tags:
- dconf_gnome_disable_thumbnailers
- unknown_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80123-3
- NIST-800-53-CM-7
- name: "Prevent user modification of GNOME3 Thumbnailers"
lineinfile:
path: /etc/dconf/db/local.d/locks/00-security-settings-lock
regexp: '^/org/gnome/desktop/thumbnailers/disable-all'
line: '/org/gnome/desktop/thumbnailers/disable-all'
create: yes
tags:
- dconf_gnome_disable_thumbnailers
- unknown_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80123-3
- NIST-800-53-CM-7
|
Disable GNOME3 Automounting
[ref]ruleThe system's default desktop environment, GNOME3, will mount
devices and removable media (such as DVDs, CDs and USB flash drives) whenever
they are inserted into the system. To disable automount and autorun within GNOME3, add or set
automount to false , automount-open to false , and
autorun-never to true in /etc/dconf/db/local.d/00-security-settings .
For example:
[org/gnome/desktop/media-handling]
automount=false
automount-open=false
autorun-never=true
Once the settings have been added, add a lock to
/etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification.
For example:
/org/gnome/desktop/media-handling/automount
/org/gnome/desktop/media-handling/automount-open
/org/gnome/desktop/media-handling/autorun-never
After the settings have been set, run dconf update .Rationale:Disabling automatic mounting in GNOME3 can prevent
the introduction of malware via removable media.
It will, however, also prevent desktop users from legitimate use
of removable media. Remediation Shell script: (show)
function include_dconf_settings {
:
}
# Function to configure DConf settings for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_settings 'org/gnome/login-screen' 'banner-message-enable' 'true' 'local.d' '10-banner'
#
function dconf_settings {
local _path=$1 _key=$2 _value=$3 _db=$4 _settingFile=$5
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: dconf_settings 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_settingsfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
SETTINGSFILES=($(grep -r "\[${_path}]" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1))
DCONFFILE="/etc/dconf/db/${_db}/${_settingFile}"
DBDIR="/etc/dconf/db/${_db}"
mkdir -p "${DBDIR}"
if [[ -z "${SETTINGSFILES[@]}" ]]
then
[ ! -z ${DCONFFILE} ] || $(echo "" >> ${DCONFFILE})
echo "[${_path}]" >> ${DCONFFILE}
echo "${_key}=${_value}" >> ${DCONFFILE}
else
if grep -q "${_key}" ${SETTINGSFILES[@]}
then
sed -i "s/${_key}\s*=\s*.*/${_key}=${_value}/g" ${SETTINGSFILES[@]}
else
sed -i "\|\[${_path}]|a\\${_key}=${_value}" ${SETTINGSFILES[@]}
fi
fi
}
# Function to configure DConf locks for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_lock 'org/gnome/login-screen' 'banner-message-enable' 'local.d' 'banner'
#
function dconf_lock {
local _key=$1 _setting=$2 _db=$3 _lockFile=$4
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: dconf_lock 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_lockfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/${_key}/${_setting}$" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/${_db}/locks"
mkdir -p "${LOCKSFOLDER}"
if [[ -z "${LOCKFILES}" ]]
then
echo "/${_key}/${_setting}" >> "/etc/dconf/db/${_db}/locks/${_lockFile}"
fi
}
include_dconf_settings
dconf_settings 'org/gnome/desktop/media-handling' 'automount' 'false' 'local.d' '00-security-settings'
dconf_settings 'org/gnome/desktop/media-handling' 'automount-open' 'false' 'local.d' '00-security-settings'
dconf_settings 'org/gnome/desktop/media-handling' 'autorun-never' 'true' 'local.d' '00-security-settings'
dconf_lock 'org/gnome/desktop/media-handling' 'automount' 'local.d' '00-security-settings-lock'
dconf_lock 'org/gnome/desktop/media-handling' 'automount-open' 'local.d' '00-security-settings-lock'
dconf_lock 'org/gnome/desktop/media-handling' 'autorun-never' 'local.d' '00-security-settings-lock'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
- name: "Disable GNOME3 Automounting - automount"
ini_file:
dest: /etc/dconf/db/local.d/00-security-settings
section: org/gnome/desktop/media-handling
option: automount
value: "false"
create: yes
tags:
- dconf_gnome_disable_automount
- unknown_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80122-5
- NIST-800-53-AC-19(a)
- NIST-800-53-AC-19(d)
- NIST-800-53-AC-19(e)
- NIST-800-171-3.1.7
- name: "Prevent user modification of GNOME3 Automounting - automount"
lineinfile:
path: /etc/dconf/db/local.d/locks/00-security-settings-lock
regexp: '^/org/gnome/desktop/media-handling/automount'
line: '/org/gnome/desktop/media-handling/automount'
create: yes
tags:
- dconf_gnome_disable_automount
- unknown_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80122-5
- NIST-800-53-AC-19(a)
- NIST-800-53-AC-19(d)
- NIST-800-53-AC-19(e)
- NIST-800-171-3.1.7
- name: "Disable GNOME3 Automounting - automount-open"
ini_file:
dest: /etc/dconf/db/local.d/00-security-settings
section: org/gnome/desktop/media-handling
option: automount-open
value: "false"
create: yes
tags:
- dconf_gnome_disable_automount
- unknown_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80122-5
- NIST-800-53-AC-19(a)
- NIST-800-53-AC-19(d)
- NIST-800-53-AC-19(e)
- NIST-800-171-3.1.7
- name: "Prevent user modification of GNOME3 Automounting - automount-open"
lineinfile:
path: /etc/dconf/db/local.d/locks/00-security-settings-lock
regexp: '^/org/gnome/desktop/media-handling/automount-open'
line: '/org/gnome/desktop/media-handling/automount-open'
create: yes
tags:
- dconf_gnome_disable_automount
- unknown_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80122-5
- NIST-800-53-AC-19(a)
- NIST-800-53-AC-19(d)
- NIST-800-53-AC-19(e)
- NIST-800-171-3.1.7
- name: "Disable GNOME3 Automounting - autorun-never"
ini_file:
dest: /etc/dconf/db/local.d/00-security-settings
section: org/gnome/desktop/media-handling
option: autorun-never
value: "true"
create: yes
tags:
- dconf_gnome_disable_automount
- unknown_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80122-5
- NIST-800-53-AC-19(a)
- NIST-800-53-AC-19(d)
- NIST-800-53-AC-19(e)
- NIST-800-171-3.1.7
- name: "Prevent user modification of GNOME3 Automounting - autorun-never"
lineinfile:
path: /etc/dconf/db/local.d/locks/00-security-settings-lock
regexp: '^/org/gnome/desktop/media-handling/autorun-never'
line: '/org/gnome/desktop/media-handling/autorun-never'
create: yes
tags:
- dconf_gnome_disable_automount
- unknown_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80122-5
- NIST-800-53-AC-19(a)
- NIST-800-53-AC-19(d)
- NIST-800-53-AC-19(e)
- NIST-800-171-3.1.7
|
GNOME System Settings
[ref]groupGNOME provides configuration and functionality to a graphical desktop environment
that changes grahical configurations or allow a user to perform
actions that users normally would not be able to do in non-graphical mode such as
remote access configuration, power policies, Geo-location, etc.
Configuring such settings in GNOME will prevent accidential graphical configuration
changes by users from taking place. |
contains 3 rules |
Disable Ctrl-Alt-Del Reboot Key Sequence in GNOME3
[ref]ruleBy default, GNOME will reboot the system if the Ctrl-Alt-Del
key sequence is pressed.
To configure the system to ignore the Ctrl-Alt-Del key sequence from the
Graphical User Interface (GUI) instead of rebooting the system, add or set
logout to string '' in
/etc/dconf/db/local.d/00-security-settings . For example:
[org/gnome/settings-daemon/plugins/media-keys]
logout=string ''
Once the settings have been added, add a lock to
/etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification.
For example:
/org/gnome/settings-daemon/plugins/media-keys/logout
After the settings have been set, run dconf update .Rationale:A locally logged-in user who presses Ctrl-Alt-Del, when at the console,
can reboot the system. If accidentally pressed, as could happen in
the case of mixed OS environment, this can create the risk of short-term
loss of availability of systems due to unintentional reboot. Remediation Shell script: (show)
function include_dconf_settings {
:
}
# Function to configure DConf settings for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_settings 'org/gnome/login-screen' 'banner-message-enable' 'true' 'local.d' '10-banner'
#
function dconf_settings {
local _path=$1 _key=$2 _value=$3 _db=$4 _settingFile=$5
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: dconf_settings 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_settingsfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
SETTINGSFILES=($(grep -r "\[${_path}]" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1))
DCONFFILE="/etc/dconf/db/${_db}/${_settingFile}"
DBDIR="/etc/dconf/db/${_db}"
mkdir -p "${DBDIR}"
if [[ -z "${SETTINGSFILES[@]}" ]]
then
[ ! -z ${DCONFFILE} ] || $(echo "" >> ${DCONFFILE})
echo "[${_path}]" >> ${DCONFFILE}
echo "${_key}=${_value}" >> ${DCONFFILE}
else
if grep -q "${_key}" ${SETTINGSFILES[@]}
then
sed -i "s/${_key}\s*=\s*.*/${_key}=${_value}/g" ${SETTINGSFILES[@]}
else
sed -i "\|\[${_path}]|a\\${_key}=${_value}" ${SETTINGSFILES[@]}
fi
fi
}
# Function to configure DConf locks for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_lock 'org/gnome/login-screen' 'banner-message-enable' 'local.d' 'banner'
#
function dconf_lock {
local _key=$1 _setting=$2 _db=$3 _lockFile=$4
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: dconf_lock 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_lockfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/${_key}/${_setting}$" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/${_db}/locks"
mkdir -p "${LOCKSFOLDER}"
if [[ -z "${LOCKFILES}" ]]
then
echo "/${_key}/${_setting}" >> "/etc/dconf/db/${_db}/locks/${_lockFile}"
fi
}
include_dconf_settings
dconf_settings 'org/gnome/settings-daemon/plugins/media-keys' 'logout' "string ''" 'local.d' '00-security-settings'
dconf_lock 'org/gnome/settings-daemon/plugins/media-keys' 'logout' 'local.d' '00-security-settings-lock'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
- name: "Disable Ctrl-Alt-Del Reboot Key Sequence in GNOME3"
ini_file:
dest: /etc/dconf/db/local.d/00-security-settings
section: org/gnome/settings-daemon/plugins/media-keys
option: logout
value: string ''
create: yes
tags:
- dconf_gnome_disable_ctrlaltdel_reboot
- high_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80124-1
- NIST-800-53-AC-6
- NIST-800-171-3.1.2
- DISA-STIG-RHEL-07-TBD
- name: "Prevent user modification of GNOME disablement of Ctrl-Alt-Del"
lineinfile:
path: /etc/dconf/db/local.d/locks/00-security-settings-lock
regexp: '^/org/gnome/settings-daemon/plugins/media-keys/logout'
line: '/org/gnome/settings-daemon/plugins/media-keys/logout'
create: yes
tags:
- dconf_gnome_disable_ctrlaltdel_reboot
- high_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80124-1
- NIST-800-53-AC-6
- NIST-800-171-3.1.2
- DISA-STIG-RHEL-07-TBD
|
Disable Geolocation in GNOME3
[ref]ruleGNOME allows the clock and applications to track and access location
information. This setting should be disabled as applications should not track
system location. To configure the system to disable location tracking, add or set
enabled to false in
/etc/dconf/db/local.d/00-security-settings . For example:
[org/gnome/system/location]
enabled=false
To configure the clock to disable location tracking, add or set
geolocation to false in
/etc/dconf/db/local.d/00-security-settings . For example:
[org/gnome/clocks]
geolocation=false
Once the settings have been added, add a lock to
/etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification.
For example:
/org/gnome/system/location/enabled
/org/gnome/clocks/geolocation
After the settings have been set, run dconf update .Rationale:Power settings should not be enabled on systems that are not mobile devices.
Enabling power settings on non-mobile devices could have unintended processing
consequences on standard systems. Remediation Shell script: (show)
function include_dconf_settings {
:
}
# Function to configure DConf settings for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_settings 'org/gnome/login-screen' 'banner-message-enable' 'true' 'local.d' '10-banner'
#
function dconf_settings {
local _path=$1 _key=$2 _value=$3 _db=$4 _settingFile=$5
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: dconf_settings 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_settingsfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
SETTINGSFILES=($(grep -r "\[${_path}]" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1))
DCONFFILE="/etc/dconf/db/${_db}/${_settingFile}"
DBDIR="/etc/dconf/db/${_db}"
mkdir -p "${DBDIR}"
if [[ -z "${SETTINGSFILES[@]}" ]]
then
[ ! -z ${DCONFFILE} ] || $(echo "" >> ${DCONFFILE})
echo "[${_path}]" >> ${DCONFFILE}
echo "${_key}=${_value}" >> ${DCONFFILE}
else
if grep -q "${_key}" ${SETTINGSFILES[@]}
then
sed -i "s/${_key}\s*=\s*.*/${_key}=${_value}/g" ${SETTINGSFILES[@]}
else
sed -i "\|\[${_path}]|a\\${_key}=${_value}" ${SETTINGSFILES[@]}
fi
fi
}
# Function to configure DConf locks for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_lock 'org/gnome/login-screen' 'banner-message-enable' 'local.d' 'banner'
#
function dconf_lock {
local _key=$1 _setting=$2 _db=$3 _lockFile=$4
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: dconf_lock 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_lockfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/${_key}/${_setting}$" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/${_db}/locks"
mkdir -p "${LOCKSFOLDER}"
if [[ -z "${LOCKFILES}" ]]
then
echo "/${_key}/${_setting}" >> "/etc/dconf/db/${_db}/locks/${_lockFile}"
fi
}
include_dconf_settings
dconf_settings 'org/gnome/system/location' 'enabled' 'false' 'local.d' '00-security-settings'
dconf_settings 'org/gnome/clocks' 'geolocation' 'false' 'local.d' '00-security-settings'
dconf_lock 'org/gnome/system/location' 'enabled' 'local.d' '00-security-settings-lock'
dconf_lock 'org/gnome/clocks' 'geolocation' 'local.d' '00-security-settings-lock'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
- name: "Disable Geolocation in GNOME3 - location tracking"
ini_file:
dest: /etc/dconf/db/local.d/00-security-settings
section: org/gnome/system/location
option: enabled
value: "false"
create: yes
tags:
- dconf_gnome_disable_geolocation
- medium_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80117-5
- name: "Disable Geolocation in GNOME3 - clock location tracking"
ini_file:
dest: /etc/dconf/db/local.d/00-security-settings
section: org/gnome/clocks
option: gelocation
value: "false"
create: yes
- name: "Prevent user modification of GNOME geolocation - location tracking"
lineinfile:
path: /etc/dconf/db/local.d/locks/00-security-settings-lock
regexp: '^/org/gnome/system/location/enabled'
line: '/org/gnome/system/location/enabled'
create: yes
tags:
- dconf_gnome_disable_geolocation
- medium_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80117-5
- name: "Prevent user modification of GNOME geolocation - clock location tracking"
lineinfile:
path: /etc/dconf/db/local.d/locks/00-security-settings-lock
regexp: '^/org/gnome/clocks/geolocation'
line: '/org/gnome/clocks/geolocation'
create: yes
tags:
- dconf_gnome_disable_geolocation
- medium_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80117-5
|
Disable User Administration in GNOME3
[ref]ruleBy default, GNOME will allow all users to have some administratrion
capability. This should be disabled so that non-administrative users are not making
configuration changes. To configure the system to disable user administration
capability in the Graphical User Interface (GUI), add or set
user-administration-disabled to true in
/etc/dconf/db/local.d/00-security-settings . For example:
[org/gnome/desktop/lockdown]
user-administration-disabled=true
Once the settings have been added, add a lock to
/etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification.
For example:
/org/gnome/desktop/lockdown/user-administration-disabled
After the settings have been set, run dconf update .Rationale:Allowing all users to have some administratrive capabilities to the system through
the Graphical User Interface (GUI) when they would not have them otherwise could allow
unintended configuration changes as well as a nefarious user the capability to make system
changes such as adding new accounts, etc. Remediation Shell script: (show)
function include_dconf_settings {
:
}
# Function to configure DConf settings for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_settings 'org/gnome/login-screen' 'banner-message-enable' 'true' 'local.d' '10-banner'
#
function dconf_settings {
local _path=$1 _key=$2 _value=$3 _db=$4 _settingFile=$5
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: dconf_settings 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_settingsfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
SETTINGSFILES=($(grep -r "\[${_path}]" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1))
DCONFFILE="/etc/dconf/db/${_db}/${_settingFile}"
DBDIR="/etc/dconf/db/${_db}"
mkdir -p "${DBDIR}"
if [[ -z "${SETTINGSFILES[@]}" ]]
then
[ ! -z ${DCONFFILE} ] || $(echo "" >> ${DCONFFILE})
echo "[${_path}]" >> ${DCONFFILE}
echo "${_key}=${_value}" >> ${DCONFFILE}
else
if grep -q "${_key}" ${SETTINGSFILES[@]}
then
sed -i "s/${_key}\s*=\s*.*/${_key}=${_value}/g" ${SETTINGSFILES[@]}
else
sed -i "\|\[${_path}]|a\\${_key}=${_value}" ${SETTINGSFILES[@]}
fi
fi
}
# Function to configure DConf locks for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_lock 'org/gnome/login-screen' 'banner-message-enable' 'local.d' 'banner'
#
function dconf_lock {
local _key=$1 _setting=$2 _db=$3 _lockFile=$4
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: dconf_lock 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_lockfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/${_key}/${_setting}$" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/${_db}/locks"
mkdir -p "${LOCKSFOLDER}"
if [[ -z "${LOCKFILES}" ]]
then
echo "/${_key}/${_setting}" >> "/etc/dconf/db/${_db}/locks/${_lockFile}"
fi
}
include_dconf_settings
dconf_settings 'org/gnome/desktop/lockdown' 'user-administration-disabled' 'true' 'local.d' '00-security-settings'
dconf_lock 'org/gnome/desktop/lockdown' 'user-administration-disabled' 'local.d' '00-security-settings-lock'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
- name: "Disable User Administration in GNOME3"
ini_file:
dest: /etc/dconf/db/local.d/00-security-settings
section: org/gnome/desktop/lockdown
option: user-administration-disabled
value: "true"
create: yes
tags:
- dconf_gnome_disable_user_admin
- high_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80115-9
- NIST-800-171-3.1.5
- name: "Prevent user modification of GNOME3 Thumbnailers"
lineinfile:
path: /etc/dconf/db/local.d/locks/00-security-settings-lock
regexp: '^/org/gnome/desktop/lockdown/user-administration-disabled'
line: '/org/gnome/desktop/lockdown/user-administration-disabled'
create: yes
tags:
- dconf_gnome_disable_user_admin
- high_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80115-9
- NIST-800-171-3.1.5
|
Configure GNOME Login Screen
[ref]groupIn the default GNOME3 desktop, the login is displayed after system boot
and can display user accounts, allow users to reboot the system, and allow users to
login automatically and/or with a guest account. The login screen should be configured
to prevent such behavior.
For more information about enforcing preferences in the GNOME3 environment using the DConf
configuration system, see https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/Desktop_Migration_and_Administration_Guide/index.html and the man page dconf(1) . |
contains 6 rules |
Enable the GNOME3 Login Smartcard Authentication
[ref]ruleIn the default graphical environment, smart card authentication
can be enabled on the login screen by setting enable-smartcard-authentication
to true .
To enable, add or edit enable-smartcard-authentication to
/etc/dconf/db/gdm.d/00-security-settings . For example:
[org/gnome/login-screen]
enable-smartcard-authentication=true
Once the setting has been added, add a lock to
/etc/dconf/db/gdm.d/locks/00-security-settings-lock to prevent user modification.
For example:
/org/gnome/login-screen/enable-smartcard-authentication
After the settings have been set, run dconf update .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. References:
CCI-000765, CCI-000766, CCI-000767, CCI-000768, CCI-000771, CCI-000772, CCI-000884, CCI-001954, Req-8.3, SRG-OS-000375-GPOS-00160, SV-92515r1_rule Remediation Shell script: (show)
function include_dconf_settings {
:
}
# Function to configure DConf settings for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_settings 'org/gnome/login-screen' 'banner-message-enable' 'true' 'local.d' '10-banner'
#
function dconf_settings {
local _path=$1 _key=$2 _value=$3 _db=$4 _settingFile=$5
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: dconf_settings 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_settingsfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
SETTINGSFILES=($(grep -r "\[${_path}]" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1))
DCONFFILE="/etc/dconf/db/${_db}/${_settingFile}"
DBDIR="/etc/dconf/db/${_db}"
mkdir -p "${DBDIR}"
if [[ -z "${SETTINGSFILES[@]}" ]]
then
[ ! -z ${DCONFFILE} ] || $(echo "" >> ${DCONFFILE})
echo "[${_path}]" >> ${DCONFFILE}
echo "${_key}=${_value}" >> ${DCONFFILE}
else
if grep -q "${_key}" ${SETTINGSFILES[@]}
then
sed -i "s/${_key}\s*=\s*.*/${_key}=${_value}/g" ${SETTINGSFILES[@]}
else
sed -i "\|\[${_path}]|a\\${_key}=${_value}" ${SETTINGSFILES[@]}
fi
fi
}
# Function to configure DConf locks for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_lock 'org/gnome/login-screen' 'banner-message-enable' 'local.d' 'banner'
#
function dconf_lock {
local _key=$1 _setting=$2 _db=$3 _lockFile=$4
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: dconf_lock 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_lockfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/${_key}/${_setting}$" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/${_db}/locks"
mkdir -p "${LOCKSFOLDER}"
if [[ -z "${LOCKFILES}" ]]
then
echo "/${_key}/${_setting}" >> "/etc/dconf/db/${_db}/locks/${_lockFile}"
fi
}
include_dconf_settings
dconf_settings 'org/gnome/login-screen' 'enable-smartcard-authentication' 'true' 'gdm.d' '00-security-settings'
dconf_lock 'org/gnome/login-screen' 'enable-smartcard-authentication' 'gdm.d' '00-security-settings-lock'
|
Disable the GNOME3 Login Restart and Shutdown Buttons
[ref]ruleIn the default graphical environment, users logging
directly into the system are greeted with a login screen that allows
any user, known or unknown, the ability the ability to shutdown or restart
the system. This functionality should be disabled by setting
disable-restart-buttons to true .
To disable, add or edit disable-restart-buttons to
/etc/dconf/db/gdm.d/00-security-settings . For example:
[org/gnome/login-screen]
disable-restart-buttons=true
Once the setting has been added, add a lock to
/etc/dconf/db/gdm.d/locks/00-security-settings-lock to prevent user modification.
For example:
/org/gnome/login-screen/disable-restart-buttons
After the settings have been set, run dconf update .Rationale:A user who is at the console can reboot the system at the login screen. If restart or shutdown buttons
are pressed at the login screen, this can create the risk of short-term loss of availability of systems
due to reboot. Remediation Shell script: (show)
function include_dconf_settings {
:
}
# Function to configure DConf settings for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_settings 'org/gnome/login-screen' 'banner-message-enable' 'true' 'local.d' '10-banner'
#
function dconf_settings {
local _path=$1 _key=$2 _value=$3 _db=$4 _settingFile=$5
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: dconf_settings 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_settingsfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
SETTINGSFILES=($(grep -r "\[${_path}]" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1))
DCONFFILE="/etc/dconf/db/${_db}/${_settingFile}"
DBDIR="/etc/dconf/db/${_db}"
mkdir -p "${DBDIR}"
if [[ -z "${SETTINGSFILES[@]}" ]]
then
[ ! -z ${DCONFFILE} ] || $(echo "" >> ${DCONFFILE})
echo "[${_path}]" >> ${DCONFFILE}
echo "${_key}=${_value}" >> ${DCONFFILE}
else
if grep -q "${_key}" ${SETTINGSFILES[@]}
then
sed -i "s/${_key}\s*=\s*.*/${_key}=${_value}/g" ${SETTINGSFILES[@]}
else
sed -i "\|\[${_path}]|a\\${_key}=${_value}" ${SETTINGSFILES[@]}
fi
fi
}
# Function to configure DConf locks for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_lock 'org/gnome/login-screen' 'banner-message-enable' 'local.d' 'banner'
#
function dconf_lock {
local _key=$1 _setting=$2 _db=$3 _lockFile=$4
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: dconf_lock 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_lockfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/${_key}/${_setting}$" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/${_db}/locks"
mkdir -p "${LOCKSFOLDER}"
if [[ -z "${LOCKFILES}" ]]
then
echo "/${_key}/${_setting}" >> "/etc/dconf/db/${_db}/locks/${_lockFile}"
fi
}
include_dconf_settings
dconf_settings 'org/gnome/login-screen' 'disable-restart-buttons' 'true' 'gdm.d' '00-security-settings'
dconf_lock 'org/gnome/login-screen' 'disable-restart-buttons' 'gdm.d' '00-security-settings-lock'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
- name: "Disable the GNOME3 Login Restart and Shutdown Buttons"
ini_file:
dest: /etc/dconf/db/gdm.d/00-security-settings
section: org/gnome/login-screen
option: disable-restart-buttons
value: "true"
create: yes
tags:
- dconf_gnome_disable_restart_shutdown
- high_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80107-6
- NIST-800-53-AC-6
- NIST-800-171-3.1.2
- DISA-STIG-RHEL-07-TBD
- name: "Prevent user modification of GNOME disablement of Login Restart and Shutdown Buttons"
lineinfile:
path: /etc/dconf/db/gdm.d/locks/00-security-settings-lock
regexp: '^/org/gnome/login-screen/disable-restart-buttons'
line: '/org/gnome/login-screen/disable-restart-buttons'
create: yes
tags:
- dconf_gnome_disable_restart_shutdown
- high_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80107-6
- NIST-800-53-AC-6
- NIST-800-171-3.1.2
- DISA-STIG-RHEL-07-TBD
|
Disable GDM Automatic Login
[ref]ruleThe GNOME Display Manager (GDM) can allow users to automatically login without
user interaction or credentials. User should always be required to authenticate themselves
to the system that they are authorized to use. To disable user ability to automatically
login to the system, set the AutomaticLoginEnable to false in the
[daemon] section in /etc/gdm/custom.conf . For example:
[daemon]
AutomaticLoginEnable=false Rationale:Failure to restrict system access to authenticated users negatively impacts operating
system security. Remediation Shell script: (show)
if rpm --quiet -q gdm
then
if ! grep -q "^AutomaticLoginEnable=" /etc/gdm/custom.conf
then
sed -i "/^\[daemon\]/a \
AutomaticLoginEnable=False" /etc/gdm/custom.conf
else
sed -i "s/^AutomaticLoginEnable=.*/AutomaticLoginEnable=False/g" /etc/gdm/custom.conf
fi
fi
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
- name: "Disable GDM Automatic Login"
ini_file:
dest: /etc/gdm/custom.conf
section: daemon
option: AutomaticLoginEnable
value: "false"
no_extra_spaces: yes
create: yes
tags:
- gnome_gdm_disable_automatic_login
- high_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80104-3
- NIST-800-53-CM-6(b)
- NIST-800-171-3.1.1
- DISA-STIG-RHEL-07-010440
|
Set the GNOME3 Login Number of Failures
[ref]ruleIn the default graphical environment, the GNOME3 login
screen and be configured to restart the authentication process after
a configured number of attempts. This can be configured by setting
allowed-failures to 3 or less.
To enable, add or edit allowed-failures to
/etc/dconf/db/gdm.d/00-security-settings . For example:
[org/gnome/login-screen]
allowed-failures=3
Once the setting has been added, add a lock to
/etc/dconf/db/gdm.d/locks/00-security-settings-lock to prevent user modification.
For example:
/org/gnome/login-screen/allowed-failures
After the settings have been set, run dconf update .Rationale:Setting the password retry prompts that are permitted on a per-session basis to a low value
requires some software, such as SSH, to re-connect. This can slow down and
draw additional attention to some types of password-guessing attacks. Remediation Shell script: (show)
function include_dconf_settings {
:
}
# Function to configure DConf settings for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_settings 'org/gnome/login-screen' 'banner-message-enable' 'true' 'local.d' '10-banner'
#
function dconf_settings {
local _path=$1 _key=$2 _value=$3 _db=$4 _settingFile=$5
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: dconf_settings 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_settingsfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
SETTINGSFILES=($(grep -r "\[${_path}]" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1))
DCONFFILE="/etc/dconf/db/${_db}/${_settingFile}"
DBDIR="/etc/dconf/db/${_db}"
mkdir -p "${DBDIR}"
if [[ -z "${SETTINGSFILES[@]}" ]]
then
[ ! -z ${DCONFFILE} ] || $(echo "" >> ${DCONFFILE})
echo "[${_path}]" >> ${DCONFFILE}
echo "${_key}=${_value}" >> ${DCONFFILE}
else
if grep -q "${_key}" ${SETTINGSFILES[@]}
then
sed -i "s/${_key}\s*=\s*.*/${_key}=${_value}/g" ${SETTINGSFILES[@]}
else
sed -i "\|\[${_path}]|a\\${_key}=${_value}" ${SETTINGSFILES[@]}
fi
fi
}
# Function to configure DConf locks for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_lock 'org/gnome/login-screen' 'banner-message-enable' 'local.d' 'banner'
#
function dconf_lock {
local _key=$1 _setting=$2 _db=$3 _lockFile=$4
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: dconf_lock 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_lockfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/${_key}/${_setting}$" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/${_db}/locks"
mkdir -p "${LOCKSFOLDER}"
if [[ -z "${LOCKFILES}" ]]
then
echo "/${_key}/${_setting}" >> "/etc/dconf/db/${_db}/locks/${_lockFile}"
fi
}
include_dconf_settings
dconf_settings 'org/gnome/login-screen' 'allowed-failures' "3" 'gdm.d' '00-security-settings'
dconf_lock 'org/gnome/login-screen' 'allowed-failures' 'gdm.d' '00-security-settings-lock'
|
Disable the GNOME3 Login User List
[ref]ruleIn the default graphical environment, users logging
directly into the system are greeted with a login screen that displays
all known users. This functionality should be disabled by setting
disable-user-list to true .
To disable, add or edit disable-user-list to
/etc/dconf/db/gdm.d/00-security-settings . For example:
[org/gnome/login-screen]
disable-user-list=true
Once the setting has been added, add a lock to
/etc/dconf/db/gdm.d/locks/00-security-settings-lock to prevent user modification.
For example:
/org/gnome/login-screen/disable-user-list
After the settings have been set, run dconf update .Rationale:Leaving the user list enabled is a security risk since it allows anyone
with physical access to the system to quickly enumerate known user accounts
without logging in. Remediation Shell script: (show)
function include_dconf_settings {
:
}
# Function to configure DConf settings for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_settings 'org/gnome/login-screen' 'banner-message-enable' 'true' 'local.d' '10-banner'
#
function dconf_settings {
local _path=$1 _key=$2 _value=$3 _db=$4 _settingFile=$5
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: dconf_settings 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_settingsfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
SETTINGSFILES=($(grep -r "\[${_path}]" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1))
DCONFFILE="/etc/dconf/db/${_db}/${_settingFile}"
DBDIR="/etc/dconf/db/${_db}"
mkdir -p "${DBDIR}"
if [[ -z "${SETTINGSFILES[@]}" ]]
then
[ ! -z ${DCONFFILE} ] || $(echo "" >> ${DCONFFILE})
echo "[${_path}]" >> ${DCONFFILE}
echo "${_key}=${_value}" >> ${DCONFFILE}
else
if grep -q "${_key}" ${SETTINGSFILES[@]}
then
sed -i "s/${_key}\s*=\s*.*/${_key}=${_value}/g" ${SETTINGSFILES[@]}
else
sed -i "\|\[${_path}]|a\\${_key}=${_value}" ${SETTINGSFILES[@]}
fi
fi
}
# Function to configure DConf locks for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_lock 'org/gnome/login-screen' 'banner-message-enable' 'local.d' 'banner'
#
function dconf_lock {
local _key=$1 _setting=$2 _db=$3 _lockFile=$4
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: dconf_lock 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_lockfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/${_key}/${_setting}$" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/${_db}/locks"
mkdir -p "${LOCKSFOLDER}"
if [[ -z "${LOCKFILES}" ]]
then
echo "/${_key}/${_setting}" >> "/etc/dconf/db/${_db}/locks/${_lockFile}"
fi
}
include_dconf_settings
dconf_settings 'org/gnome/login-screen' 'disable-user-list' 'true' 'gdm.d' '00-security-settings'
dconf_lock 'org/gnome/login-screen' 'disable-user-list' 'gdm.d' '00-security-settings-lock'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
- name: "Disable the GNOME3 Login User List"
ini_file:
dest: /etc/dconf/db/gdm.d/00-security-settings
section: org/gnome/login-screen
option: disable-user-list
value: "true"
create: yes
tags:
- dconf_gnome_disable_user_list
- medium_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80106-8
- NIST-800-53-AC-23
- name: "Prevent user modification of GNOME3 disablement of Login User List"
lineinfile:
path: /etc/dconf/db/gdm.d/locks/00-security-settings-lock
regexp: '^/org/gnome/login-screen/disable-user-list'
line: '/org/gnome/login-screen/disable-user-list'
create: yes
tags:
- dconf_gnome_disable_user_list
- medium_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80106-8
- NIST-800-53-AC-23
|
Disable GDM Guest Login
[ref]ruleThe GNOME Display Manager (GDM) can allow users to login without credentials
which can be useful for public kiosk scenarios. Allowing users to login without credentials
or "guest" account access has inherent security risks and should be disabled. To do disable
timed logins or guest account access, set the TimedLoginEnable to false in
the [daemon] section in /etc/gdm/custom.conf . For example:
[daemon]
TimedLoginEnable=false Rationale:Failure to restrict system access to authenticated users negatively impacts operating
system security. Remediation Shell script: (show)
if rpm --quiet -q gdm
then
if ! grep -q "^TimedLoginEnable=" /etc/gdm/custom.conf
then
sed -i "/^\[daemon\]/a \
TimedLoginEnable=False" /etc/gdm/custom.conf
else
sed -i "s/^TimedLoginEnable=.*/TimedLoginEnable=False/g" /etc/gdm/custom.conf
fi
fi
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
- name: "Disable GDM Guest Login"
ini_file:
dest: /etc/gdm/custom.conf
section: daemon
option: TimedLoginEnable
value: "false"
no_extra_spaces: yes
create: yes
tags:
- gnome_gdm_disable_guest_login
- high_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80105-0
- NIST-800-53-CM-6(b)
- NIST-800-171-3.1.1
- DISA-STIG-RHEL-07-010450
|
GNOME Network Settings
[ref]groupGNOME network settings that apply to the graphical interface. |
contains 2 rules |
Disable WIFI Network Connection Creation in GNOME3
[ref]ruleGNOME allows users to create ad-hoc wireless connections through the
NetworkManager applet. Wireless connections should be disabled by
adding or setting disable-wifi-create to true in
/etc/dconf/db/local.d/00-security-settings . For example:
[org/gnome/nm-applet]
disable-wifi-create=true
Once the settings have been added, add a lock to
/etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification.
For example:
/org/gnome/nm-applet/disable-wifi-create
After the settings have been set, run dconf update .Rationale:Wireless network connections should not be allowed to be configured by general
users on a given system as it could open the system to backdoor attacks. Remediation Shell script: (show)
function include_dconf_settings {
:
}
# Function to configure DConf settings for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_settings 'org/gnome/login-screen' 'banner-message-enable' 'true' 'local.d' '10-banner'
#
function dconf_settings {
local _path=$1 _key=$2 _value=$3 _db=$4 _settingFile=$5
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: dconf_settings 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_settingsfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
SETTINGSFILES=($(grep -r "\[${_path}]" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1))
DCONFFILE="/etc/dconf/db/${_db}/${_settingFile}"
DBDIR="/etc/dconf/db/${_db}"
mkdir -p "${DBDIR}"
if [[ -z "${SETTINGSFILES[@]}" ]]
then
[ ! -z ${DCONFFILE} ] || $(echo "" >> ${DCONFFILE})
echo "[${_path}]" >> ${DCONFFILE}
echo "${_key}=${_value}" >> ${DCONFFILE}
else
if grep -q "${_key}" ${SETTINGSFILES[@]}
then
sed -i "s/${_key}\s*=\s*.*/${_key}=${_value}/g" ${SETTINGSFILES[@]}
else
sed -i "\|\[${_path}]|a\\${_key}=${_value}" ${SETTINGSFILES[@]}
fi
fi
}
# Function to configure DConf locks for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_lock 'org/gnome/login-screen' 'banner-message-enable' 'local.d' 'banner'
#
function dconf_lock {
local _key=$1 _setting=$2 _db=$3 _lockFile=$4
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: dconf_lock 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_lockfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/${_key}/${_setting}$" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/${_db}/locks"
mkdir -p "${LOCKSFOLDER}"
if [[ -z "${LOCKFILES}" ]]
then
echo "/${_key}/${_setting}" >> "/etc/dconf/db/${_db}/locks/${_lockFile}"
fi
}
include_dconf_settings
dconf_settings 'org/gnome/nm-applet' 'disable-wifi-create' 'true' 'local.d' '00-security-settings'
dconf_lock 'org/gnome/nm-applet' 'disable-wifi-create' 'local.d' '00-security-settings-lock'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
- name: "Disable WiFi Network Connection Creation in GNOME3"
ini_file:
dest: /etc/dconf/db/local.d/00-security-settings
section: org/gnome/nm-applet
option: disable-wifi-create
value: "true"
create: yes
tags:
- dconf_gnome_disable_wifi_create
- medium_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80118-3
- NIST-800-171-3.1.16
- name: "Prevent user modification of GNOME3 disablement of WiFi"
lineinfile:
path: /etc/dconf/db/local.d/locks/00-security-settings-lock
regexp: '^/org/gnome/nm-applet/disable-wifi-create'
line: '/org/gnome/nm-applet/disable-wifi-create'
create: yes
tags:
- dconf_gnome_disable_wifi_create
- medium_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80118-3
- NIST-800-171-3.1.16
|
Disable WIFI Network Notification in GNOME3
[ref]ruleBy default, GNOME disables WIFI notification. This should be permanently set
so that users do not connect to a wireless network when the system finds one.
While useful for mobile devices, this setting should be disabled for all other systems.
To configure the system to disable the WIFI notication, add or set
suppress-wireless-networks-available to true in
/etc/dconf/db/local.d/00-security-settings . For example:
[org/gnome/nm-applet]
suppress-wireless-networks-available=true
Once the settings have been added, add a lock to
/etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification.
For example:
/org/gnome/nm-applet/suppress-wireless-networks-available
After the settings have been set, run dconf update .Rationale:Wireless network connections should not be allowed to be configured by general
users on a given system as it could open the system to backdoor attacks. Remediation Shell script: (show)
function include_dconf_settings {
:
}
# Function to configure DConf settings for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_settings 'org/gnome/login-screen' 'banner-message-enable' 'true' 'local.d' '10-banner'
#
function dconf_settings {
local _path=$1 _key=$2 _value=$3 _db=$4 _settingFile=$5
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: dconf_settings 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_settingsfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
SETTINGSFILES=($(grep -r "\[${_path}]" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1))
DCONFFILE="/etc/dconf/db/${_db}/${_settingFile}"
DBDIR="/etc/dconf/db/${_db}"
mkdir -p "${DBDIR}"
if [[ -z "${SETTINGSFILES[@]}" ]]
then
[ ! -z ${DCONFFILE} ] || $(echo "" >> ${DCONFFILE})
echo "[${_path}]" >> ${DCONFFILE}
echo "${_key}=${_value}" >> ${DCONFFILE}
else
if grep -q "${_key}" ${SETTINGSFILES[@]}
then
sed -i "s/${_key}\s*=\s*.*/${_key}=${_value}/g" ${SETTINGSFILES[@]}
else
sed -i "\|\[${_path}]|a\\${_key}=${_value}" ${SETTINGSFILES[@]}
fi
fi
}
# Function to configure DConf locks for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_lock 'org/gnome/login-screen' 'banner-message-enable' 'local.d' 'banner'
#
function dconf_lock {
local _key=$1 _setting=$2 _db=$3 _lockFile=$4
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: dconf_lock 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_lockfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/${_key}/${_setting}$" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/${_db}/locks"
mkdir -p "${LOCKSFOLDER}"
if [[ -z "${LOCKFILES}" ]]
then
echo "/${_key}/${_setting}" >> "/etc/dconf/db/${_db}/locks/${_lockFile}"
fi
}
include_dconf_settings
dconf_settings 'org/gnome/nm-applet' 'suppress-wireless-networks-available' 'true' 'local.d' '00-security-settings'
dconf_lock 'org/gnome/nm-applet' 'suppress-wireless-networks-available' 'local.d' '00-security-settings-lock'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
- name: "Disable WiFi Network Notification in GNOME3"
ini_file:
dest: /etc/dconf/db/local.d/00-security-settings
section: org/gnome/nm-applet
option: suppress-wireless-networks-available
value: "true"
create: yes
tags:
- dconf_gnome_disable_wifi_notification
- medium_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80119-1
- NIST-800-171-3.1.16
- name: "Prevent user modification of GNOME3 disablement of WiFi"
lineinfile:
path: /etc/dconf/db/local.d/locks/00-security-settings-lock
regexp: '^/org/gnome/nm-applet/suppress-wireless-networks-available'
line: '/org/gnome/nm-applet/suppress-wireless-networks-available'
create: yes
tags:
- dconf_gnome_disable_wifi_notification
- medium_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-80119-1
- NIST-800-171-3.1.16
|
GNOME Remote Access Settings
[ref]groupGNOME remote access settings that apply to the graphical interface. |
contains 2 rules |
Require Encryption for Remote Access in GNOME3
[ref]ruleBy default, GNOME requires encryption when using Vino for remote access.
To prevent remote access encryption from being disabled, add or set
require-encryption to true in
/etc/dconf/db/local.d/00-security-settings . For example:
[org/gnome/Vino]
require-encryption=true
Once the settings have been added, add a lock to
/etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification.
For example:
/org/gnome/Vino/require-encryption
After the settings have been set, run dconf update .Rationale:Open X displays allow an attacker to capture keystrokes and to execute commands
remotely. Remediation Shell script: (show)
function include_dconf_settings {
:
}
# Function to configure DConf settings for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_settings 'org/gnome/login-screen' 'banner-message-enable' 'true' 'local.d' '10-banner'
#
function dconf_settings {
local _path=$1 _key=$2 _value=$3 _db=$4 _settingFile=$5
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: dconf_settings 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_settingsfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
SETTINGSFILES=($(grep -r "\[${_path}]" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1))
DCONFFILE="/etc/dconf/db/${_db}/${_settingFile}"
DBDIR="/etc/dconf/db/${_db}"
mkdir -p "${DBDIR}"
if [[ -z "${SETTINGSFILES[@]}" ]]
then
[ ! -z ${DCONFFILE} ] || $(echo "" >> ${DCONFFILE})
echo "[${_path}]" >> ${DCONFFILE}
echo "${_key}=${_value}" >> ${DCONFFILE}
else
if grep -q "${_key}" ${SETTINGSFILES[@]}
then
sed -i "s/${_key}\s*=\s*.*/${_key}=${_value}/g" ${SETTINGSFILES[@]}
else
sed -i "\|\[${_path}]|a\\${_key}=${_value}" ${SETTINGSFILES[@]}
fi
fi
}
# Function to configure DConf locks for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_lock 'org/gnome/login-screen' 'banner-message-enable' 'local.d' 'banner'
#
function dconf_lock {
local _key=$1 _setting=$2 _db=$3 _lockFile=$4
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: dconf_lock 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_lockfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/${_key}/${_setting}$" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/${_db}/locks"
mkdir -p "${LOCKSFOLDER}"
if [[ -z "${LOCKFILES}" ]]
then
echo "/${_key}/${_setting}" >> "/etc/dconf/db/${_db}/locks/${_lockFile}"
fi
}
include_dconf_settings
dconf_settings 'org/gnome/Vino' 'require-encryption' 'true' 'local.d' '00-security-settings'
dconf_lock 'org/gnome/Vino' 'require-encryption' 'local.d' '00-security-settings-lock'
|
Require Credential Prompting for Remote Access in GNOME3
[ref]ruleBy default, GNOME does not require credentials when using Vino for
remote access. To configure the system to require remote credentials, add or set
authentication-methods to ['vnc'] in
/etc/dconf/db/local.d/00-security-settings . For example:
[org/gnome/Vino]
authentication-methods=['vnc']
Once the settings have been added, add a lock to
/etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification.
For example:
/org/gnome/Vino/authentication-methods
After the settings have been set, run dconf update .Rationale:Username and password prompting is required for remote access. Otherwise, non-authorized
and nefarious users can access the system freely. Remediation Shell script: (show)
function include_dconf_settings {
:
}
# Function to configure DConf settings for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_settings 'org/gnome/login-screen' 'banner-message-enable' 'true' 'local.d' '10-banner'
#
function dconf_settings {
local _path=$1 _key=$2 _value=$3 _db=$4 _settingFile=$5
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: dconf_settings 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_settingsfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
SETTINGSFILES=($(grep -r "\[${_path}]" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1))
DCONFFILE="/etc/dconf/db/${_db}/${_settingFile}"
DBDIR="/etc/dconf/db/${_db}"
mkdir -p "${DBDIR}"
if [[ -z "${SETTINGSFILES[@]}" ]]
then
[ ! -z ${DCONFFILE} ] || $(echo "" >> ${DCONFFILE})
echo "[${_path}]" >> ${DCONFFILE}
echo "${_key}=${_value}" >> ${DCONFFILE}
else
if grep -q "${_key}" ${SETTINGSFILES[@]}
then
sed -i "s/${_key}\s*=\s*.*/${_key}=${_value}/g" ${SETTINGSFILES[@]}
else
sed -i "\|\[${_path}]|a\\${_key}=${_value}" ${SETTINGSFILES[@]}
fi
fi
}
# Function to configure DConf locks for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_lock 'org/gnome/login-screen' 'banner-message-enable' 'local.d' 'banner'
#
function dconf_lock {
local _key=$1 _setting=$2 _db=$3 _lockFile=$4
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: dconf_lock 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_lockfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/${_key}/${_setting}$" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/${_db}/locks"
mkdir -p "${LOCKSFOLDER}"
if [[ -z "${LOCKFILES}" ]]
then
echo "/${_key}/${_setting}" >> "/etc/dconf/db/${_db}/locks/${_lockFile}"
fi
}
include_dconf_settings
dconf_settings 'org/gnome/Vino' 'authentication-methods' "['vnc']" 'local.d' '00-security-settings'
dconf_lock 'org/gnome/Vino' 'authentication-methods' 'local.d' '00-security-settings-lock'
|
Configure GNOME3 DConf User Profile
[ref]ruleBy default, DConf provides a standard user profile. This profile contains a list
of DConf configuration databases. The user profile and database always take the
highest priority. As such the DConf User profile should always exist and be
configured correctly.
To make sure that the user profile is configured correctly, the /etc/dconf/profile/user should be set as follows:
user-db:user
system-db:local
system-db:site
system-db:distro
Rationale:Failure to have a functional DConf profile prevents GNOME3 configuration settings
from being enforced for all users and allows various security risks. |
Configure Syslog
[ref]groupThe syslog service has been the default Unix logging mechanism for
many years. It has a number of downsides, including inconsistent log format,
lack of authentication for received messages, and lack of authentication,
encryption, or reliable transport for messages sent over a network. However,
due to its long history, syslog is a de facto standard which is supported by
almost all Unix applications.
In Red Hat Enterprise Linux 7, rsyslog has replaced ksyslogd as the
syslog daemon of choice, and it includes some additional security features
such as reliable, connection-oriented (i.e. TCP) transmission of logs, the
option to log to database formats, and the encryption of log data en route to
a central logging server.
This section discusses how to configure rsyslog for
best effect, and how to use tools provided with the system to maintain and
monitor logs. |
contains 3 rules |
Rsyslog Logs Sent To Remote Host
[ref]groupIf system logs are to be useful in detecting malicious
activities, it is necessary to send logs to a remote server. An
intruder who has compromised the root account on a system may
delete the log entries which indicate that the system was attacked
before they are seen by an administrator.
However, it is recommended that logs be stored on the local
host in addition to being sent to the loghost, especially if
rsyslog has been configured to use the UDP protocol to send
messages over a network. UDP does not guarantee reliable delivery,
and moderately busy sites will lose log messages occasionally,
especially in periods of high traffic which may be the result of an
attack. In addition, remote rsyslog messages are not
authenticated in any way by default, so it is easy for an attacker to
introduce spurious messages to the central log server. Also, some
problems cause loss of network connectivity, which will prevent the
sending of messages to the central server. For all of these reasons, it is
better to store log messages both centrally and on each host, so
that they can be correlated if necessary. |
contains 1 rule |
Ensure Logs Sent To Remote Host
[ref]ruleTo configure rsyslog to send logs to a remote log server,
open /etc/rsyslog.conf and read and understand the last section of the file,
which describes the multiple directives necessary to activate remote
logging.
Along with these other directives, the system can be configured
to forward its logs to a particular log server by
adding or correcting one of the following lines,
substituting loghost.example.com appropriately.
The choice of protocol depends on the environment of the system;
although TCP and RELP provide more reliable message delivery,
they may not be supported in all environments.
To use UDP for log message delivery:
*.* @loghost.example.com
To use TCP for log message delivery:
*.* @@loghost.example.com
To use RELP for log message delivery:
*.* :omrelp:loghost.example.com
There must be a resolvable DNS CNAME or Alias record set to "(N/A)" for logs to be sent correctly to the centralized logging utility.Rationale:A log server (loghost) receives syslog messages from one or more
systems. This data can be used as an additional log source in the event a
system is compromised and its local logs are suspect. Forwarding log messages
to a remote loghost also provides system administrators with a centralized
place to view the status of multiple hosts within the enterprise. References:
FAU_GEN.1.1.c, SV-86833r1_rule, 4.2.1.4, CCI-000366, CCI-001348, CCI-000136, CCI-001851, 164.308(a)(1)(ii)(D), 164.308(a)(5)(ii)(B), 164.308(a)(5)(ii)(C), 164.308(a)(6)(ii), 164.308(a)(8), 164.310(d)(2)(iii), 164.312(b), 164.314(a)(2)(i)(C), 164.314(a)(2)(iii), A.12.3.1, AU-3(2), AU-4(1), AU-9, SRG-OS-000480-GPOS-00227 Remediation Shell script: (show)
rsyslog_remote_loghost_address="(N/A)"
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/rsyslog.conf' '^\*\.\*' "@@$rsyslog_remote_loghost_address" 'CCE-27343-3' '%s %s'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: XCCDF Value rsyslog_remote_loghost_address # promote to variable
set_fact:
rsyslog_remote_loghost_address: (N/A)
tags:
- always
- name: "Set rsyslog remote loghost"
lineinfile:
dest: /etc/rsyslog.conf
regexp: "^\\*\\.\\*"
line: "*.* @@{{ rsyslog_remote_loghost_address }}"
create: yes
tags:
- rsyslog_remote_loghost
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27343-3
- NIST-800-53-AU-3(2)
- NIST-800-53-AU-4(1)
- NIST-800-53-AU-9
- DISA-STIG-RHEL-07-031000
|
Ensure Proper Configuration of Log Files
[ref]groupThe file /etc/rsyslog.conf controls where log message are written.
These are controlled by lines called rules, which consist of a
selector and an action.
These rules are often customized depending on the role of the system, the
requirements of the environment, and whatever may enable
the administrator to most effectively make use of log data.
The default rules in Red Hat Enterprise Linux 7 are:
*.info;mail.none;authpriv.none;cron.none /var/log/messages
authpriv.* /var/log/secure
mail.* -/var/log/maillog
cron.* /var/log/cron
*.emerg *
uucp,news.crit /var/log/spooler
local7.* /var/log/boot.log
See the man page rsyslog.conf(5) for more information.
Note that the rsyslog daemon can be configured to use a timestamp format that
some log processing programs may not understand. If this occurs,
edit the file /etc/rsyslog.conf and add or edit the following line:
$ ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat |
contains 1 rule |
Ensure cron Is Logging To Rsyslog
[ref]ruleCron logging must be implemented to spot intrusions or trace
cron job status. If cron is not logging to rsyslog , it
can be implemented by adding the following to the RULES section of
/etc/rsyslog.conf :
cron.* /var/log/cron Rationale:Cron logging can be used to trace the successful or unsuccessful execution
of cron jobs. It can also be used to spot intrusions into the use of the cron
facility by unauthorized and malicious users. Remediation Shell script: (show)
if ! grep "^\s*cron\.\*\s*/var/log/cron$" /etc/rsyslog.conf /etc/rsyslog.d/*.conf; then
echo "cron.* /var/log/cron\n" >> /etc/rsyslog.d/cron.conf
fi
|
Configure <tt>rsyslogd</tt> to Accept Remote Messages If Acting as a Log Server
[ref]groupBy default, rsyslog does not listen over the network
for log messages. If needed, modules can be enabled to allow
the rsyslog daemon to receive messages from other systems and for the system
thus to act as a log server.
If the system is not a log server, then lines concerning these modules
should remain commented out.
|
contains 1 rule |
Ensure rsyslog Does Not Accept Remote Messages Unless Acting As Log Server
[ref]ruleThe rsyslog daemon should not accept remote messages
unless the system acts as a log server.
To ensure that it is not listening on the network, ensure the following lines are
not found in /etc/rsyslog.conf :
$ModLoad imtcp
$InputTCPServerRun port
$ModLoad imudp
$UDPServerRun port
$ModLoad imrelp
$InputRELPServerRun port Rationale:Any process which receives messages from the network incurs some risk
of receiving malicious messages. This risk can be eliminated for
rsyslog by configuring it not to listen on the network. |
Network Configuration and Firewalls
[ref]groupMost systems must be connected to a network of some
sort, and this brings with it the substantial risk of network
attack. This section discusses the security impact of decisions
about networking which must be made when configuring a system.
This section also discusses firewalls, network access
controls, and other network security frameworks, which allow
system-level rules to be written that can limit an attackers' ability
to connect to your system. These rules can specify that network
traffic should be allowed or denied from certain IP addresses,
hosts, and networks. The rules can also specify which of the
system's network services are available to particular hosts or
networks. |
contains 35 rules |
firewalld
[ref]groupThe dynamic firewall daemon firewalld provides a
dynamically managed firewall with support for network “zones” to assign
a level of trust to a network and its associated connections and interfaces.
It has support for IPv4 and IPv6 firewall settings. It supports Ethernet
bridges and has a separation of runtime and permanent configuration options.
It also has an interface for services or applications to add firewall rules
directly.
A graphical configuration tool, firewall-config , is used to configure
firewalld , which in turn uses iptables tool to communicate
with Netfilter in the kernel which implements packet filtering.
The firewall service provided by firewalld is dynamic rather than
static because changes to the configuration can be made at anytime and are
immediately implemented. There is no need to save or apply the changes. No
unintended disruption of existing network connections occurs as no part of
the firewall has to be reloaded. |
contains 2 rules |
Strengthen the Default Ruleset
[ref]groupThe default rules can be strengthened. The system
scripts that activate the firewall rules expect them to be defined
in configuration files under the /etc/firewalld/services
and /etc/firewalld/zones directories.
The following recommendations describe how to strengthen the
default ruleset configuration file. An alternative to editing this
configuration file is to create a shell script that makes calls to
the firewall-cmd program to load in rules under the /etc/firewalld/services
and /etc/firewalld/zones directories.
Instructions apply to both unless otherwise noted. Language and address
conventions for regular firewalld rules are used throughout this section. |
contains 1 rule |
Set Default firewalld Zone for Incoming Packets
[ref]ruleTo set the default zone to drop for
the built-in default zone which processes incoming IPv4 and IPv6 packets,
modify the following line in
/etc/firewalld/firewalld.conf to be:
DefaultZone=drop Rationale:In firewalld the default zone is applied only after all
the applicable rules in the table are examined for a match. Setting the
default zone to drop implements proper design for a firewall, i.e.
any packets which are not explicitly permitted should not be
accepted. |
Inspect and Activate Default firewalld Rules
[ref]groupFirewalls can be used to separate networks into different zones
based on the level of trust the user has decided to place on the devices and
traffic within that network. NetworkManager informs firewalld to which
zone an interface belongs. An interface's assigned zone can be changed by
NetworkManager or via the firewall-config tool.
The zone settings in /etc/firewalld/ are a range of preset settings
which can be quickly applied to a network interface. These are the zones
provided by firewalld sorted according to the default trust level of the
zones from untrusted to trusted:
drop
Any incoming network packets are dropped, there is no
reply. Only outgoing network connections are possible. block
Any incoming network connections are rejected with an
icmp-host-prohibited message for IPv4 and icmp6-adm-prohibited
for IPv6. Only network connections initiated from within the system are
possible. public
For use in public areas. You do not trust the other
computers on the network to not harm your computer. Only selected incoming
connections are accepted. external
For use on external networks with masquerading enabled
especially for routers. You do not trust the other computers on the network to
not harm your computer. Only selected incoming connections are accepted. dmz
For computers in your demilitarized zone that are
publicly-accessible with limited access to your internal network. Only selected
incoming connections are accepted. work
For use in work areas. You mostly trust the other computers
on networks to not harm your computer. Only selected incoming connections are
accepted. home
For use in home areas. You mostly trust the other computers
on networks to not harm your computer. Only selected incoming connections are
accepted. internal
For use on internal networks. You mostly trust the
other computers on the networks to not harm your computer. Only selected
incoming connections are accepted. trusted
All network connections are accepted.
It is possible to designate one of these zones to be the default zone. When
interface connections are added to NetworkManager , they are assigned
to the default zone. On installation, the default zone in firewalld is set to
be the public zone.
To find out all the settings of a zone, for example the public zone,
enter the following command as root:
# firewall-cmd --zone=public --list-all
Example output of this command might look like the following:
# firewall-cmd --zone=public --list-all
public
interfaces:
services: mdns dhcpv6-client ssh
ports:
forward-ports:
icmp-blocks: source-quench
To view the network zones currently active, enter the following command as root:
# firewall-cmd --get-service
The following listing displays the result of this command on common Red Hat
Enterprise Linux 7 Server system:
# firewall-cmd --get-service
amanda-client bacula bacula-client dhcp dhcpv6 dhcpv6-client dns ftp
high-availability http https imaps ipp ipp-client ipsec kerberos kpasswd
ldap ldaps libvirt libvirt-tls mdns mountd ms-wbt mysql nfs ntp openvpn
pmcd pmproxy pmwebapi pmwebapis pop3s postgresql proxy-dhcp radius rpc-bind
samba samba-client smtp ssh telnet tftp tftp-client transmission-client
vnc-server wbem-https
Finally to view the network zones that will be active after the next firewalld
service reload, enter the following command as root:
# firewall-cmd --get-service --permanent |
contains 1 rule |
Verify firewalld Enabled
[ref]rule
The firewalld service can be enabled with the following command:
$ sudo systemctl enable firewalld.service
Rationale:Access control methods provide the ability to enhance system security posture
by restricting services and known good IP addresses and address ranges. This
prevents connections from unknown hosts and protocols. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
# Function to enable/disable and start/stop services on RHEL and Fedora systems.
#
# Example Call(s):
#
# service_command enable bluetooth
# service_command disable bluetooth.service
#
# Using xinetd:
# service_command disable rsh.socket xinetd=rsh
#
function service_command {
# Load function arguments into local variables
local service_state=$1
local service=$2
local xinetd=$(echo $3 | cut -d'=' -f2)
# Check sanity of the input
if [ $# -lt "2" ]
then
echo "Usage: service_command 'enable/disable' 'service_name.service'"
echo
echo "To enable or disable xinetd services add \'xinetd=service_name\'"
echo "as the last argument"
echo "Aborting."
exit 1
fi
# If systemctl is installed, use systemctl command; otherwise, use the service/chkconfig commands
if [ -f "/usr/bin/systemctl" ] ; then
service_util="/usr/bin/systemctl"
else
service_util="/sbin/service"
chkconfig_util="/sbin/chkconfig"
fi
# If disable is not specified in arg1, set variables to enable services.
# Otherwise, variables are to be set to disable services.
if [ "$service_state" != 'disable' ] ; then
service_state="enable"
service_operation="start"
chkconfig_state="on"
else
service_state="disable"
service_operation="stop"
chkconfig_state="off"
fi
# If chkconfig_util is not empty, use chkconfig/service commands.
if [ "x$chkconfig_util" != x ] ; then
$service_util $service $service_operation
$chkconfig_util --level 0123456 $service $chkconfig_state
else
$service_util $service_operation $service
$service_util $service_state $service
# The service may not be running because it has been started and failed,
# so let's reset the state so OVAL checks pass.
# Service should be 'inactive', not 'failed' after reboot though.
$service_util reset-failed $service
fi
# Test if local variable xinetd is empty using non-bashism.
# If empty, then xinetd is not being used.
if [ "x$xinetd" != x ] ; then
grep -qi disable /etc/xinetd.d/$xinetd && \
if [ "$service_operation" = 'disable' ] ; then
sed -i "s/disable.*/disable = no/gI" /etc/xinetd.d/$xinetd
else
sed -i "s/disable.*/disable = yes/gI" /etc/xinetd.d/$xinetd
fi
fi
}
service_command enable firewalld
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: Enable service firewalld
service:
name="{{item}}"
enabled="yes"
state="started"
with_items:
- firewalld
tags:
- service_firewalld_enabled
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-27361-5
- NIST-800-53-CM-6(b)
- NIST-800-171-3.1.3
- NIST-800-171-3.4.7
- DISA-STIG-RHEL-07-040520
|
IPSec Support
[ref]groupSupport for Internet Protocol Security (IPsec)
is provided in Red Hat Enterprise Linux 7 with Libreswan. |
contains 1 rule |
Verify Any Configured IPSec Tunnel Connections
[ref]ruleLibreswan provides an implementation of IPsec
and IKE, which permits the creation of secure tunnels over
untrusted networks. As such, IPsec can be used to circumvent certain
network requirements such as filtering. Verify that if any IPsec connection
(conn ) configured in /etc/ipsec.conf and /etc/ipsec.d
exists is an approved organizational connection. Rationale:IP tunneling mechanisms can be used to bypass network filtering. |
The system includes support for Internet Protocol
version 6. A major and often-mentioned improvement over IPv4 is its
enormous increase in the number of available addresses. Another
important feature is its support for automatic configuration of
many network settings. |
contains 10 rules |
Configure IPv6 Settings if Necessary
[ref]groupA major feature of IPv6 is the extent to which systems
implementing it can automatically configure their networking
devices using information from the network. From a security
perspective, manually configuring important configuration
information is preferable to accepting it from the network
in an unauthenticated fashion. |
contains 8 rules |
Disable Automatic Configuration
[ref]groupDisable the system's acceptance of router
advertisements and redirects by adding or correcting the following
line in /etc/sysconfig/network (note that this does not disable
sending router solicitations):
IPV6_AUTOCONF=no |
contains 7 rules |
Configure Kernel Parameter for Accepting Source-Routed Packets for Interfaces By Default
[ref]rule
To set the runtime status of the net.ipv6.conf.default.accept_source_route kernel parameter,
run the following command:
$ sudo sysctl -w net.ipv6.conf.default.accept_source_route=0
If this is not the system's default value, add the following line to /etc/sysctl.conf :
net.ipv6.conf.default.accept_source_route = 0
Rationale:Source-routed packets allow the source of the packet to suggest routers
forward the packet along a different path than configured on the router, which can
be used to bypass network security measures. This requirement applies only to the
forwarding of source-routerd traffic, such as when IPv6 forwarding is enabled and
the system is functioning as a router.
Accepting source-routed packets in the IPv6 protocol has few legitimate
uses. It should be disabled unless it is absolutely required. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
sysctl_net_ipv6_conf_default_accept_source_route_value="(N/A)"
#
# Set runtime for net.ipv6.conf.default.accept_source_route
#
/sbin/sysctl -q -n -w net.ipv6.conf.default.accept_source_route=$sysctl_net_ipv6_conf_default_accept_source_route_value
#
# If net.ipv6.conf.default.accept_source_route present in /etc/sysctl.conf, change value to appropriate value
# else, add "net.ipv6.conf.default.accept_source_route = value" to /etc/sysctl.conf
#
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysctl.conf' '^net.ipv6.conf.default.accept_source_route' "$sysctl_net_ipv6_conf_default_accept_source_route_value" 'CCE-80335-1'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: XCCDF Value sysctl_net_ipv6_conf_default_accept_source_route_value # promote to variable
set_fact:
sysctl_net_ipv6_conf_default_accept_source_route_value: (N/A)
tags:
- always
- name: Ensure sysctl net.ipv6.conf.default.accept_source_route is set
sysctl:
name: net.ipv6.conf.default.accept_source_route
value: "{{ sysctl_net_ipv6_conf_default_accept_source_route_value }}"
state: present
reload: yes
tags:
- sysctl_net_ipv6_conf_default_accept_source_route
- medium_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-80335-1
- NIST-800-53-AC-4
- NIST-800-171-3.1.20
|
Disable Kernel Parameter for IPv6 Forwarding
[ref]rule
To set the runtime status of the net.ipv6.conf.all.forwarding kernel parameter,
run the following command:
$ sudo sysctl -w net.ipv6.conf.all.forwarding=0
If this is not the system's default value, add the following line to /etc/sysctl.conf :
net.ipv6.conf.all.forwarding = 0
Rationale:IP forwarding permits the kernel to forward packets from one network
interface to another. The ability to forward packets between two networks is
only appropriate for systems acting as routers. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
sysctl_net_ipv6_conf_all_forwarding_value="(N/A)"
#
# Set runtime for net.ipv6.conf.all.forwarding
#
/sbin/sysctl -q -n -w net.ipv6.conf.all.forwarding=$sysctl_net_ipv6_conf_all_forwarding_value
#
# If net.ipv6.conf.all.forwarding present in /etc/sysctl.conf, change value to appropriate value
# else, add "net.ipv6.conf.all.forwarding = value" to /etc/sysctl.conf
#
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysctl.conf' '^net.ipv6.conf.all.forwarding' "$sysctl_net_ipv6_conf_all_forwarding_value" 'CCE-80356-9'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: XCCDF Value sysctl_net_ipv6_conf_all_forwarding_value # promote to variable
set_fact:
sysctl_net_ipv6_conf_all_forwarding_value: (N/A)
tags:
- always
- name: Ensure sysctl net.ipv6.conf.all.forwarding is set
sysctl:
name: net.ipv6.conf.all.forwarding
value: "{{ sysctl_net_ipv6_conf_all_forwarding_value }}"
state: present
reload: yes
tags:
- sysctl_net_ipv6_conf_all_forwarding
- medium_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-80356-9
- NIST-800-53-CM-7
- NIST-800-53-SC-5
|
Configure Kernel Parameter for Accepting Source-Routed Packets for All Interfaces
[ref]rule
To set the runtime status of the net.ipv6.conf.all.accept_source_route kernel parameter,
run the following command:
$ sudo sysctl -w net.ipv6.conf.all.accept_source_route=0
If this is not the system's default value, add the following line to /etc/sysctl.conf :
net.ipv6.conf.all.accept_source_route = 0
Rationale:Source-routed packets allow the source of the packet to suggest routers
forward the packet along a different path than configured on the router, which can
be used to bypass network security measures. This requirement applies only to the
forwarding of source-routerd traffic, such as when IPv6 forwarding is enabled and
the system is functioning as a router.
Accepting source-routed packets in the IPv6 protocol has few legitimate
uses. It should be disabled unless it is absolutely required. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
sysctl_net_ipv6_conf_all_accept_source_route_value="(N/A)"
#
# Set runtime for net.ipv6.conf.all.accept_source_route
#
/sbin/sysctl -q -n -w net.ipv6.conf.all.accept_source_route=$sysctl_net_ipv6_conf_all_accept_source_route_value
#
# If net.ipv6.conf.all.accept_source_route present in /etc/sysctl.conf, change value to appropriate value
# else, add "net.ipv6.conf.all.accept_source_route = value" to /etc/sysctl.conf
#
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysctl.conf' '^net.ipv6.conf.all.accept_source_route' "$sysctl_net_ipv6_conf_all_accept_source_route_value" 'CCE-80179-5'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: XCCDF Value sysctl_net_ipv6_conf_all_accept_source_route_value # promote to variable
set_fact:
sysctl_net_ipv6_conf_all_accept_source_route_value: (N/A)
tags:
- always
- name: Ensure sysctl net.ipv6.conf.all.accept_source_route is set
sysctl:
name: net.ipv6.conf.all.accept_source_route
value: "{{ sysctl_net_ipv6_conf_all_accept_source_route_value }}"
state: present
reload: yes
tags:
- sysctl_net_ipv6_conf_all_accept_source_route
- medium_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-80179-5
- NIST-800-53-AC-4
- NIST-800-171-3.1.20
- DISA-STIG-RHEL-07-040830
|
Configure Accepting IPv6 Redirects By Default
[ref]rule
To set the runtime status of the net.ipv6.conf.all.accept_redirects kernel parameter,
run the following command:
$ sudo sysctl -w net.ipv6.conf.all.accept_redirects=0
If this is not the system's default value, add the following line to /etc/sysctl.conf :
net.ipv6.conf.all.accept_redirects = 0
Rationale:An illicit ICMP redirect message could result in a man-in-the-middle attack. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
sysctl_net_ipv6_conf_all_accept_redirects_value="(N/A)"
#
# Set runtime for net.ipv6.conf.all.accept_redirects
#
/sbin/sysctl -q -n -w net.ipv6.conf.all.accept_redirects=$sysctl_net_ipv6_conf_all_accept_redirects_value
#
# If net.ipv6.conf.all.accept_redirects present in /etc/sysctl.conf, change value to appropriate value
# else, add "net.ipv6.conf.all.accept_redirects = value" to /etc/sysctl.conf
#
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysctl.conf' '^net.ipv6.conf.all.accept_redirects' "$sysctl_net_ipv6_conf_all_accept_redirects_value" 'CCE-80182-9'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: XCCDF Value sysctl_net_ipv6_conf_all_accept_redirects_value # promote to variable
set_fact:
sysctl_net_ipv6_conf_all_accept_redirects_value: (N/A)
tags:
- always
- name: Ensure sysctl net.ipv6.conf.all.accept_redirects is set
sysctl:
name: net.ipv6.conf.all.accept_redirects
value: "{{ sysctl_net_ipv6_conf_all_accept_redirects_value }}"
state: present
reload: yes
tags:
- sysctl_net_ipv6_conf_all_accept_redirects
- medium_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-80182-9
- NIST-800-53-CM-7
- NIST-800-171-3.1.20
|
Configure Accepting IPv6 Router Advertisements
[ref]rule
To set the runtime status of the net.ipv6.conf.default.accept_ra kernel parameter,
run the following command:
$ sudo sysctl -w net.ipv6.conf.default.accept_ra=0
If this is not the system's default value, add the following line to /etc/sysctl.conf :
net.ipv6.conf.default.accept_ra = 0
Rationale:An illicit router advertisement message could result in a man-in-the-middle attack. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
sysctl_net_ipv6_conf_default_accept_ra_value="(N/A)"
#
# Set runtime for net.ipv6.conf.default.accept_ra
#
/sbin/sysctl -q -n -w net.ipv6.conf.default.accept_ra=$sysctl_net_ipv6_conf_default_accept_ra_value
#
# If net.ipv6.conf.default.accept_ra present in /etc/sysctl.conf, change value to appropriate value
# else, add "net.ipv6.conf.default.accept_ra = value" to /etc/sysctl.conf
#
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysctl.conf' '^net.ipv6.conf.default.accept_ra' "$sysctl_net_ipv6_conf_default_accept_ra_value" 'CCE-80181-1'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: XCCDF Value sysctl_net_ipv6_conf_default_accept_ra_value # promote to variable
set_fact:
sysctl_net_ipv6_conf_default_accept_ra_value: (N/A)
tags:
- always
- name: Ensure sysctl net.ipv6.conf.default.accept_ra is set
sysctl:
name: net.ipv6.conf.default.accept_ra
value: "{{ sysctl_net_ipv6_conf_default_accept_ra_value }}"
state: present
reload: yes
tags:
- sysctl_net_ipv6_conf_default_accept_ra
- unknown_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-80181-1
- NIST-800-53-CM-7
- NIST-800-171-3.1.20
|
Configure Accepting IPv6 Router Advertisements
[ref]rule
To set the runtime status of the net.ipv6.conf.all.accept_ra kernel parameter,
run the following command:
$ sudo sysctl -w net.ipv6.conf.all.accept_ra=0
If this is not the system's default value, add the following line to /etc/sysctl.conf :
net.ipv6.conf.all.accept_ra = 0
Rationale:An illicit router advertisement message could result in a man-in-the-middle attack. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
sysctl_net_ipv6_conf_all_accept_ra_value="(N/A)"
#
# Set runtime for net.ipv6.conf.all.accept_ra
#
/sbin/sysctl -q -n -w net.ipv6.conf.all.accept_ra=$sysctl_net_ipv6_conf_all_accept_ra_value
#
# If net.ipv6.conf.all.accept_ra present in /etc/sysctl.conf, change value to appropriate value
# else, add "net.ipv6.conf.all.accept_ra = value" to /etc/sysctl.conf
#
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysctl.conf' '^net.ipv6.conf.all.accept_ra' "$sysctl_net_ipv6_conf_all_accept_ra_value" 'CCE-80180-3'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: XCCDF Value sysctl_net_ipv6_conf_all_accept_ra_value # promote to variable
set_fact:
sysctl_net_ipv6_conf_all_accept_ra_value: (N/A)
tags:
- always
- name: Ensure sysctl net.ipv6.conf.all.accept_ra is set
sysctl:
name: net.ipv6.conf.all.accept_ra
value: "{{ sysctl_net_ipv6_conf_all_accept_ra_value }}"
state: present
reload: yes
tags:
- sysctl_net_ipv6_conf_all_accept_ra
- unknown_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-80180-3
- NIST-800-53-CM-7
- NIST-800-171-3.1.20
|
Configure Accepting IPv6 Redirects By Default
[ref]rule
To set the runtime status of the net.ipv6.conf.default.accept_redirects kernel parameter,
run the following command:
$ sudo sysctl -w net.ipv6.conf.default.accept_redirects=0
If this is not the system's default value, add the following line to /etc/sysctl.conf :
net.ipv6.conf.default.accept_redirects = 0
Rationale:An illicit ICMP redirect message could result in a man-in-the-middle attack. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
sysctl_net_ipv6_conf_default_accept_redirects_value="(N/A)"
#
# Set runtime for net.ipv6.conf.default.accept_redirects
#
/sbin/sysctl -q -n -w net.ipv6.conf.default.accept_redirects=$sysctl_net_ipv6_conf_default_accept_redirects_value
#
# If net.ipv6.conf.default.accept_redirects present in /etc/sysctl.conf, change value to appropriate value
# else, add "net.ipv6.conf.default.accept_redirects = value" to /etc/sysctl.conf
#
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysctl.conf' '^net.ipv6.conf.default.accept_redirects' "$sysctl_net_ipv6_conf_default_accept_redirects_value" 'CCE-80183-7'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: XCCDF Value sysctl_net_ipv6_conf_default_accept_redirects_value # promote to variable
set_fact:
sysctl_net_ipv6_conf_default_accept_redirects_value: (N/A)
tags:
- always
- name: Ensure sysctl net.ipv6.conf.default.accept_redirects is set
sysctl:
name: net.ipv6.conf.default.accept_redirects
value: "{{ sysctl_net_ipv6_conf_default_accept_redirects_value }}"
state: present
reload: yes
tags:
- sysctl_net_ipv6_conf_default_accept_redirects
- medium_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-80183-7
- NIST-800-53-CM-7
- NIST-800-171-3.1.20
|
Use Privacy Extensions for Address
[ref]ruleTo introduce randomness into the automatic generation of IPv6
addresses, add or correct the following line in
/etc/sysconfig/network-scripts/ifcfg-interface :
IPV6_PRIVACY=rfc3041
Automatically-generated IPv6 addresses are based on the underlying hardware
(e.g. Ethernet) address, and so it becomes possible to track a piece of
hardware over its lifetime using its traffic. If it is important for a system's
IP address to not trivially reveal its hardware address, this setting should be
applied.Rationale:Remediation Shell script: (show)
# enable randomness in ipv6 address generation
for interface in /etc/sysconfig/network-scripts/ifcfg-*
do
echo "IPV6_PRIVACY=rfc3041" >> $interface
done
|
Disable Support for IPv6 Unless Needed
[ref]groupDespite configuration that suggests support for IPv6 has
been disabled, link-local IPv6 address auto-configuration occurs
even when only an IPv4 address is assigned. The only way to
effectively prevent execution of the IPv6 networking stack is to
instruct the system not to activate the IPv6 kernel module. |
contains 2 rules |
Disable IPv6 Networking Support Automatic Loading
[ref]ruleTo disable support for (ipv6 ) add the following line to
/etc/sysctl.d/ipv6.conf (or another file in
/etc/sysctl.d ):
net.ipv6.conf.all.disable_ipv6 = 1
This disables IPv6 on all network interfaces as other services and system
functionality require the IPv6 stack loaded to work.Rationale:Any unnecessary network stacks - including IPv6 - should be disabled, to reduce
the vulnerability to exploitation. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
#
# Set runtime for net.ipv6.conf.all.disable_ipv6
#
/sbin/sysctl -q -n -w net.ipv6.conf.all.disable_ipv6=1
#
# If net.ipv6.conf.all.disable_ipv6 present in /etc/sysctl.conf, change value to "1"
# else, add "net.ipv6.conf.all.disable_ipv6 = 1" to /etc/sysctl.conf
#
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysctl.conf' '^net.ipv6.conf.all.disable_ipv6' "1" 'CCE-80175-3'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: Ensure sysctl net.ipv6.conf.all.disable_ipv6 is set to 1
sysctl:
name: net.ipv6.conf.all.disable_ipv6
value: 1
state: present
reload: yes
tags:
- sysctl_net_ipv6_conf_all_disable_ipv6
- medium_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-80175-3
- NIST-800-53-CM-7
- NIST-800-171-3.1.20
|
Disable Support for RPC IPv6
[ref]ruleRPC services for NFSv4 try to load transport modules for
udp6 and tcp6 by default, even if IPv6 has been disabled in
/etc/modprobe.d . To prevent RPC services such as rpc.mountd
from attempting to start IPv6 network listeners, remove or comment out the
following two lines in /etc/netconfig :
udp6 tpi_clts v inet6 udp - -
tcp6 tpi_cots_ord v inet6 tcp - - Rationale:Remediation Shell script: (show)
# Drop 'tcp6' and 'udp6' entries from /etc/netconfig to prevent RPC
# services for NFSv4 from attempting to start IPv6 network listeners
for rpc_entry in "tcp6" "udp6"
do
sed -i "/^$rpc_entry[[:space:]]\+tpi_.*inet6.*/d" /etc/netconfig
done
|
Kernel Parameters Which Affect Networking
[ref]groupThe sysctl utility is used to set
parameters which affect the operation of the Linux kernel. Kernel parameters
which affect networking and have security implications are described here. |
contains 16 rules |
Network Related Kernel Runtime Parameters for Hosts and Routers
[ref]groupCertain kernel parameters should be set for systems which are
acting as either hosts or routers to improve the system's ability defend
against certain types of IPv4 protocol attacks. |
contains 13 rules |
Configure Kernel Parameter for Accepting Source-Routed Packets By Default
[ref]rule
To set the runtime status of the net.ipv4.conf.default.accept_source_route kernel parameter,
run the following command:
$ sudo sysctl -w net.ipv4.conf.default.accept_source_route=0
If this is not the system's default value, add the following line to /etc/sysctl.conf :
net.ipv4.conf.default.accept_source_route = 0
Rationale:Source-routed packets allow the source of the packet to suggest routers
forward the packet along a different path than configured on the router, which can
be used to bypass network security measures.
Accepting source-routed packets in the IPv4 protocol has few legitimate
uses. It should be disabled unless it is absolutely required, such as when
IPv4 forwarding is enabled and the system is legitimately functioning as
a router. References:
SV-86909r1_rule, 3.2.1, 5.10.1.1, 3.1.20, CCI-000366, CCI-001551, AC-4, CM-7, SC-5, SC-7, SRG-OS-000480-GPOS-00227 Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
sysctl_net_ipv4_conf_default_accept_source_route_value="0"
#
# Set runtime for net.ipv4.conf.default.accept_source_route
#
/sbin/sysctl -q -n -w net.ipv4.conf.default.accept_source_route=$sysctl_net_ipv4_conf_default_accept_source_route_value
#
# If net.ipv4.conf.default.accept_source_route present in /etc/sysctl.conf, change value to appropriate value
# else, add "net.ipv4.conf.default.accept_source_route = value" to /etc/sysctl.conf
#
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysctl.conf' '^net.ipv4.conf.default.accept_source_route' "$sysctl_net_ipv4_conf_default_accept_source_route_value" 'CCE-80162-1'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: XCCDF Value sysctl_net_ipv4_conf_default_accept_source_route_value # promote to variable
set_fact:
sysctl_net_ipv4_conf_default_accept_source_route_value: 0
tags:
- always
- name: Ensure sysctl net.ipv4.conf.default.accept_source_route is set
sysctl:
name: net.ipv4.conf.default.accept_source_route
value: "{{ sysctl_net_ipv4_conf_default_accept_source_route_value }}"
state: present
reload: yes
tags:
- sysctl_net_ipv4_conf_default_accept_source_route
- medium_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-80162-1
- NIST-800-53-AC-4
- NIST-800-53-CM-7
- NIST-800-53-SC-5
- NIST-800-53-SC-7
- NIST-800-171-3.1.20
- CJIS-5.10.1.1
- DISA-STIG-RHEL-07-040620
|
Configure Kernel Parameter to Ignore ICMP Broadcast Echo Requests
[ref]rule
To set the runtime status of the net.ipv4.icmp_echo_ignore_broadcasts kernel parameter,
run the following command:
$ sudo sysctl -w net.ipv4.icmp_echo_ignore_broadcasts=1
If this is not the system's default value, add the following line to /etc/sysctl.conf :
net.ipv4.icmp_echo_ignore_broadcasts = 1
Rationale:Responding to broadcast (ICMP) echoes facilitates network mapping
and provides a vector for amplification attacks.
Ignoring ICMP echo requests (pings) sent to broadcast or multicast
addresses makes the system slightly more difficult to enumerate on the network. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
sysctl_net_ipv4_icmp_echo_ignore_broadcasts_value="1"
#
# Set runtime for net.ipv4.icmp_echo_ignore_broadcasts
#
/sbin/sysctl -q -n -w net.ipv4.icmp_echo_ignore_broadcasts=$sysctl_net_ipv4_icmp_echo_ignore_broadcasts_value
#
# If net.ipv4.icmp_echo_ignore_broadcasts present in /etc/sysctl.conf, change value to appropriate value
# else, add "net.ipv4.icmp_echo_ignore_broadcasts = value" to /etc/sysctl.conf
#
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysctl.conf' '^net.ipv4.icmp_echo_ignore_broadcasts' "$sysctl_net_ipv4_icmp_echo_ignore_broadcasts_value" 'CCE-80165-4'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: XCCDF Value sysctl_net_ipv4_icmp_echo_ignore_broadcasts_value # promote to variable
set_fact:
sysctl_net_ipv4_icmp_echo_ignore_broadcasts_value: 1
tags:
- always
- name: Ensure sysctl net.ipv4.icmp_echo_ignore_broadcasts is set
sysctl:
name: net.ipv4.icmp_echo_ignore_broadcasts
value: "{{ sysctl_net_ipv4_icmp_echo_ignore_broadcasts_value }}"
state: present
reload: yes
tags:
- sysctl_net_ipv4_icmp_echo_ignore_broadcasts
- medium_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-80165-4
- NIST-800-53-AC-4
- NIST-800-53-CM-7
- NIST-800-53-SC-5
- NIST-800-171-3.1.20
- CJIS-5.10.1.1
- DISA-STIG-RHEL-07-040630
|
Configure Kernel Parameter for Accepting ICMP Redirects By Default
[ref]rule
To set the runtime status of the net.ipv4.conf.default.accept_redirects kernel parameter,
run the following command:
$ sudo sysctl -w net.ipv4.conf.default.accept_redirects=0
If this is not the system's default value, add the following line to /etc/sysctl.conf :
net.ipv4.conf.default.accept_redirects = 0
Rationale:ICMP redirect messages are used by routers to inform hosts that a more direct
route exists for a particular destination. These messages modify the host's route table
and are unauthenticated. An illicit ICMP redirect message could result in a man-in-the-middle
attack.
This feature of the IPv4 protocol has few legitimate uses. It should be disabled unless
absolutely required. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
sysctl_net_ipv4_conf_default_accept_redirects_value="0"
#
# Set runtime for net.ipv4.conf.default.accept_redirects
#
/sbin/sysctl -q -n -w net.ipv4.conf.default.accept_redirects=$sysctl_net_ipv4_conf_default_accept_redirects_value
#
# If net.ipv4.conf.default.accept_redirects present in /etc/sysctl.conf, change value to appropriate value
# else, add "net.ipv4.conf.default.accept_redirects = value" to /etc/sysctl.conf
#
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysctl.conf' '^net.ipv4.conf.default.accept_redirects' "$sysctl_net_ipv4_conf_default_accept_redirects_value" 'CCE-80163-9'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: XCCDF Value sysctl_net_ipv4_conf_default_accept_redirects_value # promote to variable
set_fact:
sysctl_net_ipv4_conf_default_accept_redirects_value: 0
tags:
- always
- name: Ensure sysctl net.ipv4.conf.default.accept_redirects is set
sysctl:
name: net.ipv4.conf.default.accept_redirects
value: "{{ sysctl_net_ipv4_conf_default_accept_redirects_value }}"
state: present
reload: yes
tags:
- sysctl_net_ipv4_conf_default_accept_redirects
- medium_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-80163-9
- NIST-800-53-AC-4
- NIST-800-53-CM-7
- NIST-800-53-SC-5
- NIST-800-53-SC-7
- NIST-800-171-3.1.20
- CJIS-5.10.1.1
- DISA-STIG-RHEL-07-040640
|
Configure Kernel Parameter to Use Reverse Path Filtering by Default
[ref]rule
To set the runtime status of the net.ipv4.conf.default.rp_filter kernel parameter,
run the following command:
$ sudo sysctl -w net.ipv4.conf.default.rp_filter=1
If this is not the system's default value, add the following line to /etc/sysctl.conf :
net.ipv4.conf.default.rp_filter = 1
Rationale:Enabling reverse path filtering drops packets with source addresses
that should not have been able to be received on the interface they were
received on. It should not be used on systems which are routers for
complicated networks, but is helpful for end hosts and routers serving small
networks. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
sysctl_net_ipv4_conf_default_rp_filter_value="(N/A)"
#
# Set runtime for net.ipv4.conf.default.rp_filter
#
/sbin/sysctl -q -n -w net.ipv4.conf.default.rp_filter=$sysctl_net_ipv4_conf_default_rp_filter_value
#
# If net.ipv4.conf.default.rp_filter present in /etc/sysctl.conf, change value to appropriate value
# else, add "net.ipv4.conf.default.rp_filter = value" to /etc/sysctl.conf
#
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysctl.conf' '^net.ipv4.conf.default.rp_filter' "$sysctl_net_ipv4_conf_default_rp_filter_value" 'CCE-80168-8'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: XCCDF Value sysctl_net_ipv4_conf_default_rp_filter_value # promote to variable
set_fact:
sysctl_net_ipv4_conf_default_rp_filter_value: (N/A)
tags:
- always
- name: Ensure sysctl net.ipv4.conf.default.rp_filter is set
sysctl:
name: net.ipv4.conf.default.rp_filter
value: "{{ sysctl_net_ipv4_conf_default_rp_filter_value }}"
state: present
reload: yes
tags:
- sysctl_net_ipv4_conf_default_rp_filter
- medium_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-80168-8
- NIST-800-53-AC-4
- NIST-800-53-SC-5
- NIST-800-53-SC-7
- NIST-800-171-3.1.20
|
Configure Kernel Parameter for Accepting Secure Redirects for All Interfaces
[ref]rule
To set the runtime status of the net.ipv4.conf.all.secure_redirects kernel parameter,
run the following command:
$ sudo sysctl -w net.ipv4.conf.all.secure_redirects=0
If this is not the system's default value, add the following line to /etc/sysctl.conf :
net.ipv4.conf.all.secure_redirects = 0
Rationale:Accepting "secure" ICMP redirects (from those gateways listed as
default gateways) has few legitimate uses. It should be disabled unless it is
absolutely required. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
sysctl_net_ipv4_conf_all_secure_redirects_value="(N/A)"
#
# Set runtime for net.ipv4.conf.all.secure_redirects
#
/sbin/sysctl -q -n -w net.ipv4.conf.all.secure_redirects=$sysctl_net_ipv4_conf_all_secure_redirects_value
#
# If net.ipv4.conf.all.secure_redirects present in /etc/sysctl.conf, change value to appropriate value
# else, add "net.ipv4.conf.all.secure_redirects = value" to /etc/sysctl.conf
#
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysctl.conf' '^net.ipv4.conf.all.secure_redirects' "$sysctl_net_ipv4_conf_all_secure_redirects_value" 'CCE-80159-7'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: XCCDF Value sysctl_net_ipv4_conf_all_secure_redirects_value # promote to variable
set_fact:
sysctl_net_ipv4_conf_all_secure_redirects_value: (N/A)
tags:
- always
- name: Ensure sysctl net.ipv4.conf.all.secure_redirects is set
sysctl:
name: net.ipv4.conf.all.secure_redirects
value: "{{ sysctl_net_ipv4_conf_all_secure_redirects_value }}"
state: present
reload: yes
tags:
- sysctl_net_ipv4_conf_all_secure_redirects
- medium_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-80159-7
- NIST-800-53-AC-4
- NIST-800-53-CM-7
- NIST-800-53-SC-5
- NIST-800-171-3.1.20
|
Configure Kernel Parameter for Accepting Source-Routed Packets for All Interfaces
[ref]rule
To set the runtime status of the net.ipv4.conf.all.accept_source_route kernel parameter,
run the following command:
$ sudo sysctl -w net.ipv4.conf.all.accept_source_route=0
If this is not the system's default value, add the following line to /etc/sysctl.conf :
net.ipv4.conf.all.accept_source_route = 0
Rationale:Source-routed packets allow the source of the packet to suggest routers
forward the packet along a different path than configured on the router, which can
be used to bypass network security measures. This requirement applies only to the
forwarding of source-routerd traffic, such as when IPv4 forwarding is enabled and
the system is functioning as a router.
Accepting source-routed packets in the IPv4 protocol has few legitimate
uses. It should be disabled unless it is absolutely required. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
sysctl_net_ipv4_conf_all_accept_source_route_value="(N/A)"
#
# Set runtime for net.ipv4.conf.all.accept_source_route
#
/sbin/sysctl -q -n -w net.ipv4.conf.all.accept_source_route=$sysctl_net_ipv4_conf_all_accept_source_route_value
#
# If net.ipv4.conf.all.accept_source_route present in /etc/sysctl.conf, change value to appropriate value
# else, add "net.ipv4.conf.all.accept_source_route = value" to /etc/sysctl.conf
#
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysctl.conf' '^net.ipv4.conf.all.accept_source_route' "$sysctl_net_ipv4_conf_all_accept_source_route_value" 'CCE-27434-0'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: XCCDF Value sysctl_net_ipv4_conf_all_accept_source_route_value # promote to variable
set_fact:
sysctl_net_ipv4_conf_all_accept_source_route_value: (N/A)
tags:
- always
- name: Ensure sysctl net.ipv4.conf.all.accept_source_route is set
sysctl:
name: net.ipv4.conf.all.accept_source_route
value: "{{ sysctl_net_ipv4_conf_all_accept_source_route_value }}"
state: present
reload: yes
tags:
- sysctl_net_ipv4_conf_all_accept_source_route
- medium_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-27434-0
- NIST-800-53-AC-4
- NIST-800-53-CM-7
- NIST-800-53-SC-5
- NIST-800-171-3.1.20
- DISA-STIG-RHEL-07-040610
|
Configure Kernel Parameter to Use TCP Syncookies
[ref]rule
To set the runtime status of the net.ipv4.tcp_syncookies kernel parameter,
run the following command:
$ sudo sysctl -w net.ipv4.tcp_syncookies=1
If this is not the system's default value, add the following line to /etc/sysctl.conf :
net.ipv4.tcp_syncookies = 1
Rationale:A TCP SYN flood attack can cause a denial of service by filling a
system's TCP connection table with connections in the SYN_RCVD state.
Syncookies can be used to track a connection when a subsequent ACK is received,
verifying the initiator is attempting a valid connection and is not a flood
source. This feature is activated when a flood condition is detected, and
enables the system to continue servicing valid connection requests. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
sysctl_net_ipv4_tcp_syncookies_value="1"
#
# Set runtime for net.ipv4.tcp_syncookies
#
/sbin/sysctl -q -n -w net.ipv4.tcp_syncookies=$sysctl_net_ipv4_tcp_syncookies_value
#
# If net.ipv4.tcp_syncookies present in /etc/sysctl.conf, change value to appropriate value
# else, add "net.ipv4.tcp_syncookies = value" to /etc/sysctl.conf
#
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysctl.conf' '^net.ipv4.tcp_syncookies' "$sysctl_net_ipv4_tcp_syncookies_value" 'CCE-27495-1'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: XCCDF Value sysctl_net_ipv4_tcp_syncookies_value # promote to variable
set_fact:
sysctl_net_ipv4_tcp_syncookies_value: 1
tags:
- always
- name: Ensure sysctl net.ipv4.tcp_syncookies is set
sysctl:
name: net.ipv4.tcp_syncookies
value: "{{ sysctl_net_ipv4_tcp_syncookies_value }}"
state: present
reload: yes
tags:
- sysctl_net_ipv4_tcp_syncookies
- medium_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-27495-1
- NIST-800-53-AC-4
- NIST-800-53-SC-5(1)(2)
- NIST-800-53-SC-5(2)
- NIST-800-53-SC-5(3)
- NIST-800-171-3.1.20
- CJIS-5.10.1.1
|
Configure Kernel Parameter for Accepting ICMP Redirects for All Interfaces
[ref]rule
To set the runtime status of the net.ipv4.conf.all.accept_redirects kernel parameter,
run the following command:
$ sudo sysctl -w net.ipv4.conf.all.accept_redirects=0
If this is not the system's default value, add the following line to /etc/sysctl.conf :
net.ipv4.conf.all.accept_redirects = 0
Rationale:ICMP redirect messages are used by routers to inform hosts that a more direct
route exists for a particular destination. These messages modify the host's route table
and are unauthenticated. An illicit ICMP redirect message could result in a man-in-the-middle
attack.
This feature of the IPv4 protocol has few legitimate uses. It should be disabled unless
absolutely required. References:
SV-87827r3_rule, 3.2.2, 5.10.1.1, 3.1.20, CCI-000366, CCI-001503, CCI-001551, CM-6(d), CM-7, SC-5, SRG-OS-000480-GPOS-00227 Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
sysctl_net_ipv4_conf_all_accept_redirects_value="0"
#
# Set runtime for net.ipv4.conf.all.accept_redirects
#
/sbin/sysctl -q -n -w net.ipv4.conf.all.accept_redirects=$sysctl_net_ipv4_conf_all_accept_redirects_value
#
# If net.ipv4.conf.all.accept_redirects present in /etc/sysctl.conf, change value to appropriate value
# else, add "net.ipv4.conf.all.accept_redirects = value" to /etc/sysctl.conf
#
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysctl.conf' '^net.ipv4.conf.all.accept_redirects' "$sysctl_net_ipv4_conf_all_accept_redirects_value" 'CCE-80158-9'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: XCCDF Value sysctl_net_ipv4_conf_all_accept_redirects_value # promote to variable
set_fact:
sysctl_net_ipv4_conf_all_accept_redirects_value: 0
tags:
- always
- name: Ensure sysctl net.ipv4.conf.all.accept_redirects is set
sysctl:
name: net.ipv4.conf.all.accept_redirects
value: "{{ sysctl_net_ipv4_conf_all_accept_redirects_value }}"
state: present
reload: yes
tags:
- sysctl_net_ipv4_conf_all_accept_redirects
- medium_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-80158-9
- NIST-800-53-CM-6(d)
- NIST-800-53-CM-7
- NIST-800-53-SC-5
- NIST-800-171-3.1.20
- CJIS-5.10.1.1
- DISA-STIG-RHEL-07-040641
|
Configure Kernel Parameter to Log Martian Packets
[ref]rule
To set the runtime status of the net.ipv4.conf.all.log_martians kernel parameter,
run the following command:
$ sudo sysctl -w net.ipv4.conf.all.log_martians=1
If this is not the system's default value, add the following line to /etc/sysctl.conf :
net.ipv4.conf.all.log_martians = 1
Rationale:The presence of "martian" packets (which have impossible addresses)
as well as spoofed packets, source-routed packets, and redirects could be a
sign of nefarious network activity. Logging these packets enables this activity
to be detected. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
sysctl_net_ipv4_conf_all_log_martians_value="(N/A)"
#
# Set runtime for net.ipv4.conf.all.log_martians
#
/sbin/sysctl -q -n -w net.ipv4.conf.all.log_martians=$sysctl_net_ipv4_conf_all_log_martians_value
#
# If net.ipv4.conf.all.log_martians present in /etc/sysctl.conf, change value to appropriate value
# else, add "net.ipv4.conf.all.log_martians = value" to /etc/sysctl.conf
#
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysctl.conf' '^net.ipv4.conf.all.log_martians' "$sysctl_net_ipv4_conf_all_log_martians_value" 'CCE-80160-5'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: XCCDF Value sysctl_net_ipv4_conf_all_log_martians_value # promote to variable
set_fact:
sysctl_net_ipv4_conf_all_log_martians_value: (N/A)
tags:
- always
- name: Ensure sysctl net.ipv4.conf.all.log_martians is set
sysctl:
name: net.ipv4.conf.all.log_martians
value: "{{ sysctl_net_ipv4_conf_all_log_martians_value }}"
state: present
reload: yes
tags:
- sysctl_net_ipv4_conf_all_log_martians
- unknown_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-80160-5
- NIST-800-53-AC-17(7)
- NIST-800-53-CM-7
- NIST-800-53-SC-5(3)
- NIST-800-171-3.1.20
|
Configure Kernel Parameter to Use Reverse Path Filtering for All Interfaces
[ref]rule
To set the runtime status of the net.ipv4.conf.all.rp_filter kernel parameter,
run the following command:
$ sudo sysctl -w net.ipv4.conf.all.rp_filter=1
If this is not the system's default value, add the following line to /etc/sysctl.conf :
net.ipv4.conf.all.rp_filter = 1
Rationale:Enabling reverse path filtering drops packets with source addresses
that should not have been able to be received on the interface they were
received on. It should not be used on systems which are routers for
complicated networks, but is helpful for end hosts and routers serving small
networks. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
sysctl_net_ipv4_conf_all_rp_filter_value="(N/A)"
#
# Set runtime for net.ipv4.conf.all.rp_filter
#
/sbin/sysctl -q -n -w net.ipv4.conf.all.rp_filter=$sysctl_net_ipv4_conf_all_rp_filter_value
#
# If net.ipv4.conf.all.rp_filter present in /etc/sysctl.conf, change value to appropriate value
# else, add "net.ipv4.conf.all.rp_filter = value" to /etc/sysctl.conf
#
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysctl.conf' '^net.ipv4.conf.all.rp_filter' "$sysctl_net_ipv4_conf_all_rp_filter_value" 'CCE-80167-0'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: XCCDF Value sysctl_net_ipv4_conf_all_rp_filter_value # promote to variable
set_fact:
sysctl_net_ipv4_conf_all_rp_filter_value: (N/A)
tags:
- always
- name: Ensure sysctl net.ipv4.conf.all.rp_filter is set
sysctl:
name: net.ipv4.conf.all.rp_filter
value: "{{ sysctl_net_ipv4_conf_all_rp_filter_value }}"
state: present
reload: yes
tags:
- sysctl_net_ipv4_conf_all_rp_filter
- medium_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-80167-0
- NIST-800-53-AC-4
- NIST-800-53-SC-5
- NIST-800-53-SC-7
- NIST-800-171-3.1.20
|
Configure Kernel Parameter to Ignore Bogus ICMP Error Responses
[ref]rule
To set the runtime status of the net.ipv4.icmp_ignore_bogus_error_responses kernel parameter,
run the following command:
$ sudo sysctl -w net.ipv4.icmp_ignore_bogus_error_responses=1
If this is not the system's default value, add the following line to /etc/sysctl.conf :
net.ipv4.icmp_ignore_bogus_error_responses = 1
Rationale:Ignoring bogus ICMP error responses reduces
log size, although some activity would not be logged. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
sysctl_net_ipv4_icmp_ignore_bogus_error_responses_value="(N/A)"
#
# Set runtime for net.ipv4.icmp_ignore_bogus_error_responses
#
/sbin/sysctl -q -n -w net.ipv4.icmp_ignore_bogus_error_responses=$sysctl_net_ipv4_icmp_ignore_bogus_error_responses_value
#
# If net.ipv4.icmp_ignore_bogus_error_responses present in /etc/sysctl.conf, change value to appropriate value
# else, add "net.ipv4.icmp_ignore_bogus_error_responses = value" to /etc/sysctl.conf
#
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysctl.conf' '^net.ipv4.icmp_ignore_bogus_error_responses' "$sysctl_net_ipv4_icmp_ignore_bogus_error_responses_value" 'CCE-80166-2'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: XCCDF Value sysctl_net_ipv4_icmp_ignore_bogus_error_responses_value # promote to variable
set_fact:
sysctl_net_ipv4_icmp_ignore_bogus_error_responses_value: (N/A)
tags:
- always
- name: Ensure sysctl net.ipv4.icmp_ignore_bogus_error_responses is set
sysctl:
name: net.ipv4.icmp_ignore_bogus_error_responses
value: "{{ sysctl_net_ipv4_icmp_ignore_bogus_error_responses_value }}"
state: present
reload: yes
tags:
- sysctl_net_ipv4_icmp_ignore_bogus_error_responses
- unknown_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-80166-2
- NIST-800-53-CM-7
- NIST-800-53-SC-5
- NIST-800-171-3.1.20
|
Configure Kernel Parameter for Accepting Secure Redirects By Default
[ref]rule
To set the runtime status of the net.ipv4.conf.default.secure_redirects kernel parameter,
run the following command:
$ sudo sysctl -w net.ipv4.conf.default.secure_redirects=0
If this is not the system's default value, add the following line to /etc/sysctl.conf :
net.ipv4.conf.default.secure_redirects = 0
Rationale:Accepting "secure" ICMP redirects (from those gateways listed as
default gateways) has few legitimate uses. It should be disabled unless it is
absolutely required. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
sysctl_net_ipv4_conf_default_secure_redirects_value="(N/A)"
#
# Set runtime for net.ipv4.conf.default.secure_redirects
#
/sbin/sysctl -q -n -w net.ipv4.conf.default.secure_redirects=$sysctl_net_ipv4_conf_default_secure_redirects_value
#
# If net.ipv4.conf.default.secure_redirects present in /etc/sysctl.conf, change value to appropriate value
# else, add "net.ipv4.conf.default.secure_redirects = value" to /etc/sysctl.conf
#
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysctl.conf' '^net.ipv4.conf.default.secure_redirects' "$sysctl_net_ipv4_conf_default_secure_redirects_value" 'CCE-80164-7'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: XCCDF Value sysctl_net_ipv4_conf_default_secure_redirects_value # promote to variable
set_fact:
sysctl_net_ipv4_conf_default_secure_redirects_value: (N/A)
tags:
- always
- name: Ensure sysctl net.ipv4.conf.default.secure_redirects is set
sysctl:
name: net.ipv4.conf.default.secure_redirects
value: "{{ sysctl_net_ipv4_conf_default_secure_redirects_value }}"
state: present
reload: yes
tags:
- sysctl_net_ipv4_conf_default_secure_redirects
- medium_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-80164-7
- NIST-800-53-AC-4
- NIST-800-53-CM-7
- NIST-800-53-SC-5
- NIST-800-53-SC-7
- NIST-800-171-3.1.20
|
Configure Kernel Parameter to Log Martian Packets By Default
[ref]rule
To set the runtime status of the net.ipv4.conf.default.log_martians kernel parameter,
run the following command:
$ sudo sysctl -w net.ipv4.conf.default.log_martians=1
If this is not the system's default value, add the following line to /etc/sysctl.conf :
net.ipv4.conf.default.log_martians = 1
Rationale:The presence of "martian" packets (which have impossible addresses)
as well as spoofed packets, source-routed packets, and redirects could be a
sign of nefarious network activity. Logging these packets enables this activity
to be detected. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
sysctl_net_ipv4_conf_default_log_martians_value="(N/A)"
#
# Set runtime for net.ipv4.conf.default.log_martians
#
/sbin/sysctl -q -n -w net.ipv4.conf.default.log_martians=$sysctl_net_ipv4_conf_default_log_martians_value
#
# If net.ipv4.conf.default.log_martians present in /etc/sysctl.conf, change value to appropriate value
# else, add "net.ipv4.conf.default.log_martians = value" to /etc/sysctl.conf
#
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysctl.conf' '^net.ipv4.conf.default.log_martians' "$sysctl_net_ipv4_conf_default_log_martians_value" 'CCE-80161-3'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: XCCDF Value sysctl_net_ipv4_conf_default_log_martians_value # promote to variable
set_fact:
sysctl_net_ipv4_conf_default_log_martians_value: (N/A)
tags:
- always
- name: Ensure sysctl net.ipv4.conf.default.log_martians is set
sysctl:
name: net.ipv4.conf.default.log_martians
value: "{{ sysctl_net_ipv4_conf_default_log_martians_value }}"
state: present
reload: yes
tags:
- sysctl_net_ipv4_conf_default_log_martians
- unknown_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-80161-3
- NIST-800-53-AC-17(7)
- NIST-800-53-CM-7
- NIST-800-53-SC-5(3)
- NIST-800-171-3.1.20
|
Network Parameters for Hosts Only
[ref]groupIf the system is not going to be used as a router, then setting certain
kernel parameters ensure that the host will not perform routing
of network traffic. |
contains 3 rules |
Disable Kernel Parameter for IP Forwarding
[ref]rule
To set the runtime status of the net.ipv4.ip_forward kernel parameter,
run the following command:
$ sudo sysctl -w net.ipv4.ip_forward=0
If this is not the system's default value, add the following line to /etc/sysctl.conf :
net.ipv4.ip_forward = 0
Rationale:Routing protocol daemons are typically used on routers to exchange
network topology information with other routers. If this capability is used when
not required, system network information may be unnecessarily transmitted across
the network. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
#
# Set runtime for net.ipv4.ip_forward
#
/sbin/sysctl -q -n -w net.ipv4.ip_forward=0
#
# If net.ipv4.ip_forward present in /etc/sysctl.conf, change value to "0"
# else, add "net.ipv4.ip_forward = 0" to /etc/sysctl.conf
#
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysctl.conf' '^net.ipv4.ip_forward' "0" 'CCE-80157-1'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: Ensure sysctl net.ipv4.ip_forward is set to 0
sysctl:
name: net.ipv4.ip_forward
value: 0
state: present
reload: yes
tags:
- sysctl_net_ipv4_ip_forward
- medium_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-80157-1
- NIST-800-53-CM-7
- NIST-800-53-SC-5
- NIST-800-53-SC-32
- NIST-800-171-3.1.20
- DISA-STIG-RHEL-07-040740
|
Disable Kernel Parameter for Sending ICMP Redirects for All Interfaces
[ref]rule
To set the runtime status of the net.ipv4.conf.all.send_redirects kernel parameter,
run the following command:
$ sudo sysctl -w net.ipv4.conf.all.send_redirects=0
If this is not the system's default value, add the following line to /etc/sysctl.conf :
net.ipv4.conf.all.send_redirects = 0
Rationale:ICMP redirect messages are used by routers to inform hosts that a more
direct route exists for a particular destination. These messages contain information
from the system's route table possibly revealing portions of the network topology.
The ability to send ICMP redirects is only appropriate for systems acting as routers. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
#
# Set runtime for net.ipv4.conf.all.send_redirects
#
/sbin/sysctl -q -n -w net.ipv4.conf.all.send_redirects=0
#
# If net.ipv4.conf.all.send_redirects present in /etc/sysctl.conf, change value to "0"
# else, add "net.ipv4.conf.all.send_redirects = 0" to /etc/sysctl.conf
#
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysctl.conf' '^net.ipv4.conf.all.send_redirects' "0" 'CCE-80156-3'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: Ensure sysctl net.ipv4.conf.all.send_redirects is set to 0
sysctl:
name: net.ipv4.conf.all.send_redirects
value: 0
state: present
reload: yes
tags:
- sysctl_net_ipv4_conf_all_send_redirects
- medium_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-80156-3
- NIST-800-53-AC-4
- NIST-800-53-CM-7
- NIST-800-53-SC-5(1)
- NIST-800-171-3.1.20
- CJIS-5.10.1.1
- DISA-STIG-RHEL-07-040660
|
Disable Kernel Parameter for Sending ICMP Redirects by Default
[ref]rule
To set the runtime status of the net.ipv4.conf.default.send_redirects kernel parameter,
run the following command:
$ sudo sysctl -w net.ipv4.conf.default.send_redirects=0
If this is not the system's default value, add the following line to /etc/sysctl.conf :
net.ipv4.conf.default.send_redirects = 0
Rationale:ICMP redirect messages are used by routers to inform hosts that a more
direct route exists for a particular destination. These messages contain information
from the system's route table possibly revealing portions of the network topology.
The ability to send ICMP redirects is only appropriate for systems acting as routers. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
#
# Set runtime for net.ipv4.conf.default.send_redirects
#
/sbin/sysctl -q -n -w net.ipv4.conf.default.send_redirects=0
#
# If net.ipv4.conf.default.send_redirects present in /etc/sysctl.conf, change value to "0"
# else, add "net.ipv4.conf.default.send_redirects = 0" to /etc/sysctl.conf
#
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysctl.conf' '^net.ipv4.conf.default.send_redirects' "0" 'CCE-80156-3'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: Ensure sysctl net.ipv4.conf.default.send_redirects is set to 0
sysctl:
name: net.ipv4.conf.default.send_redirects
value: 0
state: present
reload: yes
tags:
- sysctl_net_ipv4_conf_default_send_redirects
- medium_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-80156-3
- NIST-800-53-AC-4
- NIST-800-53-CM-7
- NIST-800-53-SC-5
- NIST-800-53-SC-7
- NIST-800-171-3.1.20
- CJIS-5.10.1.1
- DISA-STIG-RHEL-07-040650
|
Uncommon Network Protocols
[ref]groupThe system includes support for several network
protocols which are not commonly used. Although security vulnerabilities
in kernel networking code are not frequently
discovered, the consequences can be dramatic. Ensuring uncommon
network protocols are disabled reduces the system's risk to attacks
targeted at its implementation of those protocols. |
contains 2 rules |
Disable DCCP Support
[ref]ruleThe Datagram Congestion Control Protocol (DCCP) is a
relatively new transport layer protocol, designed to support
streaming media and telephony.
To configure the system to prevent the dccp
kernel module from being loaded, add the following line to a file in the directory /etc/modprobe.d :
install dccp /bin/true Rationale:Disabling DCCP protects
the system against exploitation of any flaws in its implementation. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
if grep --silent "^install dccp" /etc/modprobe.d/dccp.conf ; then
sed -i 's/^install dccp.*/install dccp /bin/true/g' /etc/modprobe.d/dccp.conf
else
echo -e "\n# Disable per security requirements" >> /etc/modprobe.d/dccp.conf
echo "install dccp /bin/true" >> /etc/modprobe.d/dccp.conf
fi
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: Ensure kernel module 'dccp' is disabled
lineinfile:
create=yes
dest="/etc/modprobe.d/{{item}}.conf"
regexp="{{item}}"
line="install {{item}} /bin/true"
with_items:
- dccp
tags:
- kernel_module_dccp_disabled
- medium_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-26828-4
- NIST-800-53-CM-7
- NIST-800-171-3.4.6
- CJIS-5.10.1
- DISA-STIG-RHEL-07-020101
|
Disable SCTP Support
[ref]ruleThe Stream Control Transmission Protocol (SCTP) is a
transport layer protocol, designed to support the idea of
message-oriented communication, with several streams of messages
within one connection.
To configure the system to prevent the sctp
kernel module from being loaded, add the following line to a file in the directory /etc/modprobe.d :
install sctp /bin/true Rationale:Disabling SCTP protects
the system against exploitation of any flaws in its implementation. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
if grep --silent "^install sctp" /etc/modprobe.d/sctp.conf ; then
sed -i 's/^install sctp.*/install sctp /bin/true/g' /etc/modprobe.d/sctp.conf
else
echo -e "\n# Disable per security requirements" >> /etc/modprobe.d/sctp.conf
echo "install sctp /bin/true" >> /etc/modprobe.d/sctp.conf
fi
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: Ensure kernel module 'sctp' is disabled
lineinfile:
create=yes
dest="/etc/modprobe.d/{{item}}.conf"
regexp="{{item}}"
line="install {{item}} /bin/true"
with_items:
- sctp
tags:
- kernel_module_sctp_disabled
- medium_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-27106-4
- NIST-800-53-CM-7
- NIST-800-171-3.4.6
- CJIS-5.10.1
|
Wireless Networking
[ref]groupWireless networking, such as 802.11
(WiFi) and Bluetooth, can present a security risk to sensitive or
classified systems and networks. Wireless networking hardware is
much more likely to be included in laptop or portable systems than
in desktops or servers.
Removal of hardware provides the greatest assurance that the wireless
capability remains disabled. Acquisition policies often include provisions to
prevent the purchase of equipment that will be used in sensitive spaces and
includes wireless capabilities. If it is impractical to remove the wireless
hardware, and policy permits the device to enter sensitive spaces as long
as wireless is disabled, efforts should instead focus on disabling wireless capability
via software. |
contains 3 rules |
Disable Wireless Through Software Configuration
[ref]groupIf it is impossible to remove the wireless hardware
from the device in question, disable as much of it as possible
through software. The following methods can disable software
support for wireless networking, but note that these methods do not
prevent malicious software or careless users from re-activating the
devices. |
contains 3 rules |
Disable Bluetooth Kernel Modules
[ref]ruleThe kernel's module loading system can be configured to prevent
loading of the Bluetooth module. Add the following to
the appropriate /etc/modprobe.d configuration file
to prevent the loading of the Bluetooth module:
install bluetooth /bin/true Rationale:If Bluetooth functionality must be disabled, preventing the kernel
from loading the kernel module provides an additional safeguard against its
activation. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
if grep --silent "^install bluetooth" /etc/modprobe.d/bluetooth.conf ; then
sed -i 's/^install bluetooth.*/install bluetooth /bin/true/g' /etc/modprobe.d/bluetooth.conf
else
echo -e "\n# Disable per security requirements" >> /etc/modprobe.d/bluetooth.conf
echo "install bluetooth /bin/true" >> /etc/modprobe.d/bluetooth.conf
fi
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: Ensure kernel module 'bluetooth' is disabled
lineinfile:
create=yes
dest="/etc/modprobe.d/{{item}}.conf"
regexp="{{item}}"
line="install {{item}} /bin/true"
with_items:
- bluetooth
tags:
- kernel_module_bluetooth_disabled
- medium_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-27327-6
- NIST-800-53-AC-17(8)
- NIST-800-53-AC-18(a)
- NIST-800-53-AC-18(d)
- NIST-800-53-AC-18(3)
- NIST-800-53-CM-7
- NIST-800-171-3.1.16
- CJIS-5.13.1.3
|
Disable Bluetooth Service
[ref]rule
The bluetooth service can be disabled with the following command:
$ sudo systemctl disable bluetooth.service
$ sudo service bluetooth stop
Rationale:Disabling the bluetooth service prevents the system from attempting
connections to Bluetooth devices, which entails some security risk.
Nevertheless, variation in this risk decision may be expected due to the
utility of Bluetooth connectivity and its limited range. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
grep -qi disable /etc/xinetd.d/bluetooth && \
sed -i 's/disable.*/disable = yes/gI' /etc/xinetd.d/bluetooth
#
# Disable bluetooth.service for all systemd targets
#
systemctl disable bluetooth.service
#
# Stop bluetooth.service if currently running
#
systemctl stop bluetooth.service
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
- name: Disable service bluetooth
service:
name="{{item}}"
enabled="no"
state="stopped"
register: service_result
failed_when: "service_result|failed and ('Could not find the requested service' not in service_result.msg)"
with_items:
- bluetooth
tags:
- service_bluetooth_disabled
- medium_severity
- disable_strategy
- low_complexity
- low_disruption
- CCE-27328-4
- NIST-800-53-AC-17(8)
- NIST-800-53-AC-18(a)
- NIST-800-53-AC-18(d)
- NIST-800-53-AC-18(3)
- NIST-800-53-CM-7
- NIST-800-171-3.1.16
|
Deactivate Wireless Network Interfaces
[ref]ruleDeactivating wireless network interfaces should prevent
normal usage of the wireless capability.
Configure the system to disable all wireless network interfaces with the
following command:
$ sudo nmcli radio wifi off Rationale:The use of wireless networking can introduce many different attack vectors into
the organization's network. Common attack vectors such as malicious association
and ad hoc networks will allow an attacker to spoof a wireless access point
(AP), allowing validated systems to connect to the malicious AP and enabling the
attacker to monitor and record network traffic. These malicious APs can also
serve to create a man-in-the-middle attack or be used to create a denial of
service to valid network resources. References:
4.3.1, 3.1.16, CCI-000085, CCI-002418, AC-17(8), AC-18(a), AC-18(d), AC-18(3), CM-7, SRG-OS-000424-GPOS-00188, SV-87829r1_rule |
Ensure System is Not Acting as a Network Sniffer
[ref]ruleThe system should not be acting as a network sniffer, which can
capture all traffic on the network to which it is connected. Run the following
to determine if any interface is running in promiscuous mode:
$ ip link | grep PROMISC Rationale:Network interfaces in promiscuous mode allow for the capture of all network traffic
visible to the system. If unauthorized individuals can access these applications, it
may allow them to collect information such as logon IDs, passwords, and key exchanges
between systems.
If the system is being used to perform a network troubleshooting function, the use of these
tools must be documented with the Information Systems Security Manager (ISSM) and restricted
to only authorized personnel. |
SELinux
[ref]groupSELinux is a feature of the Linux kernel which can be
used to guard against misconfigured or compromised programs.
SELinux enforces the idea that programs should be limited in what
files they can access and what actions they can take.
The default SELinux policy, as configured on Red Hat Enterprise Linux 7, has been
sufficiently developed and debugged that it should be usable on
almost any Red Hat system with minimal configuration and a small
amount of system administrator training. This policy prevents
system services - including most of the common network-visible
services such as mail servers, FTP servers, and DNS servers - from
accessing files which those services have no valid reason to
access. This action alone prevents a huge amount of possible damage
from network attacks against services, from trojaned software, and
so forth.
This guide recommends that SELinux be enabled using the
default (targeted) policy on every Red Hat system, unless that
system has unusual requirements which make a stronger policy
appropriate.
For more information on SELinux, see https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/SELinux_Users_and_Administrators_Guide |
contains 66 rules |
SELinux - Booleans
[ref]groupEnable or Disable runtime customization of SELinux system policies
without having to reload or recompile the SELinux policy. |
contains 61 rules |
Disable the ssh_sysadm_login SELinux Boolean
[ref]ruleBy default, the SELinux boolean ssh_sysadm_login is disabled.
If this setting is enabled, it should be disabled.
To disable the ssh_sysadm_login SELinux boolean, run the following command:
$ sudo setsebool -P ssh_sysadm_login off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_ssh_sysadm_login="(N/A)"
setsebool -P ssh_sysadm_login $var_ssh_sysadm_login
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_ssh_sysadm_login # promote to variable
set_fact:
var_ssh_sysadm_login: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean ssh_sysadm_login accordingly
seboolean:
name: ssh_sysadm_login
state: "{{ var_ssh_sysadm_login }}"
persistent: yes
tags:
- sebool_ssh_sysadm_login
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
disable the selinuxuser_execstack SELinux Boolean
[ref]ruleBy default, the SELinux boolean selinuxuser_execstack is enabled.
This setting should be disabled as unconfined executables should not be able
to make their stack executable.
To disable the selinuxuser_execstack SELinux boolean, run the following command:
$ sudo setsebool -P selinuxuser_execstack off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_selinuxuser_execstack="(N/A)"
setsebool -P selinuxuser_execstack $var_selinuxuser_execstack
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_selinuxuser_execstack # promote to variable
set_fact:
var_selinuxuser_execstack: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean selinuxuser_execstack accordingly
seboolean:
name: selinuxuser_execstack
state: "{{ var_selinuxuser_execstack }}"
persistent: yes
tags:
- sebool_selinuxuser_execstack
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the gpg_web_anon_write SELinux Boolean
[ref]ruleBy default, the SELinux boolean gpg_web_anon_write is disabled.
If this setting is enabled, it should be disabled.
To disable the gpg_web_anon_write SELinux boolean, run the following command:
$ sudo setsebool -P gpg_web_anon_write off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_gpg_web_anon_write="(N/A)"
setsebool -P gpg_web_anon_write $var_gpg_web_anon_write
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_gpg_web_anon_write # promote to variable
set_fact:
var_gpg_web_anon_write: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean gpg_web_anon_write accordingly
seboolean:
name: gpg_web_anon_write
state: "{{ var_gpg_web_anon_write }}"
persistent: yes
tags:
- sebool_gpg_web_anon_write
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Enable the mount_anyfile SELinux Boolean
[ref]ruleBy default, the SELinux boolean mount_anyfile is enabled.
If this setting is disabled, it should be enabled to allow any file
or directory to be mounted.
To enable the mount_anyfile SELinux boolean, run the following command:
$ sudo setsebool -P mount_anyfile on Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_mount_anyfile="(N/A)"
setsebool -P mount_anyfile $var_mount_anyfile
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_mount_anyfile # promote to variable
set_fact:
var_mount_anyfile: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean mount_anyfile accordingly
seboolean:
name: mount_anyfile
state: "{{ var_mount_anyfile }}"
persistent: yes
tags:
- sebool_mount_anyfile
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the selinuxuser_tcp_server SELinux Boolean
[ref]ruleBy default, the SELinux boolean selinuxuser_tcp_server is disabled.
If this setting is enabled, it should be disabled.
To disable the selinuxuser_tcp_server SELinux boolean, run the following command:
$ sudo setsebool -P selinuxuser_tcp_server off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_selinuxuser_tcp_server="(N/A)"
setsebool -P selinuxuser_tcp_server $var_selinuxuser_tcp_server
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_selinuxuser_tcp_server # promote to variable
set_fact:
var_selinuxuser_tcp_server: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean selinuxuser_tcp_server accordingly
seboolean:
name: selinuxuser_tcp_server
state: "{{ var_selinuxuser_tcp_server }}"
persistent: yes
tags:
- sebool_selinuxuser_tcp_server
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the daemons_use_tcp_wrapper SELinux Boolean
[ref]ruleBy default, the SELinux boolean daemons_use_tcp_wrapper is disabled.
If this setting is enabled, it should be disabled.
To disable the daemons_use_tcp_wrapper SELinux boolean, run the following command:
$ sudo setsebool -P daemons_use_tcp_wrapper off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_daemons_use_tcp_wrapper="(N/A)"
setsebool -P daemons_use_tcp_wrapper $var_daemons_use_tcp_wrapper
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_daemons_use_tcp_wrapper # promote to variable
set_fact:
var_daemons_use_tcp_wrapper: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean daemons_use_tcp_wrapper accordingly
seboolean:
name: daemons_use_tcp_wrapper
state: "{{ var_daemons_use_tcp_wrapper }}"
persistent: yes
tags:
- sebool_daemons_use_tcp_wrapper
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the cron_can_relabel SELinux Boolean
[ref]ruleBy default, the SELinux boolean cron_can_relabel is disabled.
If this setting is enabled, it should be disabled.
To disable the cron_can_relabel SELinux boolean, run the following command:
$ sudo setsebool -P cron_can_relabel off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_cron_can_relabel="(N/A)"
setsebool -P cron_can_relabel $var_cron_can_relabel
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_cron_can_relabel # promote to variable
set_fact:
var_cron_can_relabel: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean cron_can_relabel accordingly
seboolean:
name: cron_can_relabel
state: "{{ var_cron_can_relabel }}"
persistent: yes
tags:
- sebool_cron_can_relabel
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Enable the logadm_exec_content SELinux Boolean
[ref]ruleBy default, the SELinux boolean logadm_exec_content is enabled.
If this setting is disabled, it should be enabled.
To enable the logadm_exec_content SELinux boolean, run the following command:
$ sudo setsebool -P logadm_exec_content on Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_logadm_exec_content="(N/A)"
setsebool -P logadm_exec_content $var_logadm_exec_content
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_logadm_exec_content # promote to variable
set_fact:
var_logadm_exec_content: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean logadm_exec_content accordingly
seboolean:
name: logadm_exec_content
state: "{{ var_logadm_exec_content }}"
persistent: yes
tags:
- sebool_logadm_exec_content
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Enable the user_exec_content SELinux Boolean
[ref]ruleBy default, the SELinux boolean user_exec_content is enabled.
If this setting is disabled, it should be enabled.
To enable the user_exec_content SELinux boolean, run the following command:
$ sudo setsebool -P user_exec_content on Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_user_exec_content="(N/A)"
setsebool -P user_exec_content $var_user_exec_content
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_user_exec_content # promote to variable
set_fact:
var_user_exec_content: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean user_exec_content accordingly
seboolean:
name: user_exec_content
state: "{{ var_user_exec_content }}"
persistent: yes
tags:
- sebool_user_exec_content
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the deny_ptrace SELinux Boolean
[ref]ruleBy default, the SELinux boolean deny_ptrace is disabled.
If this setting is enabled, it should be disabled.
To disable the deny_ptrace SELinux boolean, run the following command:
$ sudo setsebool -P deny_ptrace off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_deny_ptrace="(N/A)"
setsebool -P deny_ptrace $var_deny_ptrace
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_deny_ptrace # promote to variable
set_fact:
var_deny_ptrace: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean deny_ptrace accordingly
seboolean:
name: deny_ptrace
state: "{{ var_deny_ptrace }}"
persistent: yes
tags:
- sebool_deny_ptrace
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the xserver_object_manager SELinux Boolean
[ref]ruleBy default, the SELinux boolean xserver_object_manager is disabled.
If this setting is enabled, it should be disabled.
To disable the xserver_object_manager SELinux boolean, run the following command:
$ sudo setsebool -P xserver_object_manager off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_xserver_object_manager="(N/A)"
setsebool -P xserver_object_manager $var_xserver_object_manager
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_xserver_object_manager # promote to variable
set_fact:
var_xserver_object_manager: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean xserver_object_manager accordingly
seboolean:
name: xserver_object_manager
state: "{{ var_xserver_object_manager }}"
persistent: yes
tags:
- sebool_xserver_object_manager
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the xdm_sysadm_login SELinux Boolean
[ref]ruleBy default, the SELinux boolean xdm_sysadm_login is disabled.
If this setting is enabled, it should be disabled.
To disable the xdm_sysadm_login SELinux boolean, run the following command:
$ sudo setsebool -P xdm_sysadm_login off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_xdm_sysadm_login="(N/A)"
setsebool -P xdm_sysadm_login $var_xdm_sysadm_login
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_xdm_sysadm_login # promote to variable
set_fact:
var_xdm_sysadm_login: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean xdm_sysadm_login accordingly
seboolean:
name: xdm_sysadm_login
state: "{{ var_xdm_sysadm_login }}"
persistent: yes
tags:
- sebool_xdm_sysadm_login
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the selinuxuser_mysql_connect_enabled SELinux Boolean
[ref]ruleBy default, the SELinux boolean selinuxuser_mysql_connect_enabled is disabled.
If this setting is enabled, it should be disabled.
To disable the selinuxuser_mysql_connect_enabled SELinux boolean, run the following command:
$ sudo setsebool -P selinuxuser_mysql_connect_enabled off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_selinuxuser_mysql_connect_enabled="(N/A)"
setsebool -P selinuxuser_mysql_connect_enabled $var_selinuxuser_mysql_connect_enabled
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_selinuxuser_mysql_connect_enabled # promote to variable
set_fact:
var_selinuxuser_mysql_connect_enabled: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean selinuxuser_mysql_connect_enabled accordingly
seboolean:
name: selinuxuser_mysql_connect_enabled
state: "{{ var_selinuxuser_mysql_connect_enabled }}"
persistent: yes
tags:
- sebool_selinuxuser_mysql_connect_enabled
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the ssh_keysign SELinux Boolean
[ref]ruleBy default, the SELinux boolean ssh_keysign is disabled.
If this setting is enabled, it should be disabled.
To disable the ssh_keysign SELinux boolean, run the following command:
$ sudo setsebool -P ssh_keysign off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_ssh_keysign="(N/A)"
setsebool -P ssh_keysign $var_ssh_keysign
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_ssh_keysign # promote to variable
set_fact:
var_ssh_keysign: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean ssh_keysign accordingly
seboolean:
name: ssh_keysign
state: "{{ var_ssh_keysign }}"
persistent: yes
tags:
- sebool_ssh_keysign
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Enable the login_console_enabled SELinux Boolean
[ref]ruleBy default, the SELinux boolean login_console_enabled is enabled.
If this setting is disabled, it should be enabled as it allows login from
/dev/console to a console session.
To enable the login_console_enabled SELinux boolean, run the following command:
$ sudo setsebool -P login_console_enabled on Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_login_console_enabled="(N/A)"
setsebool -P login_console_enabled $var_login_console_enabled
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_login_console_enabled # promote to variable
set_fact:
var_login_console_enabled: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean login_console_enabled accordingly
seboolean:
name: login_console_enabled
state: "{{ var_login_console_enabled }}"
persistent: yes
tags:
- sebool_login_console_enabled
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Enable the cron_userdomain_transition SELinux Boolean
[ref]ruleBy default, the SELinux boolean cron_userdomain_transition is enabled.
This setting should be enabled as end user cron jobs run in their default
associated user domain(s) instead of the general cronjob domain.
To enable the cron_userdomain_transition SELinux boolean, run the following command:
$ sudo setsebool -P cron_userdomain_transition on Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_cron_userdomain_transition="(N/A)"
setsebool -P cron_userdomain_transition $var_cron_userdomain_transition
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_cron_userdomain_transition # promote to variable
set_fact:
var_cron_userdomain_transition: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean cron_userdomain_transition accordingly
seboolean:
name: cron_userdomain_transition
state: "{{ var_cron_userdomain_transition }}"
persistent: yes
tags:
- sebool_cron_userdomain_transition
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the xguest_mount_media SELinux Boolean
[ref]ruleBy default, the SELinux boolean xguest_mount_media is enabled.
This setting should be disabled as guest users should not be able to mount
any media.
To disable the xguest_mount_media SELinux boolean, run the following command:
$ sudo setsebool -P xguest_mount_media off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_xguest_mount_media="(N/A)"
setsebool -P xguest_mount_media $var_xguest_mount_media
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_xguest_mount_media # promote to variable
set_fact:
var_xguest_mount_media: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean xguest_mount_media accordingly
seboolean:
name: xguest_mount_media
state: "{{ var_xguest_mount_media }}"
persistent: yes
tags:
- sebool_xguest_mount_media
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the selinuxuser_rw_noexattrfile SELinux Boolean
[ref]ruleBy default, the SELinux boolean selinuxuser_rw_noexattrfile is enabled.
This setting should be disabled as users should not be able to read/write files
on filesystems that do not have extended attributes e.g. FAT, CDROM, FLOPPY, etc.
To disable the selinuxuser_rw_noexattrfile SELinux boolean, run the following command:
$ sudo setsebool -P selinuxuser_rw_noexattrfile off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_selinuxuser_rw_noexattrfile="(N/A)"
setsebool -P selinuxuser_rw_noexattrfile $var_selinuxuser_rw_noexattrfile
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_selinuxuser_rw_noexattrfile # promote to variable
set_fact:
var_selinuxuser_rw_noexattrfile: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean selinuxuser_rw_noexattrfile accordingly
seboolean:
name: selinuxuser_rw_noexattrfile
state: "{{ var_selinuxuser_rw_noexattrfile }}"
persistent: yes
tags:
- sebool_selinuxuser_rw_noexattrfile
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the deny_execmem SELinux Boolean
[ref]ruleBy default, the SELinux boolean deny_execmem is disabled.
If this setting is enabled, it should be disabled.
To disable the deny_execmem SELinux boolean, run the following command:
$ sudo setsebool -P deny_execmem off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_deny_execmem="(N/A)"
setsebool -P deny_execmem $var_deny_execmem
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_deny_execmem # promote to variable
set_fact:
var_deny_execmem: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean deny_execmem accordingly
seboolean:
name: deny_execmem
state: "{{ var_deny_execmem }}"
persistent: yes
tags:
- sebool_deny_execmem
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the ssh_chroot_rw_homedirs SELinux Boolean
[ref]ruleBy default, the SELinux boolean ssh_chroot_rw_homedirs is disabled.
If this setting is enabled, it should be disabled.
To disable the ssh_chroot_rw_homedirs SELinux boolean, run the following command:
$ sudo setsebool -P ssh_chroot_rw_homedirs off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_ssh_chroot_rw_homedirs="(N/A)"
setsebool -P ssh_chroot_rw_homedirs $var_ssh_chroot_rw_homedirs
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_ssh_chroot_rw_homedirs # promote to variable
set_fact:
var_ssh_chroot_rw_homedirs: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean ssh_chroot_rw_homedirs accordingly
seboolean:
name: ssh_chroot_rw_homedirs
state: "{{ var_ssh_chroot_rw_homedirs }}"
persistent: yes
tags:
- sebool_ssh_chroot_rw_homedirs
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the abrt_anon_write SELinux Boolean
[ref]ruleBy default, the SELinux boolean abrt_anon_write is disabled.
If this setting is enabled, it should be disabled.
To disable the abrt_anon_write SELinux boolean, run the following command:
$ sudo setsebool -P abrt_anon_write off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_abrt_anon_write="(N/A)"
setsebool -P abrt_anon_write $var_abrt_anon_write
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_abrt_anon_write # promote to variable
set_fact:
var_abrt_anon_write: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean abrt_anon_write accordingly
seboolean:
name: abrt_anon_write
state: "{{ var_abrt_anon_write }}"
persistent: yes
tags:
- sebool_abrt_anon_write
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-80419-5
- NIST-800-53-TBD
- NIST-800-171-3.7.2
- DISA-STIG-RHEL-07-TBD
|
Disable the abrt_handle_event SELinux Boolean
[ref]ruleBy default, the SELinux boolean abrt_handle_event is disabled.
If this setting is enabled, it should be disabled.
To disable the abrt_handle_event SELinux boolean, run the following command:
$ sudo setsebool -P abrt_handle_event off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_abrt_handle_event="(N/A)"
setsebool -P abrt_handle_event $var_abrt_handle_event
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_abrt_handle_event # promote to variable
set_fact:
var_abrt_handle_event: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean abrt_handle_event accordingly
seboolean:
name: abrt_handle_event
state: "{{ var_abrt_handle_event }}"
persistent: yes
tags:
- sebool_abrt_handle_event
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-80420-3
- NIST-800-53-TBD
- NIST-800-171-3.7.2
- DISA-STIG-RHEL-07-TBD
|
Disable the mock_enable_homedirs SELinux Boolean
[ref]ruleBy default, the SELinux boolean mock_enable_homedirs is disabled.
If this setting is enabled, it should be disabled.
To disable the mock_enable_homedirs SELinux boolean, run the following command:
$ sudo setsebool -P mock_enable_homedirs off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_mock_enable_homedirs="(N/A)"
setsebool -P mock_enable_homedirs $var_mock_enable_homedirs
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_mock_enable_homedirs # promote to variable
set_fact:
var_mock_enable_homedirs: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean mock_enable_homedirs accordingly
seboolean:
name: mock_enable_homedirs
state: "{{ var_mock_enable_homedirs }}"
persistent: yes
tags:
- sebool_mock_enable_homedirs
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Enable the logging_syslogd_use_tty SELinux Boolean
[ref]ruleBy default, the SELinux boolean logging_syslogd_use_tty is enabled.
If this setting is disabled, it should be enabled as it allows syslog
the ability to read/write to terminal.
To enable the logging_syslogd_use_tty SELinux boolean, run the following command:
$ sudo setsebool -P logging_syslogd_use_tty on Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_logging_syslogd_use_tty="(N/A)"
setsebool -P logging_syslogd_use_tty $var_logging_syslogd_use_tty
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_logging_syslogd_use_tty # promote to variable
set_fact:
var_logging_syslogd_use_tty: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean logging_syslogd_use_tty accordingly
seboolean:
name: logging_syslogd_use_tty
state: "{{ var_logging_syslogd_use_tty }}"
persistent: yes
tags:
- sebool_logging_syslogd_use_tty
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the secure_mode_insmod SELinux Boolean
[ref]ruleBy default, the SELinux boolean secure_mode_insmod is disabled.
If this setting is enabled, it should be disabled.
To disable the secure_mode_insmod SELinux boolean, run the following command:
$ sudo setsebool -P secure_mode_insmod off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_secure_mode_insmod="(N/A)"
setsebool -P secure_mode_insmod $var_secure_mode_insmod
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_secure_mode_insmod # promote to variable
set_fact:
var_secure_mode_insmod: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean secure_mode_insmod accordingly
seboolean:
name: secure_mode_insmod
state: "{{ var_secure_mode_insmod }}"
persistent: yes
tags:
- sebool_secure_mode_insmod
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Enable the auditadm_exec_content SELinux Boolean
[ref]ruleBy default, the SELinux boolean auditadm_exec_content is enabled.
If this setting is disabled, it should be enabled.
To enable the auditadm_exec_content SELinux boolean, run the following command:
$ sudo setsebool -P auditadm_exec_content on Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_auditadm_exec_content="(N/A)"
setsebool -P auditadm_exec_content $var_auditadm_exec_content
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_auditadm_exec_content # promote to variable
set_fact:
var_auditadm_exec_content: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean auditadm_exec_content accordingly
seboolean:
name: auditadm_exec_content
state: "{{ var_auditadm_exec_content }}"
persistent: yes
tags:
- sebool_auditadm_exec_content
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-80424-5
- NIST-800-53-TBD
- NIST-800-171-80424-5
- DISA-STIG-RHEL-07-TBD
|
Enable the kerberos_enabled SELinux Boolean
[ref]ruleBy default, the SELinux boolean kerberos_enabled is enabled.
If this setting is disabled, it should be enabled to allow confined
applications to run with Kerberos.
To enable the kerberos_enabled SELinux boolean, run the following command:
$ sudo setsebool -P kerberos_enabled on Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_kerberos_enabled="(N/A)"
setsebool -P kerberos_enabled $var_kerberos_enabled
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_kerberos_enabled # promote to variable
set_fact:
var_kerberos_enabled: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean kerberos_enabled accordingly
seboolean:
name: kerberos_enabled
state: "{{ var_kerberos_enabled }}"
persistent: yes
tags:
- sebool_kerberos_enabled
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Enable the unconfined_login SELinux Boolean
[ref]ruleBy default, the SELinux boolean unconfined_login is enabled.
If this setting is disabled, it should be enabled.
To enable the unconfined_login SELinux boolean, run the following command:
$ sudo setsebool -P unconfined_login on Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_unconfined_login="(N/A)"
setsebool -P unconfined_login $var_unconfined_login
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_unconfined_login # promote to variable
set_fact:
var_unconfined_login: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean unconfined_login accordingly
seboolean:
name: unconfined_login
state: "{{ var_unconfined_login }}"
persistent: yes
tags:
- sebool_unconfined_login
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the selinuxuser_postgresql_connect_enabled SELinux Boolean
[ref]ruleBy default, the SELinux boolean selinuxuser_postgresql_connect_enabled is disabled.
If this setting is enabled, it should be disabled.
To disable the selinuxuser_postgresql_connect_enabled SELinux boolean, run the following command:
$ sudo setsebool -P selinuxuser_postgresql_connect_enabled off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_selinuxuser_postgresql_connect_enabled="(N/A)"
setsebool -P selinuxuser_postgresql_connect_enabled $var_selinuxuser_postgresql_connect_enabled
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_selinuxuser_postgresql_connect_enabled # promote to variable
set_fact:
var_selinuxuser_postgresql_connect_enabled: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean selinuxuser_postgresql_connect_enabled accordingly
seboolean:
name: selinuxuser_postgresql_connect_enabled
state: "{{ var_selinuxuser_postgresql_connect_enabled }}"
persistent: yes
tags:
- sebool_selinuxuser_postgresql_connect_enabled
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the abrt_upload_watch_anon_write SELinux Boolean
[ref]ruleBy default, the SELinux boolean abrt_upload_watch_anon_write is enabled.
This setting should be disabled as it allows the Automatic Bug Report Tool (ABRT)
to modify public files used for public file transfer services.
To disable the abrt_upload_watch_anon_write SELinux boolean, run the following command:
$ sudo setsebool -P abrt_upload_watch_anon_write off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_abrt_upload_watch_anon_write="(N/A)"
setsebool -P abrt_upload_watch_anon_write $var_abrt_upload_watch_anon_write
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_abrt_upload_watch_anon_write # promote to variable
set_fact:
var_abrt_upload_watch_anon_write: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean abrt_upload_watch_anon_write accordingly
seboolean:
name: abrt_upload_watch_anon_write
state: "{{ var_abrt_upload_watch_anon_write }}"
persistent: yes
tags:
- sebool_abrt_upload_watch_anon_write
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-80421-1
- NIST-800-53-TBD
- NIST-800-171-3.7.2
- DISA-STIG-RHEL-07-TBD
|
Disable the daemons_use_tty SELinux Boolean
[ref]ruleBy default, the SELinux boolean daemons_use_tty is disabled.
If this setting is enabled, it should be disabled.
To disable the daemons_use_tty SELinux boolean, run the following command:
$ sudo setsebool -P daemons_use_tty off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_daemons_use_tty="(N/A)"
setsebool -P daemons_use_tty $var_daemons_use_tty
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_daemons_use_tty # promote to variable
set_fact:
var_daemons_use_tty: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean daemons_use_tty accordingly
seboolean:
name: daemons_use_tty
state: "{{ var_daemons_use_tty }}"
persistent: yes
tags:
- sebool_daemons_use_tty
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Enable the staff_exec_content SELinux Boolean
[ref]ruleBy default, the SELinux boolean staff_exec_content is enabled.
If this setting is disabled, it should be enabled.
To enable the staff_exec_content SELinux boolean, run the following command:
$ sudo setsebool -P staff_exec_content on Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_staff_exec_content="(N/A)"
setsebool -P staff_exec_content $var_staff_exec_content
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_staff_exec_content # promote to variable
set_fact:
var_staff_exec_content: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean staff_exec_content accordingly
seboolean:
name: staff_exec_content
state: "{{ var_staff_exec_content }}"
persistent: yes
tags:
- sebool_staff_exec_content
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Configure the selinuxuser_direct_dri_enabled SELinux Boolean
[ref]ruleBy default, the SELinux boolean selinuxuser_direct_dri_enabled is enabled.
If XWindows is not installed or used on the system, this setting should be disabled.
Otherwise, enable it.
To disable the selinuxuser_direct_dri_enabled SELinux boolean, run the following command:
$ sudo setsebool -P selinuxuser_direct_dri_enabled off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_selinuxuser_direct_dri_enabled="(N/A)"
setsebool -P selinuxuser_direct_dri_enabled $var_selinuxuser_direct_dri_enabled
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_selinuxuser_direct_dri_enabled # promote to variable
set_fact:
var_selinuxuser_direct_dri_enabled: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean selinuxuser_direct_dri_enabled accordingly
seboolean:
name: selinuxuser_direct_dri_enabled
state: "{{ var_selinuxuser_direct_dri_enabled }}"
persistent: yes
tags:
- sebool_selinuxuser_direct_dri_enabled
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the xserver_execmem SELinux Boolean
[ref]ruleBy default, the SELinux boolean xserver_execmem is disabled.
If this setting is enabled, it should be disabled.
To disable the xserver_execmem SELinux boolean, run the following command:
$ sudo setsebool -P xserver_execmem off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_xserver_execmem="(N/A)"
setsebool -P xserver_execmem $var_xserver_execmem
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_xserver_execmem # promote to variable
set_fact:
var_xserver_execmem: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean xserver_execmem accordingly
seboolean:
name: xserver_execmem
state: "{{ var_xserver_execmem }}"
persistent: yes
tags:
- sebool_xserver_execmem
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the xserver_clients_write_xshm SELinux Boolean
[ref]ruleBy default, the SELinux boolean xserver_clients_write_xshm is disabled.
If this setting is enabled, it should be disabled.
To disable the xserver_clients_write_xshm SELinux boolean, run the following command:
$ sudo setsebool -P xserver_clients_write_xshm off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_xserver_clients_write_xshm="(N/A)"
setsebool -P xserver_clients_write_xshm $var_xserver_clients_write_xshm
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_xserver_clients_write_xshm # promote to variable
set_fact:
var_xserver_clients_write_xshm: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean xserver_clients_write_xshm accordingly
seboolean:
name: xserver_clients_write_xshm
state: "{{ var_xserver_clients_write_xshm }}"
persistent: yes
tags:
- sebool_xserver_clients_write_xshm
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the use_ecryptfs_home_dirs SELinux Boolean
[ref]ruleBy default, the SELinux boolean use_ecryptfs_home_dirs is disabled.
If this setting is enabled, it should be disabled.
To disable the use_ecryptfs_home_dirs SELinux boolean, run the following command:
$ sudo setsebool -P use_ecryptfs_home_dirs off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_use_ecryptfs_home_dirs="(N/A)"
setsebool -P use_ecryptfs_home_dirs $var_use_ecryptfs_home_dirs
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_use_ecryptfs_home_dirs # promote to variable
set_fact:
var_use_ecryptfs_home_dirs: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean use_ecryptfs_home_dirs accordingly
seboolean:
name: use_ecryptfs_home_dirs
state: "{{ var_use_ecryptfs_home_dirs }}"
persistent: yes
tags:
- sebool_use_ecryptfs_home_dirs
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the xguest_exec_content SELinux Boolean
[ref]ruleBy default, the SELinux boolean xguest_exec_content is enabled.
This setting should be disabled as guest users should not be able to run
executables.
To disable the xguest_exec_content SELinux boolean, run the following command:
$ sudo setsebool -P xguest_exec_content off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_xguest_exec_content="(N/A)"
setsebool -P xguest_exec_content $var_xguest_exec_content
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_xguest_exec_content # promote to variable
set_fact:
var_xguest_exec_content: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean xguest_exec_content accordingly
seboolean:
name: xguest_exec_content
state: "{{ var_xguest_exec_content }}"
persistent: yes
tags:
- sebool_xguest_exec_content
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the domain_kernel_load_modules SELinux Boolean
[ref]ruleBy default, the SELinux boolean domain_kernel_load_modules is disabled.
If this setting is enabled, it should be disabled.
To disable the domain_kernel_load_modules SELinux boolean, run the following command:
$ sudo setsebool -P domain_kernel_load_modules off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_domain_kernel_load_modules="(N/A)"
setsebool -P domain_kernel_load_modules $var_domain_kernel_load_modules
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_domain_kernel_load_modules # promote to variable
set_fact:
var_domain_kernel_load_modules: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean domain_kernel_load_modules accordingly
seboolean:
name: domain_kernel_load_modules
state: "{{ var_domain_kernel_load_modules }}"
persistent: yes
tags:
- sebool_domain_kernel_load_modules
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the xdm_bind_vnc_tcp_port SELinux Boolean
[ref]ruleBy default, the SELinux boolean xdm_bind_vnc_tcp_port is disabled.
If this setting is enabled, it should be disabled.
To disable the xdm_bind_vnc_tcp_port SELinux boolean, run the following command:
$ sudo setsebool -P xdm_bind_vnc_tcp_port off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_xdm_bind_vnc_tcp_port="(N/A)"
setsebool -P xdm_bind_vnc_tcp_port $var_xdm_bind_vnc_tcp_port
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_xdm_bind_vnc_tcp_port # promote to variable
set_fact:
var_xdm_bind_vnc_tcp_port: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean xdm_bind_vnc_tcp_port accordingly
seboolean:
name: xdm_bind_vnc_tcp_port
state: "{{ var_xdm_bind_vnc_tcp_port }}"
persistent: yes
tags:
- sebool_xdm_bind_vnc_tcp_port
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Enable the domain_fd_use SELinux Boolean
[ref]ruleBy default, the SELinux boolean domain_fd_use is enabled.
If this setting is disabled, it should be enabled.
To enable the domain_fd_use SELinux boolean, run the following command:
$ sudo setsebool -P domain_fd_use on Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_domain_fd_use="(N/A)"
setsebool -P domain_fd_use $var_domain_fd_use
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_domain_fd_use # promote to variable
set_fact:
var_domain_fd_use: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean domain_fd_use accordingly
seboolean:
name: domain_fd_use
state: "{{ var_domain_fd_use }}"
persistent: yes
tags:
- sebool_domain_fd_use
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the secure_mode SELinux Boolean
[ref]ruleBy default, the SELinux boolean secure_mode is disabled.
If this setting is enabled, it should be disabled.
To disable the secure_mode SELinux boolean, run the following command:
$ sudo setsebool -P secure_mode off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_secure_mode="(N/A)"
setsebool -P secure_mode $var_secure_mode
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_secure_mode # promote to variable
set_fact:
var_secure_mode: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean secure_mode accordingly
seboolean:
name: secure_mode
state: "{{ var_secure_mode }}"
persistent: yes
tags:
- sebool_secure_mode
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the mmap_low_allowed SELinux Boolean
[ref]ruleBy default, the SELinux boolean mmap_low_allowed is disabled.
If this setting is enabled, it should be disabled.
To disable the mmap_low_allowed SELinux boolean, run the following command:
$ sudo setsebool -P mmap_low_allowed off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_mmap_low_allowed="(N/A)"
setsebool -P mmap_low_allowed $var_mmap_low_allowed
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_mmap_low_allowed # promote to variable
set_fact:
var_mmap_low_allowed: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean mmap_low_allowed accordingly
seboolean:
name: mmap_low_allowed
state: "{{ var_mmap_low_allowed }}"
persistent: yes
tags:
- sebool_mmap_low_allowed
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the selinuxuser_share_music SELinux Boolean
[ref]ruleBy default, the SELinux boolean selinuxuser_share_music is disabled.
If this setting is enabled, it should be disabled.
To disable the selinuxuser_share_music SELinux boolean, run the following command:
$ sudo setsebool -P selinuxuser_share_music off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_selinuxuser_share_music="(N/A)"
setsebool -P selinuxuser_share_music $var_selinuxuser_share_music
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_selinuxuser_share_music # promote to variable
set_fact:
var_selinuxuser_share_music: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean selinuxuser_share_music accordingly
seboolean:
name: selinuxuser_share_music
state: "{{ var_selinuxuser_share_music }}"
persistent: yes
tags:
- sebool_selinuxuser_share_music
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Enable the selinuxuser_execmod SELinux Boolean
[ref]ruleBy default, the SELinux boolean selinuxuser_execmod is enabled.
If this setting is disabled, it should be enabled.
To enable the selinuxuser_execmod SELinux boolean, run the following command:
$ sudo setsebool -P selinuxuser_execmod on Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_selinuxuser_execmod="(N/A)"
setsebool -P selinuxuser_execmod $var_selinuxuser_execmod
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_selinuxuser_execmod # promote to variable
set_fact:
var_selinuxuser_execmod: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean selinuxuser_execmod accordingly
seboolean:
name: selinuxuser_execmod
state: "{{ var_selinuxuser_execmod }}"
persistent: yes
tags:
- sebool_selinuxuser_execmod
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the selinuxuser_udp_server SELinux Boolean
[ref]ruleBy default, the SELinux boolean selinuxuser_udp_server is disabled.
If this setting is enabled, it should be disabled.
To disable the selinuxuser_udp_server SELinux boolean, run the following command:
$ sudo setsebool -P selinuxuser_udp_server off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_selinuxuser_udp_server="(N/A)"
setsebool -P selinuxuser_udp_server $var_selinuxuser_udp_server
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_selinuxuser_udp_server # promote to variable
set_fact:
var_selinuxuser_udp_server: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean selinuxuser_udp_server accordingly
seboolean:
name: selinuxuser_udp_server
state: "{{ var_selinuxuser_udp_server }}"
persistent: yes
tags:
- sebool_selinuxuser_udp_server
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the cron_system_cronjob_use_shares SELinux Boolean
[ref]ruleBy default, the SELinux boolean cron_system_cronjob_use_shares is disabled.
If this setting is enabled, it should be disabled.
To disable the cron_system_cronjob_use_shares SELinux boolean, run the following command:
$ sudo setsebool -P cron_system_cronjob_use_shares off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_cron_system_cronjob_use_shares="(N/A)"
setsebool -P cron_system_cronjob_use_shares $var_cron_system_cronjob_use_shares
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_cron_system_cronjob_use_shares # promote to variable
set_fact:
var_cron_system_cronjob_use_shares: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean cron_system_cronjob_use_shares accordingly
seboolean:
name: cron_system_cronjob_use_shares
state: "{{ var_cron_system_cronjob_use_shares }}"
persistent: yes
tags:
- sebool_cron_system_cronjob_use_shares
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the xguest_connect_network SELinux Boolean
[ref]ruleBy default, the SELinux boolean xguest_connect_network is enabled.
This setting should be disabled as guest users should not be able to configure
NetworkManager .
To disable the xguest_connect_network SELinux boolean, run the following command:
$ sudo setsebool -P xguest_connect_network off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_xguest_connect_network="(N/A)"
setsebool -P xguest_connect_network $var_xguest_connect_network
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_xguest_connect_network # promote to variable
set_fact:
var_xguest_connect_network: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean xguest_connect_network accordingly
seboolean:
name: xguest_connect_network
state: "{{ var_xguest_connect_network }}"
persistent: yes
tags:
- sebool_xguest_connect_network
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the xdm_write_home SELinux Boolean
[ref]ruleBy default, the SELinux boolean xdm_write_home is disabled.
If this setting is enabled, it should be disabled.
To disable the xdm_write_home SELinux boolean, run the following command:
$ sudo setsebool -P xdm_write_home off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_xdm_write_home="(N/A)"
setsebool -P xdm_write_home $var_xdm_write_home
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_xdm_write_home # promote to variable
set_fact:
var_xdm_write_home: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean xdm_write_home accordingly
seboolean:
name: xdm_write_home
state: "{{ var_xdm_write_home }}"
persistent: yes
tags:
- sebool_xdm_write_home
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Enable the sysadm_exec_content SELinux Boolean
[ref]ruleBy default, the SELinux boolean sysadm_exec_content is enabled.
If this setting is disabled, it should be enabled.
To enable the sysadm_exec_content SELinux boolean, run the following command:
$ sudo setsebool -P sysadm_exec_content on Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_sysadm_exec_content="(N/A)"
setsebool -P sysadm_exec_content $var_sysadm_exec_content
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_sysadm_exec_content # promote to variable
set_fact:
var_sysadm_exec_content: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean sysadm_exec_content accordingly
seboolean:
name: sysadm_exec_content
state: "{{ var_sysadm_exec_content }}"
persistent: yes
tags:
- sebool_sysadm_exec_content
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the xguest_use_bluetooth SELinux Boolean
[ref]ruleBy default, the SELinux boolean xguest_use_bluetooth is enabled.
This setting should be disabled as guests users should not be able to access
or use bluetooth.
To disable the xguest_use_bluetooth SELinux boolean, run the following command:
$ sudo setsebool -P xguest_use_bluetooth off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_xguest_use_bluetooth="(N/A)"
setsebool -P xguest_use_bluetooth $var_xguest_use_bluetooth
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_xguest_use_bluetooth # promote to variable
set_fact:
var_xguest_use_bluetooth: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean xguest_use_bluetooth accordingly
seboolean:
name: xguest_use_bluetooth
state: "{{ var_xguest_use_bluetooth }}"
persistent: yes
tags:
- sebool_xguest_use_bluetooth
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the logging_syslogd_can_sendmail SELinux Boolean
[ref]ruleBy default, the SELinux boolean logging_syslogd_can_sendmail is disabled.
If this setting is enabled, it should be disabled.
To disable the logging_syslogd_can_sendmail SELinux boolean, run the following command:
$ sudo setsebool -P logging_syslogd_can_sendmail off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_logging_syslogd_can_sendmail="(N/A)"
setsebool -P logging_syslogd_can_sendmail $var_logging_syslogd_can_sendmail
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_logging_syslogd_can_sendmail # promote to variable
set_fact:
var_logging_syslogd_can_sendmail: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean logging_syslogd_can_sendmail accordingly
seboolean:
name: logging_syslogd_can_sendmail
state: "{{ var_logging_syslogd_can_sendmail }}"
persistent: yes
tags:
- sebool_logging_syslogd_can_sendmail
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the guest_exec_content SELinux Boolean
[ref]ruleBy default, the SELinux boolean guest_exec_content is enabled.
This setting should be disabled as no guest accounts should be used.
To enable the guest_exec_content SELinux boolean, run the following command:
$ sudo setsebool -P guest_exec_content on Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_guest_exec_content="(N/A)"
setsebool -P guest_exec_content $var_guest_exec_content
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_guest_exec_content # promote to variable
set_fact:
var_guest_exec_content: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean guest_exec_content accordingly
seboolean:
name: guest_exec_content
state: "{{ var_guest_exec_content }}"
persistent: yes
tags:
- sebool_guest_exec_content
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the daemons_dump_core SELinux Boolean
[ref]ruleBy default, the SELinux boolean daemons_dump_core is disabled.
If this setting is enabled, it should be disabled.
To disable the daemons_dump_core SELinux boolean, run the following command:
$ sudo setsebool -P daemons_dump_core off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_daemons_dump_core="(N/A)"
setsebool -P daemons_dump_core $var_daemons_dump_core
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_daemons_dump_core # promote to variable
set_fact:
var_daemons_dump_core: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean daemons_dump_core accordingly
seboolean:
name: daemons_dump_core
state: "{{ var_daemons_dump_core }}"
persistent: yes
tags:
- sebool_daemons_dump_core
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the xdm_exec_bootloader SELinux Boolean
[ref]ruleBy default, the SELinux boolean xdm_exec_bootloader is disabled.
If this setting is enabled, it should be disabled.
To disable the xdm_exec_bootloader SELinux boolean, run the following command:
$ sudo setsebool -P xdm_exec_bootloader off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_xdm_exec_bootloader="(N/A)"
setsebool -P xdm_exec_bootloader $var_xdm_exec_bootloader
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_xdm_exec_bootloader # promote to variable
set_fact:
var_xdm_exec_bootloader: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean xdm_exec_bootloader accordingly
seboolean:
name: xdm_exec_bootloader
state: "{{ var_xdm_exec_bootloader }}"
persistent: yes
tags:
- sebool_xdm_exec_bootloader
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Enable the fips_mode SELinux Boolean
[ref]ruleBy default, the SELinux boolean fips_mode is enabled.
This allows all SELinux domains to execute in fips_mode .
If this setting is disabled, it should be enabled.
To enable the fips_mode SELinux boolean, run the following command:
$ sudo setsebool -P fips_mode on Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_fips_mode="(N/A)"
setsebool -P fips_mode $var_fips_mode
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_fips_mode # promote to variable
set_fact:
var_fips_mode: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean fips_mode accordingly
seboolean:
name: fips_mode
state: "{{ var_fips_mode }}"
persistent: yes
tags:
- sebool_fips_mode
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-80418-7
- NIST-800-53-SC-13
- NIST-800-171-3.13.11
- DISA-STIG-RHEL-07-TBD
|
Disable the polyinstantiation_enabled SELinux Boolean
[ref]ruleBy default, the SELinux boolean polyinstantiation_enabled is disabled.
If this setting is enabled, it should be disabled.
To disable the polyinstantiation_enabled SELinux boolean, run the following command:
$ sudo setsebool -P polyinstantiation_enabled off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_polyinstantiation_enabled="(N/A)"
setsebool -P polyinstantiation_enabled $var_polyinstantiation_enabled
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_polyinstantiation_enabled # promote to variable
set_fact:
var_polyinstantiation_enabled: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean polyinstantiation_enabled accordingly
seboolean:
name: polyinstantiation_enabled
state: "{{ var_polyinstantiation_enabled }}"
persistent: yes
tags:
- sebool_polyinstantiation_enabled
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the selinuxuser_use_ssh_chroot SELinux Boolean
[ref]ruleBy default, the SELinux boolean selinuxuser_use_ssh_chroot is disabled.
If this setting is enabled, it should be disabled.
To disable the selinuxuser_use_ssh_chroot SELinux boolean, run the following command:
$ sudo setsebool -P selinuxuser_use_ssh_chroot off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_selinuxuser_use_ssh_chroot="(N/A)"
setsebool -P selinuxuser_use_ssh_chroot $var_selinuxuser_use_ssh_chroot
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_selinuxuser_use_ssh_chroot # promote to variable
set_fact:
var_selinuxuser_use_ssh_chroot: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean selinuxuser_use_ssh_chroot accordingly
seboolean:
name: selinuxuser_use_ssh_chroot
state: "{{ var_selinuxuser_use_ssh_chroot }}"
persistent: yes
tags:
- sebool_selinuxuser_use_ssh_chroot
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Enable the selinuxuser_ping SELinux Boolean
[ref]ruleBy default, the SELinux boolean selinuxuser_ping is enabled.
If this setting is disabled, it should be enabled as it allows confined users
to use ping and traceroute which is helpful for network troubleshooting.
To enable the selinuxuser_ping SELinux boolean, run the following command:
$ sudo setsebool -P selinuxuser_ping on Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_selinuxuser_ping="(N/A)"
setsebool -P selinuxuser_ping $var_selinuxuser_ping
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_selinuxuser_ping # promote to variable
set_fact:
var_selinuxuser_ping: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean selinuxuser_ping accordingly
seboolean:
name: selinuxuser_ping
state: "{{ var_selinuxuser_ping }}"
persistent: yes
tags:
- sebool_selinuxuser_ping
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the secure_mode_policyload SELinux Boolean
[ref]ruleBy default, the SELinux boolean secure_mode_policyload is disabled.
If this setting is enabled, it should be disabled.
To disable the secure_mode_policyload SELinux boolean, run the following command:
$ sudo setsebool -P secure_mode_policyload off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_secure_mode_policyload="(N/A)"
setsebool -P secure_mode_policyload $var_secure_mode_policyload
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_secure_mode_policyload # promote to variable
set_fact:
var_secure_mode_policyload: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean secure_mode_policyload accordingly
seboolean:
name: secure_mode_policyload
state: "{{ var_secure_mode_policyload }}"
persistent: yes
tags:
- sebool_secure_mode_policyload
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Disable the selinuxuser_execheap SELinux Boolean
[ref]ruleBy default, the SELinux boolean selinuxuser_execheap is disabled.
If this setting is enabled, it should be disabled.
To disable the selinuxuser_execheap SELinux boolean, run the following command:
$ sudo setsebool -P selinuxuser_execheap off Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_selinuxuser_execheap="(N/A)"
setsebool -P selinuxuser_execheap $var_selinuxuser_execheap
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_selinuxuser_execheap # promote to variable
set_fact:
var_selinuxuser_execheap: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean selinuxuser_execheap accordingly
seboolean:
name: selinuxuser_execheap
state: "{{ var_selinuxuser_execheap }}"
persistent: yes
tags:
- sebool_selinuxuser_execheap
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- DISA-STIG-RHEL-07-TBD
|
Enable the secadm_exec_content SELinux Boolean
[ref]ruleBy default, the SELinux boolean secadm_exec_content is enabled.
If this setting is disabled, it should be enabled.
To enable the secadm_exec_content SELinux boolean, run the following command:
$ sudo setsebool -P secadm_exec_content on Rationale:Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
var_secadm_exec_content="(N/A)"
setsebool -P secadm_exec_content $var_secadm_exec_content
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: XCCDF Value var_secadm_exec_content # promote to variable
set_fact:
var_secadm_exec_content: (N/A)
tags:
- always
- name: Ensure libsemanage-python installed
package:
name: libsemanage-python
state: latest
- name: Set SELinux boolean secadm_exec_content accordingly
seboolean:
name: secadm_exec_content
state: "{{ var_secadm_exec_content }}"
persistent: yes
tags:
- sebool_secadm_exec_content
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-RHEL7-CCE-TBD
- NIST-800-53-TBD
- DISA-STIG-RHEL-07-TBD
|
Configure SELinux Policy
[ref]ruleThe SELinux targeted policy is appropriate for
general-purpose desktops and servers, as well as systems in many other roles.
To configure the system to use this policy, add or correct the following line
in /etc/selinux/config :
SELINUXTYPE=targeted
Other policies, such as mls , provide additional security labeling
and greater confinement but are not compatible with many general-purpose
use cases.Rationale:Setting the SELinux policy to targeted or a more specialized policy
ensures the system will confine processes that are likely to be
targeted for exploitation, such as network or system services.
Note: During the development or debugging of SELinux modules, it is common to
temporarily place non-production systems in permissive mode. In such
temporary cases, SELinux policies should be developed, and once work
is completed, the system should be reconfigured to
targeted . References:
SV-86615r3_rule, 1.6.1.3, 3.1.2, 3.7.2, CCI-002696, 164.308(a)(1)(ii)(D), 164.308(a)(3), 164.308(a)(4), 164.310(b), 164.310(c), 164.312(a), 164.312(e), AC-3, AC-3(3), AC-3(4), AC-4, AC-6, AU-9, SI-6(a), SRG-OS-000445-GPOS-00199 Remediation Shell script: (show)
var_selinux_policy_name="targeted"
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysconfig/selinux' '^SELINUXTYPE=' $var_selinux_policy_name 'CCE-27279-9' '%s=%s'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: XCCDF Value var_selinux_policy_name # promote to variable
set_fact:
var_selinux_policy_name: targeted
tags:
- always
- name: "Configure SELinux Policy"
lineinfile:
path: /etc/sysconfig/selinux
regexp: '^SELINUXTYPE='
line: "SELINUXTYPE={{ var_selinux_policy_name }}"
create: yes
tags:
- selinux_policytype
- high_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27279-9
- NIST-800-53-AC-3
- NIST-800-53-AC-3(3)
- NIST-800-53-AC-3(4)
- NIST-800-53-AC-4
- NIST-800-53-AC-6
- NIST-800-53-AU-9
- NIST-800-53-SI-6(a)
- NIST-800-171-3.1.2
- NIST-800-171-3.7.2
- DISA-STIG-RHEL-07-020220
|
Ensure SELinux Not Disabled in /etc/default/grub
[ref]ruleSELinux can be disabled at boot time by an argument in
/etc/default/grub .
Remove any instances of selinux=0 from the kernel arguments in that
file to prevent SELinux from being disabled at boot. Rationale:Disabling a major host protection feature, such as SELinux, at boot time prevents
it from confining system services at boot time. Further, it increases
the chances that it will remain off during system operation. References:
1.6.1.1, 3.1.2, 3.7.2, CCI-000022, CCI-000032, 164.308(a)(1)(ii)(D), 164.308(a)(3), 164.308(a)(4), 164.310(b), 164.310(c), 164.312(a), 164.312(e), AC-3, AC-3(3), AC-3(4), AC-4, AC-6, AU-9, SI-6(a) Remediation Shell script: (show)
sed -i --follow-symlinks "s/selinux=0//gI" /etc/default/grub /etc/grub2.cfg /etc/grub.d/*
sed -i --follow-symlinks "s/enforcing=0//gI" /etc/default/grub /etc/grub2.cfg /etc/grub.d/*
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: Ensure SELinux Not Disabled in /etc/default/grub
replace:
dest: /etc/default/grub
regexp: selinux=0
tags:
- enable_selinux_bootloader
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-26961-3
- NIST-800-53-AC-3
- NIST-800-53-AC-3(3)
- NIST-800-53-AC-3(4)
- NIST-800-53-AC-4
- NIST-800-53-AC-6
- NIST-800-53-AU-9
- NIST-800-53-SI-6(a)
- NIST-800-171-3.1.2
- NIST-800-171-3.7.2
|
Ensure No Daemons are Unconfined by SELinux
[ref]ruleDaemons for which the SELinux policy does not contain rules will inherit the
context of the parent process. Because daemons are launched during
startup and descend from the init process, they inherit the initrc_t context.
To check for unconfined daemons, run the following command:
$ sudo ps -eZ | egrep "initrc" | egrep -vw "tr|ps|egrep|bash|awk" | tr ':' ' ' | awk '{ print $NF }'
It should produce no output in a well-configured system.Rationale:Daemons which run with the initrc_t context may cause AVC denials,
or allow privileges that the daemon does not require. References:
1.6.1.6, 3.1.2, 3.1.5, 3.7.2, 164.308(a)(1)(ii)(D), 164.308(a)(3), 164.308(a)(4), 164.310(b), 164.310(c), 164.312(a), 164.312(e), AC-6, AU-9, CM-7 |
Ensure No Device Files are Unlabeled by SELinux
[ref]ruleDevice files, which are used for communication with important
system resources, should be labeled with proper SELinux types. If any device
files do not carry the SELinux type device_t , report the bug so
that policy can be corrected. Supply information about what the device is
and what programs use it.
To check for unlabeled device files, run the following command:
$ sudo find /dev -context *:device_t:* \( -type c -o -type b \) -printf "%p %Z\n"
It should produce no output in a well-configured system.Rationale:If a device file carries the SELinux type device_t , then SELinux
cannot properly restrict access to the device file. References:
SV-86663r1_rule, 3.1.2, 3.1.5, 3.7.2, CCI-000022, CCI-000032, CCI-000368, CCI-000318, CCI-001812, CCI-001813, CCI-001814, AC-6, AU-9, CM-3(f), CM-7, SRG-OS-000480-GPOS-00227 |
Ensure SELinux State is Enforcing
[ref]ruleThe SELinux state should be set to enforcing at
system boot time. In the file /etc/selinux/config , add or correct the
following line to configure the system to boot into enforcing mode:
SELINUX=enforcing Rationale:Setting the SELinux state to enforcing ensures SELinux is able to confine
potentially compromised processes to the security policy, which is designed to
prevent them from causing damage to the system or further elevating their
privileges. References:
SV-86613r2_rule, 1.6.1.2, 3.1.2, 3.7.2, CCI-002165, CCI-002696, 164.308(a)(1)(ii)(D), 164.308(a)(3), 164.308(a)(4), 164.310(b), 164.310(c), 164.312(a), 164.312(e), AC-3, AC-3(3), AC-3(4), AC-4, AC-6, AU-9, SI-6(a), SRG-OS-000445-GPOS-00199 Remediation Shell script: (show)
var_selinux_state="enforcing"
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state 'CCE-27334-2' '%s=%s'
fixfiles onboot
fixfiles -f relabel
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: XCCDF Value var_selinux_state # promote to variable
set_fact:
var_selinux_state: enforcing
tags:
- always
- name: "Ensure SELinux State is Enforcing"
lineinfile:
path: /etc/sysconfig/selinux
regexp: '^SELINUX='
line: "SELINUX={{ var_selinux_state }}"
create: yes
tags:
- selinux_state
- high_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27334-2
- NIST-800-53-AC-3
- NIST-800-53-AC-3(3)
- NIST-800-53-AC-3(4)
- NIST-800-53-AC-4
- NIST-800-53-AC-6
- NIST-800-53-AU-9
- NIST-800-53-SI-6(a)
- NIST-800-171-3.1.2
- NIST-800-171-3.7.2
- DISA-STIG-RHEL-07-020210
|
Account and Access Control
[ref]groupIn traditional Unix security, if an attacker gains
shell access to a certain login account, they can perform any action
or access any file to which that account has access. Therefore,
making it more difficult for unauthorized people to gain shell
access to accounts, particularly to privileged accounts, is a
necessary part of securing a system. This section introduces
mechanisms for restricting access to accounts under
Red Hat Enterprise Linux 7. |
contains 50 rules |
Protect Accounts by Restricting Password-Based Login
[ref]groupConventionally, Unix shell accounts are accessed by
providing a username and password to a login program, which tests
these values for correctness using the /etc/passwd and
/etc/shadow files. Password-based login is vulnerable to
guessing of weak passwords, and to sniffing and man-in-the-middle
attacks against passwords entered over a network or at an insecure
console. Therefore, mechanisms for accessing accounts by entering
usernames and passwords should be restricted to those which are
operationally necessary. |
contains 12 rules |
Restrict Root Logins
[ref]groupDirect root logins should be allowed only for emergency use.
In normal situations, the administrator should access the system
via a unique unprivileged account, and then use su or sudo to execute
privileged commands. Discouraging administrators from accessing the
root account directly ensures an audit trail in organizations with
multiple administrators. Locking down the channels through which
root can connect directly also reduces opportunities for
password-guessing against the root account. The login program
uses the file /etc/securetty to determine which interfaces
should allow root logins.
The virtual devices /dev/console
and /dev/tty* represent the system consoles (accessible via
the Ctrl-Alt-F1 through Ctrl-Alt-F6 keyboard sequences on a default
installation). The default securetty file also contains /dev/vc/* .
These are likely to be deprecated in most environments, but may be retained
for compatibility. Root should also be prohibited from connecting
via network protocols. Other sections of this document
include guidance describing how to prevent root from logging in via SSH. |
contains 4 rules |
Verify Only Root Has UID 0
[ref]ruleIf any account other than root has a UID of 0, this misconfiguration should
be investigated and the accounts other than root should be removed or
have their UID changed.
If the account is associated with system commands or applications the UID should be changed
to one greater than "0" but less than "1000." Otherwise assign a UID greater than "1000" that
has not already been assigned. Rationale:An account has root authority if it has a UID of 0. Multiple accounts
with a UID of 0 afford more opportunity for potential intruders to
guess a password for a privileged account. Proper configuration of
sudo is recommended to afford multiple system administrators
access to root privileges in an accountable manner. Remediation Shell script: (show)
awk -F: '$3 == 0 && $1 != "root" { print $1 }' /etc/passwd | xargs passwd -l
|
Direct root Logins Not Allowed
[ref]ruleTo further limit access to the root account, administrators
can disable root logins at the console by editing the /etc/securetty file.
This file lists all devices the root user is allowed to login to. If the file does
not exist at all, the root user can login through any communication device on the
system, whether via the console or via a raw network interface. This is dangerous
as user can login to the system as root via Telnet, which sends the password in
plain text over the network. By default, Red Hat Enteprise Linux's
/etc/securetty file only allows the root user to login at the console
physically attached to the system. To prevent root from logging in, remove the
contents of this file. To prevent direct root logins, remove the contents of this
file by typing the following command:
$ sudo echo > /etc/securetty
Rationale:Disabling direct root logins ensures proper accountability and multifactor
authentication to privileged accounts. Users will first login, then escalate
to privileged (root) access via su / sudo. This is required for FISMA Low
and FISMA Moderate systems. References:
5.5, 3.1.1, 3.1.6, 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii), IA-2(1) Remediation Shell script: (show) Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: "Direct root Logins Not Allowed"
shell: echo > /etc/securetty
tags:
- no_direct_root_logins
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27294-8
- NIST-800-53-IA-2(1)
- NIST-800-171-3.1.1
- NIST-800-171-3.1.6
|
Restrict Virtual Console Root Logins
[ref]ruleTo restrict root logins through the (deprecated) virtual console devices,
ensure lines of this form do not appear in /etc/securetty :
vc/1
vc/2
vc/3
vc/4 Rationale:Preventing direct root login to virtual console devices
helps ensure accountability for actions taken on the system
using the root account. References:
3.1.1, 3.1.5, CCI-000770, 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii), AC-6(2) Remediation Shell script: (show)
sed -i '/^vc\//d' /etc/securetty
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: "Restrict Virtual Console Root Logins"
lineinfile:
dest: /etc/securetty
regexp: '^vc'
state: absent
tags:
- securetty_root_login_console_only
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27318-5
- NIST-800-53-AC-6(2)
- NIST-800-171-3.1.1
- NIST-800-171-3.1.5
|
Restrict Serial Port Root Logins
[ref]ruleTo restrict root logins on serial ports,
ensure lines of this form do not appear in /etc/securetty :
ttyS0
ttyS1 Rationale:Preventing direct root login to serial port interfaces
helps ensure accountability for actions taken on the systems
using the root account. References:
3.1.1, 3.1.5, CCI-000770, 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii), AC-6(2) Remediation Shell script: (show)
sed -i '/ttyS/d' /etc/securetty
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: "Restrict Serial Port Root Logins"
lineinfile:
dest: /etc/securetty
regexp: 'ttyS[0-9]'
state: absent
tags:
- restrict_serial_port_logins
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27268-2
- NIST-800-53-AC-6(2)
- NIST-800-171-3.1.1
- NIST-800-171-3.1.5
|
Verify Proper Storage and Existence of Password
Hashes
[ref]groupBy default, password hashes for local accounts are stored
in the second field (colon-separated) in
/etc/shadow . This file should be readable only by
processes running with root credentials, preventing users from
casually accessing others' password hashes and attempting
to crack them.
However, it remains possible to misconfigure the system
and store password hashes
in world-readable files such as /etc/passwd , or
to even store passwords themselves in plaintext on the system.
Using system-provided tools for password change/creation
should allow administrators to avoid such misconfiguration. |
contains 3 rules |
Prevent Log In to Accounts With Empty Password
[ref]ruleIf an account is configured for password authentication
but does not have an assigned password, it may be possible to log
into the account without authentication. Remove any instances of the nullok
option in /etc/pam.d/system-auth to
prevent logins with empty passwords. Rationale:If an account has an empty password, anyone could log in and
run commands with the privileges of that account. Accounts with
empty passwords should never be used in operational environments. References:
FIA_AFL.1, SV-86561r2_rule, 5.5.2, 3.1.1, 3.1.5, CCI-000366, 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii), AC-6, IA-5(b), IA-5(c), IA-5(1)(a), Req-8.2.3, SRG-OS-000480-GPOS-00227 Remediation Shell script: (show)
sed --follow-symlinks -i 's/\<nullok\>//g' /etc/pam.d/system-auth
sed --follow-symlinks -i 's/\<nullok\>//g' /etc/pam.d/password-auth
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Strategy: | configure |
---|
- name: "Prevent Log In to Accounts With Empty Password - system-auth"
replace:
dest: /etc/pam.d/system-auth
follow: yes
regexp: 'nullok'
tags:
- no_empty_passwords
- high_severity
- configure_strategy
- low_complexity
- medium_disruption
- CCE-27286-4
- NIST-800-53-AC-6
- NIST-800-53-IA-5(b)
- NIST-800-53-IA-5(c)
- NIST-800-53-IA-5(1)(a)
- NIST-800-171-3.1.1
- NIST-800-171-3.1.5
- PCI-DSS-Req-8.2.3
- CJIS-5.5.2
- DISA-STIG-RHEL-07-010290
- name: "Prevent Log In to Accounts With Empty Password - password-auth"
replace:
dest: /etc/pam.d/password-auth
follow: yes
regexp: 'nullok'
tags:
- no_empty_passwords
- high_severity
- configure_strategy
- low_complexity
- medium_disruption
- CCE-27286-4
- NIST-800-53-AC-6
- NIST-800-53-IA-5(b)
- NIST-800-53-IA-5(c)
- NIST-800-53-IA-5(1)(a)
- NIST-800-171-3.1.1
- NIST-800-171-3.1.5
- PCI-DSS-Req-8.2.3
- CJIS-5.5.2
- DISA-STIG-RHEL-07-010290
|
Verify All Account Password Hashes are Shadowed
[ref]ruleIf any password hashes are stored in /etc/passwd (in the second field,
instead of an x or * ), 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. |
All GIDs referenced in /etc/passwd must be defined in /etc/group
[ref]ruleAdd a group to the system for each GID referenced without a corresponding group. Rationale:If a user is assigned the Group Identifier (GID) of a group not existing on the system, and a group
with the Gruop Identifier (GID) is subsequently created, the user may have unintended rights to
any files associated with the group. |
Set Password Expiration Parameters
[ref]groupThe file /etc/login.defs controls several
password-related settings. Programs such as passwd ,
su , and
login consult /etc/login.defs to determine
behavior with regard to password aging, expiration warnings,
and length. See the man page login.defs(5) for more information.
Users should be forced to change their passwords, in order to
decrease the utility of compromised passwords. However, the need to
change passwords often should be balanced against the risk that
users will reuse or write down passwords if forced to change them
too often. Forcing password changes every 90-360 days, depending on
the environment, is recommended. Set the appropriate value as
PASS_MAX_DAYS and apply it to existing accounts with the
-M flag.
The PASS_MIN_DAYS (-m ) setting prevents password
changes for 7 days after the first change, to discourage password
cycling. If you use this setting, train users to contact an administrator
for an emergency password change in case a new password becomes
compromised. The PASS_WARN_AGE (-W ) setting gives
users 7 days of warnings at login time that their passwords are about to expire.
For example, for each existing human user USER, expiration parameters
could be adjusted to a 180 day maximum password age, 7 day minimum password
age, and 7 day warning period with the following command:
$ sudo chage -M 180 -m 7 -W 7 USER |
contains 4 rules |
Set Password Minimum Length in login.defs
[ref]ruleTo specify password length requirements for new accounts,
edit the file /etc/login.defs and add or correct the following
line:
PASS_MIN_LEN 6
The DoD requirement is 15 .
The FISMA requirement is 12 .
The profile requirement is 6 .
If a program consults /etc/login.defs and also another PAM module
(such as pam_pwquality ) during a password change operation,
then the most restrictive must be satisfied. See PAM section
for more information about enforcing password quality requirements.Rationale:Requiring a minimum password length makes password
cracking attacks more difficult by ensuring a larger
search space. However, any security benefit from an onerous requirement
must be carefully weighed against usability problems, support costs, or counterproductive
behavior that may result. Remediation Shell script: (show)
var_accounts_password_minlen_login_defs="6"
grep -q ^PASS_MIN_LEN /etc/login.defs && \
sed -i "s/PASS_MIN_LEN.*/PASS_MIN_LEN $var_accounts_password_minlen_login_defs/g" /etc/login.defs
if ! [ $? -eq 0 ]; then
echo "PASS_MIN_LEN $var_accounts_password_minlen_login_defs" >> /etc/login.defs
fi
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: XCCDF Value var_accounts_password_minlen_login_defs # promote to variable
set_fact:
var_accounts_password_minlen_login_defs: 6
tags:
- always
- name: "Set Password Minimum Length in login.defs"
lineinfile:
dest: /etc/login.defs
regexp: "^PASS_MIN_LEN *[0-9]*"
state: present
line: "PASS_MIN_LEN {{ var_accounts_password_minlen_login_defs }}"
tags:
- accounts_password_minlen_login_defs
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27123-9
- NIST-800-53-IA-5(f)
- NIST-800-53-IA-5(1)(a)
- NIST-800-171-3.5.7
- CJIS-5.6.2.1
|
Set Password Warning Age
[ref]ruleTo specify how many days prior to password
expiration that a warning will be issued to users,
edit the file /etc/login.defs and add or correct
the following line:
PASS_WARN_AGE 7
The DoD requirement is 7.
The profile requirement is 7 .Rationale:Setting the password warning age enables users to
make the change at a practical time. Remediation Shell script: (show)
var_accounts_password_warn_age_login_defs="7"
grep -q ^PASS_WARN_AGE /etc/login.defs && \
sed -i "s/PASS_WARN_AGE.*/PASS_WARN_AGE $var_accounts_password_warn_age_login_defs/g" /etc/login.defs
if ! [ $? -eq 0 ]; then
echo "PASS_WARN_AGE $var_accounts_password_warn_age_login_defs" >> /etc/login.defs
fi
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: XCCDF Value var_accounts_password_warn_age_login_defs # promote to variable
set_fact:
var_accounts_password_warn_age_login_defs: 7
tags:
- always
- name: "Set Password Warning Age"
lineinfile:
dest: /etc/login.defs
regexp: "^PASS_WARN_AGE *[0-9]*"
state: present
line: "PASS_WARN_AGE {{ var_accounts_password_warn_age_login_defs }}"
tags:
- accounts_password_warn_age_login_defs
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-26486-1
- NIST-800-53-AC-2(2)
- NIST-800-53-IA-5(f)
- NIST-800-171-3.5.8
|
Set Password Minimum Age
[ref]ruleTo specify password minimum age for new accounts,
edit the file /etc/login.defs
and add or correct the following line:
PASS_MIN_DAYS 7
A value of 1 day is considered sufficient for many
environments. The DoD requirement is 1.
The profile requirement is 7 .Rationale:Enforcing a minimum password lifetime helps to prevent repeated password changes to defeat
the password reuse or history enforcement requirement. If users are allowed to immediately
and continually change their password, then the password could be repeatedly changed in a
short period of time to defeat the organization's policy regarding password reuse.
Setting the minimum password age protects against users cycling back to a favorite password
after satisfying the password reuse requirement. Remediation Shell script: (show)
var_accounts_minimum_age_login_defs="7"
grep -q ^PASS_MIN_DAYS /etc/login.defs && \
sed -i "s/PASS_MIN_DAYS.*/PASS_MIN_DAYS $var_accounts_minimum_age_login_defs/g" /etc/login.defs
if ! [ $? -eq 0 ]; then
echo "PASS_MIN_DAYS $var_accounts_minimum_age_login_defs" >> /etc/login.defs
fi
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: XCCDF Value var_accounts_minimum_age_login_defs # promote to variable
set_fact:
var_accounts_minimum_age_login_defs: 7
tags:
- always
- name: Set Password Minimum Age
lineinfile:
create: yes
dest: /etc/login.defs
regexp: ^#?PASS_MIN_DAYS
line: "PASS_MIN_DAYS {{ var_accounts_minimum_age_login_defs }}"
tags:
- accounts_minimum_age_login_defs
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27002-5
- NIST-800-53-IA-5(f)
- NIST-800-53-IA-5(1)(d)
- NIST-800-171-3.5.8
- CJIS-5.6.2.1.1
- DISA-STIG-RHEL-07-010230
|
Set Password Maximum Age
[ref]ruleTo specify password maximum age for new accounts,
edit the file /etc/login.defs
and add or correct the following line:
PASS_MAX_DAYS 60
A value of 180 days is sufficient for many environments.
The DoD requirement is 60.
The profile requirement is 60 .Rationale:Any password, no matter how complex, can eventually be cracked. Therefore, passwords
need to be changed periodically. If the operating system does not limit the lifetime
of passwords and force users to change their passwords, there is the risk that the
operating system passwords could be compromised.
Setting the password maximum age ensures users are required to
periodically change their passwords. Requiring shorter password lifetimes
increases the risk of users writing down the password in a convenient
location subject to physical compromise. Remediation Shell script: (show)
var_accounts_maximum_age_login_defs="60"
grep -q ^PASS_MAX_DAYS /etc/login.defs && \
sed -i "s/PASS_MAX_DAYS.*/PASS_MAX_DAYS $var_accounts_maximum_age_login_defs/g" /etc/login.defs
if ! [ $? -eq 0 ]; then
echo "PASS_MAX_DAYS $var_accounts_maximum_age_login_defs" >> /etc/login.defs
fi
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: XCCDF Value var_accounts_maximum_age_login_defs # promote to variable
set_fact:
var_accounts_maximum_age_login_defs: 60
tags:
- always
- name: Set Password Maximum Age
lineinfile:
create: yes
dest: /etc/login.defs
regexp: ^#?PASS_MAX_DAYS
line: "PASS_MAX_DAYS {{ var_accounts_maximum_age_login_defs }}"
tags:
- accounts_maximum_age_login_defs
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27051-2
- NIST-800-53-IA-5(f)
- NIST-800-53-IA-5(g)
- NIST-800-53-IA-5(1)(d)
- NIST-800-171-3.5.6
- PCI-DSS-Req-8.2.4
- CJIS-5.6.2.1
- DISA-STIG-RHEL-07-010250
|
Set Account Expiration Parameters
[ref]groupAccounts can be configured to be automatically disabled
after a certain time period,
meaning that they will require administrator interaction to become usable again.
Expiration of accounts after inactivity can be set for all accounts by default
and also on a per-account basis, such as for accounts that are known to be temporary.
To configure automatic expiration of an account following
the expiration of its password (that is, after the password has expired and not been changed),
run the following command, substituting NUM_DAYS and USER appropriately:
$ sudo chage -I NUM_DAYS USER
Accounts, such as temporary accounts, can also be configured to expire on an explicitly-set date with the
-E option.
The file /etc/default/useradd controls
default settings for all newly-created accounts created with the system's
normal command line utilities. |
contains 1 rule |
Set Account Expiration Following Inactivity
[ref]ruleTo specify the number of days after a password expires (which
signifies inactivity) until an account is permanently disabled, add or correct
the following lines in /etc/default/useradd , substituting
NUM_DAYS appropriately:
INACTIVE=35
A value of 35 is recommended; however, this profile expects that the value is set to
35 .
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. Remediation Shell script: (show)
var_account_disable_post_pw_expiration="35"
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/default/useradd' '^INACTIVE' "$var_account_disable_post_pw_expiration" 'CCE-27355-7' '%s=%s'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: XCCDF Value var_account_disable_post_pw_expiration # promote to variable
set_fact:
var_account_disable_post_pw_expiration: 35
tags:
- always
- name: Set Account Expiration Following Inactivity
lineinfile:
create: yes
dest: /etc/default/useradd
regexp: ^INACTIVE
line: "INACTIVE={{ var_account_disable_post_pw_expiration }}"
tags:
- account_disable_post_pw_expiration
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27355-7
- NIST-800-53-AC-2(2)
- NIST-800-53-AC-2(3)
- NIST-800-53-IA-4(e)
- NIST-800-171-3.5.6
- PCI-DSS-Req-8.1.4
- CJIS-5.6.2.1.1
- DISA-STIG-RHEL-07-010310
|
Protect Physical Console Access
[ref]groupIt is impossible to fully protect a system from an
attacker with physical access, so securing the space in which the
system is located should be considered a necessary step. However,
there are some steps which, if taken, make it more difficult for an
attacker to quickly or undetectably modify a system from its
console. |
contains 12 rules |
Configure Screen Locking
[ref]groupWhen a user must temporarily leave an account
logged-in, screen locking should be employed to prevent passersby
from abusing the account. User education and training is
particularly important for screen locking to be effective, and policies
can be implemented to reinforce this.
Automatic screen locking is only meant as a safeguard for
those cases where a user forgot to lock the screen. |
contains 2 rules |
Configure Console Screen Locking
[ref]groupA console screen locking mechanism is provided in the
screen package, which is not installed by default. |
contains 1 rule |
Install the screen Package
[ref]ruleTo enable console screen locking, install the screen package:
$ sudo yum install screen
Instruct users to begin new terminal sessions with the following command:
$ screen
The console can now be locked with the following key combination:
ctrl+a x Rationale:A session time-out lock is a temporary action taken when a user stops work and moves away from the immediate
physical vicinity of the information system but des not logout because of the temporary nature of the absence.
Rather than relying on the user to manually lock their operation system session prior to vacating the vicinity,
operating systems need to be able to identify when a user's session has idled and take action to initiate the
session lock.
The screen package allows for a session lock to be implemented and configured. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
# Function to install packages on RHEL, Fedora, Debian, and possibly other systems.
#
# Example Call(s):
#
# package_install aide
#
function package_install {
# Load function arguments into local variables
local package="$1"
# Check sanity of the input
if [ $# -ne "1" ]
then
echo "Usage: package_install 'package_name'"
echo "Aborting."
exit 1
fi
if which dnf ; then
if ! rpm -q --quiet "$package"; then
dnf install -y "$package"
fi
elif which yum ; then
if ! rpm -q --quiet "$package"; then
yum install -y "$package"
fi
elif which apt-get ; then
apt-get install -y "$package"
else
echo "Failed to detect available packaging system, tried dnf, yum and apt-get!"
echo "Aborting."
exit 1
fi
}
package_install screen
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: Ensure screen is installed
package:
name="{{item}}"
state=present
with_items:
- screen
tags:
- package_screen_installed
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-27351-6
- NIST-800-53-AC-11(a)
- NIST-800-171-3.1.10
- DISA-STIG-RHEL-07-010090
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
include install_screen
class install_screen {
package { 'screen':
ensure => 'installed',
}
}
Remediation Anaconda snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
package --add=screen
|
Hardware Tokens for Authentication
[ref]groupThe use of hardware tokens such as smart cards for system login
provides stronger, two-factor authentication than using a username and password.
In Red Hat Enterprise Linux servers and workstations, hardware token login
is not enabled by default and must be enabled in the system settings. |
contains 1 rule |
Enable Smart Card Login
[ref]ruleTo enable smart card authentication, consult the documentation at:
For guidance on enabling SSH to authenticate against a Common Access Card (CAC), consult documentation at:
Rationale:Smart card login provides two-factor authentication stronger than
that provided by a username and password combination. Smart cards leverage PKI
(public key infrastructure) in order to provide and verify credentials. References:
SV-86589r1_rule, CCI-000765, CCI-000766, CCI-000767, CCI-000768, CCI-000771, CCI-000772, CCI-000884, IA-2(2), Req-8.3, SRG-OS-000104-GPOS-00051, SRG-OS-000106-GPOS-00053, SRG-OS-000107-GPOS-00054, SRG-OS-000109-GPOS-00056, SRG-OS-000108-GPOS-00055, SRG-OS-000108-GPOS-00057, SRG-OS-000108-GPOS-00058 Remediation Shell script: (show)
# Install required packages
# Function to install packages on RHEL, Fedora, Debian, and possibly other systems.
#
# Example Call(s):
#
# package_install aide
#
function package_install {
# Load function arguments into local variables
local package="$1"
# Check sanity of the input
if [ $# -ne "1" ]
then
echo "Usage: package_install 'package_name'"
echo "Aborting."
exit 1
fi
if which dnf ; then
if ! rpm -q --quiet "$package"; then
dnf install -y "$package"
fi
elif which yum ; then
if ! rpm -q --quiet "$package"; then
yum install -y "$package"
fi
elif which apt-get ; then
apt-get install -y "$package"
else
echo "Failed to detect available packaging system, tried dnf, yum and apt-get!"
echo "Aborting."
exit 1
fi
}
package_install esc
package_install pam_pkcs11
# Enable pcscd.socket systemd activation socket
# Function to enable/disable and start/stop services on RHEL and Fedora systems.
#
# Example Call(s):
#
# service_command enable bluetooth
# service_command disable bluetooth.service
#
# Using xinetd:
# service_command disable rsh.socket xinetd=rsh
#
function service_command {
# Load function arguments into local variables
local service_state=$1
local service=$2
local xinetd=$(echo $3 | cut -d'=' -f2)
# Check sanity of the input
if [ $# -lt "2" ]
then
echo "Usage: service_command 'enable/disable' 'service_name.service'"
echo
echo "To enable or disable xinetd services add \'xinetd=service_name\'"
echo "as the last argument"
echo "Aborting."
exit 1
fi
# If systemctl is installed, use systemctl command; otherwise, use the service/chkconfig commands
if [ -f "/usr/bin/systemctl" ] ; then
service_util="/usr/bin/systemctl"
else
service_util="/sbin/service"
chkconfig_util="/sbin/chkconfig"
fi
# If disable is not specified in arg1, set variables to enable services.
# Otherwise, variables are to be set to disable services.
if [ "$service_state" != 'disable' ] ; then
service_state="enable"
service_operation="start"
chkconfig_state="on"
else
service_state="disable"
service_operation="stop"
chkconfig_state="off"
fi
# If chkconfig_util is not empty, use chkconfig/service commands.
if [ "x$chkconfig_util" != x ] ; then
$service_util $service $service_operation
$chkconfig_util --level 0123456 $service $chkconfig_state
else
$service_util $service_operation $service
$service_util $service_state $service
# The service may not be running because it has been started and failed,
# so let's reset the state so OVAL checks pass.
# Service should be 'inactive', not 'failed' after reboot though.
$service_util reset-failed $service
fi
# Test if local variable xinetd is empty using non-bashism.
# If empty, then xinetd is not being used.
if [ "x$xinetd" != x ] ; then
grep -qi disable /etc/xinetd.d/$xinetd && \
if [ "$service_operation" = 'disable' ] ; then
sed -i "s/disable.*/disable = no/gI" /etc/xinetd.d/$xinetd
else
sed -i "s/disable.*/disable = yes/gI" /etc/xinetd.d/$xinetd
fi
fi
}
service_command enable pcscd.socket
# Configure the expected /etc/pam.d/system-auth{,-ac} settings directly
#
# The code below will configure system authentication in the way smart card
# logins will be enabled, but also user login(s) via other method to be allowed
#
# NOTE: It is not possible to use the 'authconfig' command to perform the
# remediation for us, because call of 'authconfig' would discard changes
# for other remediations (see RH BZ#1357019 for details)
#
# Therefore we need to configure the necessary settings directly.
#
# Define system-auth config location
SYSTEM_AUTH_CONF="/etc/pam.d/system-auth"
# Define expected 'pam_env.so' row in $SYSTEM_AUTH_CONF
PAM_ENV_SO="auth.*required.*pam_env.so"
# Define 'pam_succeed_if.so' row to be appended past $PAM_ENV_SO row into $SYSTEM_AUTH_CONF
SYSTEM_AUTH_PAM_SUCCEED="\
auth [success=1 default=ignore] pam_succeed_if.so service notin \
login:gdm:xdm:kdm:xscreensaver:gnome-screensaver:kscreensaver quiet use_uid"
# Define 'pam_pkcs11.so' row to be appended past $SYSTEM_AUTH_PAM_SUCCEED
# row into SYSTEM_AUTH_CONF file
SYSTEM_AUTH_PAM_PKCS11="\
auth [success=done authinfo_unavail=ignore ignore=ignore default=die] \
pam_pkcs11.so nodebug"
# Define smartcard-auth config location
SMARTCARD_AUTH_CONF="/etc/pam.d/smartcard-auth"
# Define 'pam_pkcs11.so' auth section to be appended past $PAM_ENV_SO into $SMARTCARD_AUTH_CONF
SMARTCARD_AUTH_SECTION="\
auth [success=done ignore=ignore default=die] pam_pkcs11.so wait_for_card card_only"
# Define expected 'pam_permit.so' row in $SMARTCARD_AUTH_CONF
PAM_PERMIT_SO="account.*required.*pam_permit.so"
# Define 'pam_pkcs11.so' password section
SMARTCARD_PASSWORD_SECTION="\
password required pam_pkcs11.so"
# First Correct the SYSTEM_AUTH_CONF configuration
if ! grep -q 'pam_pkcs11.so' "$SYSTEM_AUTH_CONF"
then
# Append (expected) pam_succeed_if.so row past the pam_env.so into SYSTEM_AUTH_CONF file
# and append (expected) pam_pkcs11.so row right after the pam_succeed_if.so we just added
# in SYSTEM_AUTH_CONF file
# This will preserve any other already existing row equal to "$SYSTEM_AUTH_PAM_SUCCEED"
echo "$(awk '/^'"$PAM_ENV_SO"'/{print $0 RS "'"$SYSTEM_AUTH_PAM_SUCCEED"'" RS "'"$SYSTEM_AUTH_PAM_PKCS11"'";next}1' "$SYSTEM_AUTH_CONF")" > "$SYSTEM_AUTH_CONF"
fi
# Then also correct the SMARTCARD_AUTH_CONF
if ! grep -q 'pam_pkcs11.so' "$SMARTCARD_AUTH_CONF"
then
# Append (expected) SMARTCARD_AUTH_SECTION row past the pam_env.so into SMARTCARD_AUTH_CONF file
sed -i --follow-symlinks -e '/^'"$PAM_ENV_SO"'/a '"$SMARTCARD_AUTH_SECTION" "$SMARTCARD_AUTH_CONF"
# Append (expected) SMARTCARD_PASSWORD_SECTION row past the pam_permit.so into SMARTCARD_AUTH_CONF file
sed -i --follow-symlinks -e '/^'"$PAM_PERMIT_SO"'/a '"$SMARTCARD_PASSWORD_SECTION" "$SMARTCARD_AUTH_CONF"
fi
# Perform /etc/pam_pkcs11/pam_pkcs11.conf settings below
# Define selected constants for later reuse
SP="[:space:]"
PAM_PKCS11_CONF="/etc/pam_pkcs11/pam_pkcs11.conf"
# Ensure OCSP is turned on in $PAM_PKCS11_CONF
# 1) First replace any occurrence of 'none' value of 'cert_policy' key setting with the correct configuration
sed -i "s/^[$SP]*cert_policy[$SP]\+=[$SP]\+none;/\t\tcert_policy = ca, ocsp_on, signature;/g" "$PAM_PKCS11_CONF"
# 2) Then append 'ocsp_on' value setting to each 'cert_policy' key in $PAM_PKCS11_CONF configuration line,
# which does not contain it yet
sed -i "/ocsp_on/! s/^[$SP]*cert_policy[$SP]\+=[$SP]\+\(.*\);/\t\tcert_policy = \1, ocsp_on;/" "$PAM_PKCS11_CONF"
Remediation Anaconda snippet: (show)
package --add=pam_pkcs11 --add=esc
|
Set Boot Loader Password
[ref]groupDuring the boot process, the boot loader is
responsible for starting the execution of the kernel and passing
options to it. The boot loader allows for the selection of
different kernels - possibly on different partitions or media.
The default Red Hat Enterprise Linux boot loader for x86 systems is called GRUB2.
Options it can pass to the kernel include single-user mode, which
provides root access without any authentication, and the ability to
disable SELinux. To prevent local users from modifying the boot
parameters and endangering security, protect the boot loader configuration
with a password and ensure its configuration file's permissions
are set properly. |
contains 5 rules |
Verify /boot/grub2/grub.cfg Group Ownership
[ref]ruleThe file /boot/grub2/grub.cfg should
be group-owned by the root group to prevent
destruction or modification of the file.
To properly set the group owner of /boot/grub2/grub.cfg , run the command:
$ sudo chgrp root /boot/grub2/grub.cfg Rationale:The root group is a highly-privileged group. Furthermore, the group-owner of this
file should not have any access privileges anyway. References:
1.4.1, 5.5.2.2, 3.4.5, CCI-000225, 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii), AC-6(7), Req-7.1 Remediation Shell script: (show)
chgrp root /boot/grub2/grub.cfg
|
Verify /boot/grub2/grub.cfg User Ownership
[ref]ruleThe file /boot/grub2/grub.cfg should
be owned by the root user to prevent destruction
or modification of the file.
To properly set the owner of /boot/grub2/grub.cfg , run the command:
$ sudo chown root /boot/grub2/grub.cfg Rationale:Only root should be able to modify important boot parameters. References:
1.4.1, 5.5.2.2, 3.4.5, CCI-000225, 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii), AC-6(7), Req-7.1 Remediation Shell script: (show)
chown root /boot/grub2/grub.cfg
|
Verify /boot/grub2/grub.cfg Permissions
[ref]ruleFile permissions for /boot/grub2/grub.cfg should be set to 600.
To properly set the permissions of /boot/grub2/grub.cfg , run the command:
$ sudo chmod 600 /boot/grub2/grub.cfg Rationale:Proper permissions ensure that only the root user can modify important boot
parameters. References:
1.4.1, 3.4.5, CCI-000225, 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii), AC-6(7) Remediation Shell script: (show)
chmod 600 /boot/grub2/grub.cfg
|
Set Boot Loader Password
[ref]ruleThe grub2 boot loader should have a superuser account and password
protection enabled to protect boot-time settings.
To do so, select a superuser account name and password and and modify the
/etc/grub.d/01_users configuration file with the new account name.
Since plaintext passwords are a security risk, generate a hash for the pasword
by running the following command:
$ grub2-setpassword
When prompted, enter the password that was selected.
NOTE: It is recommended not to use common administrator account names like root,
admin, or administrator for the grub2 superuser account.
Change the superuser to a different username (The default is 'root').
$ sed -i s/root/bootuser/g /etc/grub.d/01_users
To meet FISMA Moderate, the bootloader superuser account and password MUST
differ from the root account and password.
Once the superuser account and password have been added, update the
grub.cfg file by running:
grub2-mkconfig -o /boot/grub2/grub.cfg
NOTE: Do NOT manually add the superuser account and password to the
grub.cfg file as the grub2-mkconfig command overwrites this file.Rationale:Password protection on the boot loader configuration ensures
users with physical access cannot trivially alter
important bootloader settings. These include which kernel to use,
and whether to enter single-user mode. For more information on how to configure
the grub2 superuser account and password, please refer to
References:
FIA_AFL.1, SV-86585r4_rule, 1.4.2, 3.4.5, CCI-000213, 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii), IA-2(1), IA-5(e), AC-3, SRG-OS-000080-GPOS-00048 |
Set the UEFI Boot Loader Password
[ref]ruleThe grub2 boot loader should have a superuser account and password
protection enabled to protect boot-time settings.
To do so, select a superuser account name and password and and modify the
/etc/grub.d/01_users configuration file with the new account name.
Since plaintext passwords are a security risk, generate a hash for the pasword
by running the following command:
$ grub2-setpassword
When prompted, enter the password that was selected.
NOTE: It is recommended not to use common administrator account names like root,
admin, or administrator for the grub2 superuser account.
Change the superuser to a different username (The default is 'root').
$ sed -i s/root/bootuser/g /etc/grub.d/01_users
To meet FISMA Moderate, the bootloader superuser account and password MUST
differ from the root account and password.
Once the superuser account and password have been added, update the
grub.cfg file by running:
grub2-mkconfig -o /boot/efi/EFI/redhat/grub.cfg
NOTE: Do NOT manually add the superuser account and password to the
grub.cfg file as the grub2-mkconfig command overwrites this file.Rationale:Password protection on the boot loader configuration ensures
users with physical access cannot trivially alter
important bootloader settings. These include which kernel to use,
and whether to enter single-user mode. For more information on how to configure
the grub2 superuser account and password, please refer to
References:
FIA_AFL.1, SV-86587r3_rule, 1.4.2, 3.4.5, CCI-000213, 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii), AC-3, SRG-OS-000080-GPOS-00048 |
Verify that Interactive Boot is Disabled
[ref]ruleRed Hat Enterprise Linux systems support an "interactive boot" option that can
be used to prevent services from being started. On a Red Hat Enterprise Linux 7
system, interactive boot can be enabled by providing a 1 ,
yes , true , or on value to the
systemd.confirm_spawn kernel argument in /etc/default/grub .
Remove any instance of systemd.confirm_spawn=(1|yes|true|on) from
the kernel arguments in that file to disable interactive boot.Rationale:Using interactive boot, the console user could disable auditing, firewalls,
or other services, weakening system security. References:
FIA_AFL.1, 3.1.2, 3.4.5, CCI-000213, 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii), SC-2, AC-3 Remediation Shell script: (show)
# Systemd confirm_spawn regex to search for and delete if found
CONFIRM_SPAWN_REGEX="systemd.confirm_spawn=\(1\|yes\|true\|on\)"
# Modify both the GRUB_CMDLINE_LINUX and GRUB_CMDLINE_LINUX_DEFAULT directives
for grubcmdline in "GRUB_CMDLINE_LINUX" "GRUB_CMDLINE_LINUX_DEFAULT"
do
# Remove 'systemd.confirm_spawn' argument from /etc/default/grub if found
if grep -q "^${grubcmdline}=\".*${CONFIRM_SPAWN_REGEX}.*\"" /etc/default/grub
then
# Remove all three possible occurrences of CONFIRM_SPAWN_REGEX:
# At the start
sed -i "s/\"${CONFIRM_SPAWN_REGEX} /\"/" /etc/default/grub
# At the end
sed -i "s/ ${CONFIRM_SPAWN_REGEX}\"$/\"/" /etc/default/grub
# In the middle
sed -i "s/ ${CONFIRM_SPAWN_REGEX}//" /etc/default/grub
fi
done
# Remove 'systemd.confirm_spawn' kernel argument also from runtime settings
/sbin/grubby --update-kernel=ALL --remove-args="systemd.confirm_spawn"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: Verify that Interactive Boot is Disabled
lineinfile:
create: yes
dest: /etc/default/grub
regexp: ^systemd.confirm_spawn=
line: systemd.confirm_spawn=no
state: present
tags:
- disable_interactive_boot
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27335-9
- NIST-800-53-SC-2
- NIST-800-53-AC-3
- NIST-800-171-3.1.2
- NIST-800-171-3.4.5
|
Disable Ctrl-Alt-Del Burst Action
[ref]ruleBy default, SystemD will reboot the system if the Ctrl-Alt-Del
key sequence is pressed Ctrl-Alt-Delete more than 7 times in 2 seconds.
To configure the system to ignore the CtrlAltDelBurstAction
setting, add or modify the following to /etc/systemd/system.conf :
CtrlAltDelBurstAction=none Rationale:A locally logged-in user who presses Ctrl-Alt-Del, when at the console,
can reboot the system. If accidentally pressed, as could happen in
the case of mixed OS environment, this can create the risk of short-term
loss of availability of systems due to unintentional reboot. References:
3.4.5, CCI-000366, 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii), AC-6, SRG-OS-000480-GPOS-00227 Remediation Shell script: (show)
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/systemd/system.conf' '^CtrlAltDelBurstAction=' 'none' 'CCE-80449-2' '%s=%s'
|
Disable debug-shell SystemD Service
[ref]ruleSystemD's debug-shell service is intended to
diagnose SystemD related boot issues with various systemctl
commands. Once enabled and following a system reboot, the root shell
will be available on tty9 which is access by pressing
CTRL-ALT-F9 . The debug-shell service should only be used
for SystemD related issues and should otherwise be disabled.
By default, the debug-shell SystemD service is disabled.
The debug-shell service can be disabled with the following command:
$ sudo systemctl disable debug-shell.service Rationale:This prevents attackers with physical access from trivially bypassing security
on the machine through valid troubleshooting configurations and gaining root
access when the system is rebooted. References:
FIA_AFL.1, 3.4.5, 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii) Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
# Function to enable/disable and start/stop services on RHEL and Fedora systems.
#
# Example Call(s):
#
# service_command enable bluetooth
# service_command disable bluetooth.service
#
# Using xinetd:
# service_command disable rsh.socket xinetd=rsh
#
function service_command {
# Load function arguments into local variables
local service_state=$1
local service=$2
local xinetd=$(echo $3 | cut -d'=' -f2)
# Check sanity of the input
if [ $# -lt "2" ]
then
echo "Usage: service_command 'enable/disable' 'service_name.service'"
echo
echo "To enable or disable xinetd services add \'xinetd=service_name\'"
echo "as the last argument"
echo "Aborting."
exit 1
fi
# If systemctl is installed, use systemctl command; otherwise, use the service/chkconfig commands
if [ -f "/usr/bin/systemctl" ] ; then
service_util="/usr/bin/systemctl"
else
service_util="/sbin/service"
chkconfig_util="/sbin/chkconfig"
fi
# If disable is not specified in arg1, set variables to enable services.
# Otherwise, variables are to be set to disable services.
if [ "$service_state" != 'disable' ] ; then
service_state="enable"
service_operation="start"
chkconfig_state="on"
else
service_state="disable"
service_operation="stop"
chkconfig_state="off"
fi
# If chkconfig_util is not empty, use chkconfig/service commands.
if [ "x$chkconfig_util" != x ] ; then
$service_util $service $service_operation
$chkconfig_util --level 0123456 $service $chkconfig_state
else
$service_util $service_operation $service
$service_util $service_state $service
# The service may not be running because it has been started and failed,
# so let's reset the state so OVAL checks pass.
# Service should be 'inactive', not 'failed' after reboot though.
$service_util reset-failed $service
fi
# Test if local variable xinetd is empty using non-bashism.
# If empty, then xinetd is not being used.
if [ "x$xinetd" != x ] ; then
grep -qi disable /etc/xinetd.d/$xinetd && \
if [ "$service_operation" = 'disable' ] ; then
sed -i "s/disable.*/disable = no/gI" /etc/xinetd.d/$xinetd
else
sed -i "s/disable.*/disable = yes/gI" /etc/xinetd.d/$xinetd
fi
fi
}
service_command disable debug-shell
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
- name: Disable service debug-shell
service:
name="{{item}}"
enabled="no"
state="stopped"
register: service_result
failed_when: "service_result|failed and ('Could not find the requested service' not in service_result.msg)"
with_items:
- debug-shell
tags:
- service_debug-shell_disabled
- medium_severity
- disable_strategy
- low_complexity
- low_disruption
- CCE-80206-6
- NIST-800-171-3.4.5
|
Disable Ctrl-Alt-Del Reboot Activation
[ref]ruleBy default, SystemD will reboot the system if the Ctrl-Alt-Del
key sequence is pressed.
To configure the system to ignore the Ctrl-Alt-Del key sequence from the
command line instead of rebooting the system, do either of the following:
ln -sf /dev/null /etc/systemd/system/ctrl-alt-del.target
or
systemctl mask ctrl-alt-del.target
Do not simply delete the /usr/lib/systemd/system/ctrl-alt-del.service file,
as this file may be restored during future system updates.Rationale:A locally logged-in user who presses Ctrl-Alt-Del, when at the console,
can reboot the system. If accidentally pressed, as could happen in
the case of mixed OS environment, this can create the risk of short-term
loss of availability of systems due to unintentional reboot. References:
SV-86617r1_rule, 3.4.5, CCI-000366, 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii), AC-6, SRG-OS-000480-GPOS-00227 Remediation Shell script: (show)
# The process to disable ctrl+alt+del has changed in RHEL7.
# Reference: https://access.redhat.com/solutions/1123873
systemctl mask ctrl-alt-del.target
|
Require Authentication for Single User Mode
[ref]ruleSingle-user mode is intended as a system recovery
method, providing a single user root access to the system by
providing a boot option at startup. By default, no authentication
is performed if single-user mode is selected.
By default, single-user mode is protected by requiring a password and is set
in /usr/lib/systemd/system/rescue.service . Rationale:This prevents attackers with physical access from trivially bypassing security
on the machine and gaining root access. Such accesses are further prevented
by configuring the bootloader password. References:
FIA_AFL.1, SV-92519r1_rule, 1.4.3, 3.1.1, 3.4.5, CCI-000213, 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii), IA-2(1), AC-3, SRG-OS-000080-GPOS-00048, SV-92519r1_rule Remediation Shell script: (show)
grep -q "^ExecStart=\-.*/sbin/sulogin" /usr/lib/systemd/system/rescue.service
if ! [ $? -eq 0 ]; then
sed -i "s/ExecStart=-.*-c \"/&\/sbin\/sulogin; /g" /usr/lib/systemd/system/rescue.service
fi
|
Protect Accounts by Configuring PAM
[ref]groupPAM, or Pluggable Authentication Modules, is a system
which implements modular authentication for Linux programs. PAM provides
a flexible and configurable architecture for authentication, and it should be configured
to minimize exposure to unnecessary risk. This section contains
guidance on how to accomplish that.
PAM is implemented as a set of shared objects which are
loaded and invoked whenever an application wishes to authenticate a
user. Typically, the application must be running as root in order
to take advantage of PAM, because PAM's modules often need to be able
to access sensitive stores of account information, such as /etc/shadow.
Traditional privileged network listeners
(e.g. sshd) or SUID programs (e.g. sudo) already meet this
requirement. An SUID root application, userhelper, is provided so
that programs which are not SUID or privileged themselves can still
take advantage of PAM.
PAM looks in the directory /etc/pam.d for
application-specific configuration information. For instance, if
the program login attempts to authenticate a user, then PAM's
libraries follow the instructions in the file /etc/pam.d/login
to determine what actions should be taken.
One very important file in /etc/pam.d is
/etc/pam.d/system-auth . This file, which is included by
many other PAM configuration files, defines 'default' system authentication
measures. Modifying this file is a good way to make far-reaching
authentication changes, for instance when implementing a
centralized authentication service. |
contains 19 rules |
Set Password Hashing Algorithm
[ref]groupThe system's default algorithm for storing password hashes in
/etc/shadow is SHA-512. This can be configured in several
locations. |
contains 3 rules |
Set Password Hashing Algorithm in /etc/login.defs
[ref]ruleIn /etc/login.defs , add or correct the following line to ensure
the system will use SHA-512 as the hashing algorithm:
ENCRYPT_METHOD SHA512 Rationale:Passwords need to be protected at all times, and encryption is the standard method for protecting passwords.
If passwords are not encrypted, they can be plainly read (i.e., clear text) and easily compromised. Passwords
that are encrypted with a weak algorithm are no more protected than if they are kept in plain text.
Using a stronger hashing algorithm makes password cracking attacks more difficult. References:
SV-86545r1_rule, 6.3.1, 5.6.2.2, 3.13.11, CCI-000196, IA-5(b), IA-5(c), IA-5(1)(c), IA-7, Req-8.2.1, SRG-OS-000073-GPOS-00041 Remediation Shell script: (show)
if grep --silent ^ENCRYPT_METHOD /etc/login.defs ; then
sed -i 's/^ENCRYPT_METHOD.*/ENCRYPT_METHOD SHA512/g' /etc/login.defs
else
echo "" >> /etc/login.defs
echo "ENCRYPT_METHOD SHA512" >> /etc/login.defs
fi
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: Set Password Hashing Algorithm in /etc/login.defs
lineinfile:
dest: /etc/login.defs
regexp: ^#?ENCRYPT_METHOD
line: ENCRYPT_METHOD SHA512
state: present
tags:
- set_password_hashing_algorithm_logindefs
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27124-7
- NIST-800-53-IA-5(b)
- NIST-800-53-IA-5(c)
- NIST-800-53-IA-5(1)(c)
- NIST-800-53-IA-7
- NIST-800-171-3.13.11
- PCI-DSS-Req-8.2.1
- CJIS-5.6.2.2
- DISA-STIG-RHEL-07-010210
|
Set PAM's Password Hashing Algorithm
[ref]ruleThe PAM system service can be configured to only store encrypted representations of passwords.
In /etc/pam.d/system-auth , the password section of the file controls
which PAM modules execute during a password change. Set the pam_unix.so
module in the password section to include the argument sha512 , as shown below:
password sufficient pam_unix.so sha512 other arguments...
This will help ensure when local users change their passwords, hashes for the new
passwords will be generated using the SHA-512 algorithm. This is the default.Rationale:Passwords need to be protected at all times, and encryption is the standard method for protecting
passwords. If passwords are not encrypted, they can be plainly read (i.e., clear text) and easily
compromised. Passwords that are encrypted with a weak algorithm are no more protected than if they
are kepy in plain text.
This setting ensures user and group account administration utilities are configured to store only
encrypted representations of passwords. Additionally, the crypt_style configuration option
ensures the use of a strong hashing algorithm that makes password cracking attacks more difficult. References:
SV-86543r2_rule, 6.3.1, 5.6.2.2, 3.13.11, CCI-000196, IA-5(b), IA-5(c), IA-5(1)(c), IA-7, Req-8.2.1, SRG-OS-000073-GPOS-00041 Remediation Shell script: (show)
AUTH_FILES[0]="/etc/pam.d/system-auth"
AUTH_FILES[1]="/etc/pam.d/password-auth"
for pamFile in "${AUTH_FILES[@]}"
do
if ! grep -q "^password.*sufficient.*pam_unix.so.*sha512" $pamFile; then
sed -i --follow-symlinks "/^password.*sufficient.*pam_unix.so/ s/$/ sha512/" $pamFile
fi
done
|
Set Password Hashing Algorithm in /etc/libuser.conf
[ref]ruleIn /etc/libuser.conf , add or correct the following line in its
[defaults] section to ensure the system will use the SHA-512
algorithm for password hashing:
crypt_style = sha512 Rationale:Passwords need to be protected at all times, and encryption is the standard method for protecting
passwords. If passwords are not encrypted, they can be plainly read (i.e., clear text) and easily
compromised. Passwords that are encrypted with a weak algorithm are no more protected than if they
are kepy in plain text.
This setting ensures user and group account administration utilities are configured to store only
encrypted representations of passwords. Additionally, the crypt_style configuration option
ensures the use of a strong hashing algorithm that makes password cracking attacks more difficult. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: Set Password Hashing Algorithm in /etc/libuser.conf
lineinfile:
dest: /etc/libuser.conf
insertafter: "^.default]"
regexp: ^#?crypt_style
line: crypt_style = sha512
state: present
tags:
- set_password_hashing_algorithm_libuserconf
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27053-8
- NIST-800-53-IA-5(b)
- NIST-800-53-IA-5(c)
- NIST-800-53-IA-5(1)(c)
- NIST-800-53-IA-7
- NIST-800-171-3.13.11
- PCI-DSS-Req-8.2.1
- CJIS-5.6.2.2
- DISA-STIG-RHEL-07-010220
|
Set Lockouts for Failed Password Attempts
[ref]groupThe pam_faillock PAM module provides the capability to
lock out user accounts after a number of failed login attempts. Its
documentation is available in
/usr/share/doc/pam-VERSION/txts/README.pam_faillock .
|
contains 5 rules |
Configure the root Account for Failed Password Attempts
[ref]ruleTo configure the system to lock out the root account 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:
- Modify the following line in the
AUTH section to add even_deny_root :
auth required pam_faillock.so preauth silent even_deny_root deny=3 unlock_time=never fail_interval=900 - Modify the following line in the
AUTH section to add even_deny_root :
auth [default=die] pam_faillock.so authfail even_deny_root deny=3 unlock_time=never fail_interval=900 Rationale:By limiting the number of failed logon attempts, the risk of unauthorized system access via user password
guessing, otherwise known as brute-forcing, is reduced. Limits are imposed by locking the account. Remediation Shell script: (show)
AUTH_FILES[0]="/etc/pam.d/system-auth"
AUTH_FILES[1]="/etc/pam.d/password-auth"
# This script fixes absence of pam_faillock.so in PAM stack or the
# absense of even_deny_root and deny=[0-9]+ in pam_faillock.so arguments
# When inserting auth pam_faillock.so entries,
# the entry with preauth argument will be added before pam_unix.so module
# and entry with authfail argument will be added before pam_deny.so module.
# The placement of pam_faillock.so entries will not be changed
# if they are already present
for pamFile in "${AUTH_FILES[@]}"
do
# pam_faillock.so already present?
if grep -q "^auth.*pam_faillock.so.*" $pamFile; then
# pam_faillock.so present, preauth even_deny_root directive present?
if ! grep -q "^auth.*required.*pam_faillock.so.*preauth.*even_deny_root" $pamFile; then
# even_deny_root is not present
sed -i --follow-symlinks "s/\(^auth.*required.*pam_faillock.so.*preauth.*\).*/\1 even_deny_root/" $pamFile
fi
# pam_faillock.so present, authfail even_deny_root directive present?
if ! grep -q "^auth.*\[default=die\].*pam_faillock.so.*authfail.*even_deny_root" $pamFile; then
# even_deny_root is not present
sed -i --follow-symlinks "s/\(^auth.*\[default=die\].*pam_faillock.so.*authfail.*\).*/\1 even_deny_root/" $pamFile
fi
# pam_faillock.so not present yet
else
# insert pam_faillock.so preauth row with proper value of the 'deny' option before pam_unix.so
sed -i --follow-symlinks "/^auth.*pam_unix.so.*/i auth required pam_faillock.so preauth silent even_deny_root" $pamFile
# insert pam_faillock.so authfail row with proper value of the 'deny' option before pam_deny.so, after all modules which determine authentication outcome.
sed -i --follow-symlinks "/^auth.*pam_deny.so.*/i auth [default=die] pam_faillock.so authfail silent even_deny_root" $pamFile
fi
done
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: XCCDF Value var_accounts_passwords_pam_faillock_deny # promote to variable
set_fact:
var_accounts_passwords_pam_faillock_deny: 3
tags:
- always
- name: XCCDF Value var_accounts_passwords_pam_faillock_unlock_time # promote to variable
set_fact:
var_accounts_passwords_pam_faillock_unlock_time: never
tags:
- always
- name: XCCDF Value var_accounts_passwords_pam_faillock_fail_interval # promote to variable
set_fact:
var_accounts_passwords_pam_faillock_fail_interval: 900
tags:
- always
- name: set auth pam_faillock before pam_unix.so
pamd:
name: system-auth
type: auth
control: sufficient
module_path: pam_unix.so
new_type: auth
new_control: required
new_module_path: pam_faillock.so
module_arguments: 'preauth
silent
even_deny_root
deny={{ var_accounts_passwords_pam_faillock_deny }}
unlock_time={{ var_accounts_passwords_pam_faillock_unlock_time }}
fail_interval={{ var_accounts_passwords_pam_faillock_fail_interval }}'
state: before
tags:
- accounts_passwords_pam_faillock_deny_root
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80353-6
- NIST-800-53-AC-7(b)
- DISA-STIG-RHEL-07-010330
- name: set auth pam_faillock after pam_unix.so
pamd:
name: system-auth
type: auth
control: sufficient
module_path: pam_unix.so
new_type: auth
new_control: '[default=die]'
new_module_path: pam_faillock.so
module_arguments: 'preauth
silent
even_deny_root
deny={{ var_accounts_passwords_pam_faillock_deny }}
unlock_time={{ var_accounts_passwords_pam_faillock_unlock_time }}
fail_interval={{ var_accounts_passwords_pam_faillock_fail_interval }}'
state: after
tags:
- accounts_passwords_pam_faillock_deny_root
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80353-6
- NIST-800-53-AC-7(b)
- DISA-STIG-RHEL-07-010330
- name: set account pam_faillock before pam_unix.so
pamd:
name: system-auth
type: account
control: required
module_path: pam_unix.so
new_type: account
new_control: required
new_module_path: pam_faillock.so
state: before
tags:
- accounts_passwords_pam_faillock_deny_root
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80353-6
- NIST-800-53-AC-7(b)
- DISA-STIG-RHEL-07-010330
|
Set Lockout Time For Failed Password Attempts
[ref]ruleTo 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=3 unlock_time=never 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=3 unlock_time=never 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. Remediation Shell script: (show)
var_accounts_passwords_pam_faillock_unlock_time="never"
# Invoke the function without args, so its body is substituded right here.
function set_faillock_option_to_value_in_pam_file {
# If invoked with no arguments, exit. This is an intentional behavior.
[ $# -gt 1 ] || return 0
[ $# -ge 3 ] || die "$0 requires exactly zero, three, or four arguments"
[ $# -le 4 ] || die "$0 requires exactly zero, three, or four arguments"
local _pamFile="$1" _option="$2" _value="$3" _insert_lines_callback="$4"
# pam_faillock.so already present?
if grep -q "^auth.*pam_faillock.so.*" "$_pamFile"; then
# pam_faillock.so present, is the option present?
if grep -q "^auth.*[default=die].*pam_faillock.so.*authfail.*$_option=" "$_pamFile"; then
# both pam_faillock.so & option present, just correct option to the right value
sed -i --follow-symlinks "s/\(^auth.*required.*pam_faillock.so.*preauth.*silent.*\)\($_option *= *\).*/\1\2$_value/" "$_pamFile"
sed -i --follow-symlinks "s/\(^auth.*[default=die].*pam_faillock.so.*authfail.*\)\($_option *= *\).*/\1\2$_value/" "$_pamFile"
# pam_faillock.so present, but the option not yet
else
# append correct option value to appropriate places
sed -i --follow-symlinks "/^auth.*required.*pam_faillock.so.*preauth.*silent.*/ s/$/ $_option=$_value/" "$_pamFile"
sed -i --follow-symlinks "/^auth.*[default=die].*pam_faillock.so.*authfail.*/ s/$/ $_option=$_value/" "$_pamFile"
fi
# pam_faillock.so not present yet
else
test -z "$_insert_lines_callback" || "$_insert_lines_callback" "$_option" "$_value" "$_pamFile"
# insert pam_faillock.so preauth & authfail rows with proper value of the option in question
fi
}
set_faillock_option_to_value_in_pam_file
AUTH_FILES[0]="/etc/pam.d/system-auth"
AUTH_FILES[1]="/etc/pam.d/password-auth"
function insert_lines_if_pam_faillock_so_not_present {
local _option="$1" _value="$2" _pamFile="$3"
sed -i --follow-symlinks "/^auth.*sufficient.*pam_unix.so.*/i auth required pam_faillock.so preauth silent $_option=$_value" "$_pamFile"
sed -i --follow-symlinks "/^auth.*sufficient.*pam_unix.so.*/a auth [default=die] pam_faillock.so authfail $_option=$_value" "$_pamFile"
sed -i --follow-symlinks "/^account.*required.*pam_unix.so/i account required pam_faillock.so" "$_pamFile"
}
for pamFile in "${AUTH_FILES[@]}"
do
# 'true &&' has to be there due to build system limitation
true && set_faillock_option_to_value_in_pam_file "$pamFile" unlock_time "$var_accounts_passwords_pam_faillock_unlock_time" insert_lines_if_pam_faillock_so_not_present
done
|
Limit Password Reuse
[ref]ruleDo not allow users to reuse recent passwords. This can be
accomplished by using the remember option for the pam_unix
or pam_pwhistory PAM modules.
In the file /etc/pam.d/system-auth , append remember=5
to the line which refers to the pam_unix.so or pam_pwhistory.so module, as shown below:
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. Remediation Shell script: (show)
var_password_pam_unix_remember="5"
AUTH_FILES[0]="/etc/pam.d/system-auth"
AUTH_FILES[1]="/etc/pam.d/password-auth"
for pamFile in "${AUTH_FILES[@]}"
do
if grep -q "remember=" $pamFile; then
sed -i --follow-symlinks "s/\(^password.*sufficient.*pam_unix.so.*\)\(\(remember *= *\)[^ $]*\)/\1remember=$var_password_pam_unix_remember/" $pamFile
else
sed -i --follow-symlinks "/^password[[:space:]]\+sufficient[[:space:]]\+pam_unix.so/ s/$/ remember=$var_password_pam_unix_remember/" $pamFile
fi
done
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Strategy: | configure |
---|
- name: XCCDF Value var_password_pam_unix_remember # promote to variable
set_fact:
var_password_pam_unix_remember: 5
tags:
- always
- name: "Do not allow users to reuse recent passwords - system-auth (change)"
replace:
dest: /etc/pam.d/system-auth
follow: yes
regexp: '^(password\s+sufficient\s+pam_unix\.so\s.*remember\s*=\s*)(\S+)(.*)$'
replace: '\g<1>{{ var_password_pam_unix_remember }}\g<3>'
tags:
- accounts_password_pam_unix_remember
- medium_severity
- configure_strategy
- low_complexity
- medium_disruption
- CCE-26923-3
- NIST-800-53-IA-5(f)
- NIST-800-53-IA-5(1)(e)
- NIST-800-171-3.5.8
- PCI-DSS-Req-8.2.5
- CJIS-5.6.2.1.1
- DISA-STIG-RHEL-07-010270
- name: "Do not allow users to reuse recent passwords - system-auth (add)"
replace:
dest: /etc/pam.d/system-auth
follow: yes
regexp: '^password\s+sufficient\s+pam_unix\.so\s(?!.*remember\s*=\s*).*$'
replace: '\g<0> remember={{ var_password_pam_unix_remember }}'
tags:
- accounts_password_pam_unix_remember
- medium_severity
- configure_strategy
- low_complexity
- medium_disruption
- CCE-26923-3
- NIST-800-53-IA-5(f)
- NIST-800-53-IA-5(1)(e)
- NIST-800-171-3.5.8
- PCI-DSS-Req-8.2.5
- CJIS-5.6.2.1.1
- DISA-STIG-RHEL-07-010270
|
Set Interval For Counting Failed Password Attempts
[ref]ruleUtilizing pam_faillock.so , the fail_interval directive
configures the system to lock out an accounts after a number of incorrect login
attempts within a specified time period. 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=3 unlock_time=never 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=3 unlock_time=never fail_interval=900 - Add the following line immediately
before the pam_unix.so statement in the ACCOUNT section:
account required pam_faillock.so Rationale:By limiting the number of failed logon attempts the risk of unauthorized system
access via user password guessing, otherwise known as brute-forcing, is reduced.
Limits are imposed by locking the account. Remediation Shell script: (show)
var_accounts_passwords_pam_faillock_fail_interval="900"
# Invoke the function without args, so its body is substituded right here.
function set_faillock_option_to_value_in_pam_file {
# If invoked with no arguments, exit. This is an intentional behavior.
[ $# -gt 1 ] || return 0
[ $# -ge 3 ] || die "$0 requires exactly zero, three, or four arguments"
[ $# -le 4 ] || die "$0 requires exactly zero, three, or four arguments"
local _pamFile="$1" _option="$2" _value="$3" _insert_lines_callback="$4"
# pam_faillock.so already present?
if grep -q "^auth.*pam_faillock.so.*" "$_pamFile"; then
# pam_faillock.so present, is the option present?
if grep -q "^auth.*[default=die].*pam_faillock.so.*authfail.*$_option=" "$_pamFile"; then
# both pam_faillock.so & option present, just correct option to the right value
sed -i --follow-symlinks "s/\(^auth.*required.*pam_faillock.so.*preauth.*silent.*\)\($_option *= *\).*/\1\2$_value/" "$_pamFile"
sed -i --follow-symlinks "s/\(^auth.*[default=die].*pam_faillock.so.*authfail.*\)\($_option *= *\).*/\1\2$_value/" "$_pamFile"
# pam_faillock.so present, but the option not yet
else
# append correct option value to appropriate places
sed -i --follow-symlinks "/^auth.*required.*pam_faillock.so.*preauth.*silent.*/ s/$/ $_option=$_value/" "$_pamFile"
sed -i --follow-symlinks "/^auth.*[default=die].*pam_faillock.so.*authfail.*/ s/$/ $_option=$_value/" "$_pamFile"
fi
# pam_faillock.so not present yet
else
test -z "$_insert_lines_callback" || "$_insert_lines_callback" "$_option" "$_value" "$_pamFile"
# insert pam_faillock.so preauth & authfail rows with proper value of the option in question
fi
}
set_faillock_option_to_value_in_pam_file
AUTH_FILES[0]="/etc/pam.d/system-auth"
AUTH_FILES[1]="/etc/pam.d/password-auth"
function insert_lines_if_pam_faillock_so_not_present {
local _option="$1" _value="$2" _pamFile="$3"
sed -i --follow-symlinks "/^auth.*sufficient.*pam_unix.so.*/i auth required pam_faillock.so preauth silent $_option=$_value" "$_pamFile"
sed -i --follow-symlinks "/^auth.*sufficient.*pam_unix.so.*/a auth [default=die] pam_faillock.so authfail $_option=$_value" "$_pamFile"
sed -i --follow-symlinks "/^account.*required.*pam_unix.so/i account required pam_faillock.so" "$_pamFile"
}
for pamFile in "${AUTH_FILES[@]}"
do
# 'true &&' has to be there due to build system limitation
true && set_faillock_option_to_value_in_pam_file "$pamFile" fail_interval "$var_accounts_passwords_pam_faillock_fail_interval" insert_lines_if_pam_faillock_so_not_present
done
|
Set Deny For Failed Password Attempts
[ref]ruleTo 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=3 unlock_time=never 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=3 unlock_time=never 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. Remediation Shell script: (show)
var_accounts_passwords_pam_faillock_deny="3"
AUTH_FILES[0]="/etc/pam.d/system-auth"
AUTH_FILES[1]="/etc/pam.d/password-auth"
# This script fixes absence of pam_faillock.so in PAM stack or the
# absense of deny=[0-9]+ in pam_faillock.so arguments
# When inserting auth pam_faillock.so entries,
# the entry with preauth argument will be added before pam_unix.so module
# and entry with authfail argument will be added before pam_deny.so module.
# The placement of pam_faillock.so entries will not be changed
# if they are already present
# Invoke the function without args, so its body is substituded right here.
function set_faillock_option_to_value_in_pam_file {
# If invoked with no arguments, exit. This is an intentional behavior.
[ $# -gt 1 ] || return 0
[ $# -ge 3 ] || die "$0 requires exactly zero, three, or four arguments"
[ $# -le 4 ] || die "$0 requires exactly zero, three, or four arguments"
local _pamFile="$1" _option="$2" _value="$3" _insert_lines_callback="$4"
# pam_faillock.so already present?
if grep -q "^auth.*pam_faillock.so.*" "$_pamFile"; then
# pam_faillock.so present, is the option present?
if grep -q "^auth.*[default=die].*pam_faillock.so.*authfail.*$_option=" "$_pamFile"; then
# both pam_faillock.so & option present, just correct option to the right value
sed -i --follow-symlinks "s/\(^auth.*required.*pam_faillock.so.*preauth.*silent.*\)\($_option *= *\).*/\1\2$_value/" "$_pamFile"
sed -i --follow-symlinks "s/\(^auth.*[default=die].*pam_faillock.so.*authfail.*\)\($_option *= *\).*/\1\2$_value/" "$_pamFile"
# pam_faillock.so present, but the option not yet
else
# append correct option value to appropriate places
sed -i --follow-symlinks "/^auth.*required.*pam_faillock.so.*preauth.*silent.*/ s/$/ $_option=$_value/" "$_pamFile"
sed -i --follow-symlinks "/^auth.*[default=die].*pam_faillock.so.*authfail.*/ s/$/ $_option=$_value/" "$_pamFile"
fi
# pam_faillock.so not present yet
else
test -z "$_insert_lines_callback" || "$_insert_lines_callback" "$_option" "$_value" "$_pamFile"
# insert pam_faillock.so preauth & authfail rows with proper value of the option in question
fi
}
set_faillock_option_to_value_in_pam_file
function insert_lines_if_pam_faillock_so_not_present {
# insert pam_faillock.so preauth row with proper value of the 'deny' option before pam_unix.so
sed -i --follow-symlinks "/^auth.*pam_unix.so.*/i auth required pam_faillock.so preauth silent $_option=$_value" $_pamFile
# insert pam_faillock.so authfail row with proper value of the 'deny' option before pam_deny.so, after all modules which determine authentication outcome.
sed -i --follow-symlinks "/^auth.*pam_deny.so.*/i auth [default=die] pam_faillock.so authfail $_option=$_value" $_pamFile
}
for pamFile in "${AUTH_FILES[@]}"
do
# 'true &&' has to be there due to build system limitation
true && set_faillock_option_to_value_in_pam_file "$pamFile" deny "$var_accounts_passwords_pam_faillock_deny" insert_lines_if_pam_faillock_so_not_present
# add pam_faillock.so into account phase
if ! grep -q "^account.*required.*pam_faillock.so" $pamFile; then
sed -i --follow-symlinks "/^account.*required.*pam_unix.so/i account required pam_faillock.so" $pamFile
fi
done
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: XCCDF Value var_accounts_passwords_pam_faillock_deny # promote to variable
set_fact:
var_accounts_passwords_pam_faillock_deny: 3
tags:
- always
- name: XCCDF Value var_accounts_passwords_pam_faillock_unlock_time # promote to variable
set_fact:
var_accounts_passwords_pam_faillock_unlock_time: never
tags:
- always
- name: XCCDF Value var_accounts_passwords_pam_faillock_fail_interval # promote to variable
set_fact:
var_accounts_passwords_pam_faillock_fail_interval: 900
tags:
- always
- name: set auth pam_faillock before pam_unix.so
pamd:
name: system-auth
type: auth
control: sufficient
module_path: pam_unix.so
new_type: auth
new_control: required
new_module_path: pam_faillock.so
module_arguments: 'preauth
silent
deny={{ var_accounts_passwords_pam_faillock_deny }}
unlock_time={{ var_accounts_passwords_pam_faillock_unlock_time }}
fail_interval={{ var_accounts_passwords_pam_faillock_fail_interval }}'
state: before
tags:
- accounts_passwords_pam_faillock_deny
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27350-8
- NIST-800-53-AC-7(b)
- NIST-800-171-3.1.8
- PCI-DSS-Req-8.1.6
- CJIS-5.5.3
- DISA-STIG-RHEL-07-010320
- name: set auth pam_faillock after pam_unix.so
pamd:
name: system-auth
type: auth
control: sufficient
module_path: pam_unix.so
new_type: auth
new_control: '[default=die]'
new_module_path: pam_faillock.so
module_arguments: 'preauth
silent
deny={{ var_accounts_passwords_pam_faillock_deny }}
unlock_time={{ var_accounts_passwords_pam_faillock_unlock_time }}
fail_interval={{ var_accounts_passwords_pam_faillock_fail_interval }}'
state: after
tags:
- accounts_passwords_pam_faillock_deny
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27350-8
- NIST-800-53-AC-7(b)
- NIST-800-171-3.1.8
- PCI-DSS-Req-8.1.6
- CJIS-5.5.3
- DISA-STIG-RHEL-07-010320
- name: set account pam_faillock before pam_unix.so
pamd:
name: system-auth
type: account
control: required
module_path: pam_unix.so
new_type: account
new_control: required
new_module_path: pam_faillock.so
state: before
tags:
- accounts_passwords_pam_faillock_deny
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27350-8
- NIST-800-53-AC-7(b)
- NIST-800-171-3.1.8
- PCI-DSS-Req-8.1.6
- CJIS-5.5.3
- DISA-STIG-RHEL-07-010320
|
Set Password Quality Requirements
[ref]groupThe default pam_pwquality PAM module provides strength
checking for passwords. It performs a number of checks, such as
making sure passwords are not similar to dictionary words, are of
at least a certain length, are not the previous password reversed,
and are not simply a change of case from the previous password. It
can also require passwords to be in certain character classes. The
pam_pwquality module is the preferred way of configuring
password requirements.
The pam_cracklib PAM module can also provide strength
checking for passwords as the pam_pwquality module.
It performs a number of checks, such as making sure passwords are
not similar to dictionary words, are of at least a certain length,
are not the previous password reversed, and are not simply a change
of case from the previous password. It can also require passwords to
be in certain character classes.
The man pages pam_pwquality(8) and pam_cracklib(8)
provide information on the capabilities and configuration of
each. |
contains 10 rules |
Set Password Quality Requirements with pam_pwquality
[ref]groupThe pam_pwquality PAM module can be configured to meet
requirements for a variety of policies.
For example, to configure pam_pwquality to require at least one uppercase
character, lowercase character, digit, and other (special)
character, make sure that pam_pwquality exists in /etc/pam.d/system-auth :
password requisite pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type=
If no such line exists, add one as the first line of the password section in /etc/pam.d/system-auth .
Next, modify the settings in /etc/security/pwquality.conf to match the following:
difok = 4
minlen = 14
dcredit = -1
ucredit = -1
lcredit = -1
ocredit = -1
maxrepeat = 3
The arguments can be modified to ensure compliance with
your organization's security policy. Discussion of each parameter follows. |
contains 10 rules |
Set Password Minimum Length
[ref]ruleThe pam_pwquality module's minlen parameter controls requirements for
minimum characters required in a password. Add minlen=15
after pam_pwquality to set minimum password length requirements. Rationale:The shorter the password, the lower the number of possible combinations
that need to be tested before the password is compromised.
Password complexity, or strength, is a measure of the effectiveness of a
password in resisting attempts at guessing and brute-force attacks.
Password length is one factor of several that helps to determine strength
and how long it takes to crack a password. Use of more characters in a password
helps to exponentially increase the time and/or resources required to
compromose the password. Remediation Shell script: (show)
var_password_pam_minlen="15"
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/security/pwquality.conf' '^minlen' $var_password_pam_minlen 'CCE-27293-0' '%s = %s'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: XCCDF Value var_password_pam_minlen # promote to variable
set_fact:
var_password_pam_minlen: 15
tags:
- always
- name: Ensure PAM variable minlen is set accordingly
lineinfile:
create=yes
dest="/etc/security/pwquality.conf"
regexp="^minlen"
line="minlen = {{ var_password_pam_minlen }}"
tags:
- accounts_password_pam_minlen
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27293-0
- NIST-800-53-IA-5(1)(a)
- PCI-DSS-Req-8.2.3
- CJIS-5.6.2.1.1
- DISA-STIG-RHEL-07-010280
|
Set Password to Maximum of Consecutive Repeating Characters from Same Character Class
[ref]ruleThe pam_pwquality module's maxclassrepeat parameter controls requirements for
consecutive repeating characters from the same character class. When set to a positive number, it will reject passwords
which contain more than that number of consecutive characters from the same character class. Modify the
maxclassrepeat setting in /etc/security/pwquality.conf to equal 4
to prevent a run of (4 + 1) or more identical characters. Rationale:Use of a complex password helps to increase the time and resources required to comrpomise the password.
Password complexity, or strength, is a measure of the effectiveness of a password in resisting
attempts at guessing and brute-force attacks.
Password complexity is one factor of several that determines how long it takes to crack a password. The
more complex a password, the greater the number of possible combinations that need to be tested before the
password is compromised. Remediation Shell script: (show)
var_password_pam_maxclassrepeat="4"
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/security/pwquality.conf' '^maxclassrepeat' $var_password_pam_maxclassrepeat 'CCE-27512-3' '%s = %s'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: XCCDF Value var_password_pam_maxclassrepeat # promote to variable
set_fact:
var_password_pam_maxclassrepeat: 4
tags:
- always
- name: Ensure PAM variable maxclassrepeat is set accordingly
lineinfile:
create=yes
dest="/etc/security/pwquality.conf"
regexp="^maxclassrepeat"
line="maxclassrepeat = {{ var_password_pam_maxclassrepeat }}"
tags:
- accounts_password_pam_maxclassrepeat
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27512-3
- NIST-800-53-IA-5
- NIST-800-53-IA-5(c)
- DISA-STIG-RHEL-07-010190
|
Set Password Maximum Consecutive Repeating Characters
[ref]ruleThe pam_pwquality module's maxrepeat parameter controls requirements for
consecutive repeating characters. When set to a positive number, it will reject passwords
which contain more than that number of consecutive characters. Modify the maxrepeat setting
in /etc/security/pwquality.conf to equal 2 to prevent a
run of (2 + 1) or more identical characters. Rationale:Use of a complex password helps to increase the time and resources required to compromise the password.
Password complexity, or strength, is a measure of the effectiveness of a password in resisting attempts at
guessing and brute-force attacks.
Password complexity is one factor of several that determines how long it takes to crack a password. The more
complex the password, the greater the number of possible combinations that need to be tested before the
password is compromised.
Passwords with excessive repeating characters may be more vulnerable to password-guessing attacks. Remediation Shell script: (show)
var_password_pam_maxrepeat="2"
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/security/pwquality.conf' '^maxrepeat' $var_password_pam_maxrepeat 'CCE-27333-4' '%s = %s'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: XCCDF Value var_password_pam_maxrepeat # promote to variable
set_fact:
var_password_pam_maxrepeat: 2
tags:
- always
- name: Ensure PAM variable maxrepeat is set accordingly
lineinfile:
create=yes
dest="/etc/security/pwquality.conf"
regexp="^maxrepeat"
line="maxrepeat = {{ var_password_pam_maxrepeat }}"
tags:
- accounts_password_pam_maxrepeat
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27333-4
- NIST-800-53-IA-5
- NIST-800-53-IA-5(c)
- DISA-STIG-RHEL-07-010180
|
Set Password Strength Minimum Digit Characters
[ref]ruleThe pam_pwquality module's dcredit parameter controls requirements for
usage of digits in a password. When set to a negative number, any password will be required to
contain that many digits. When set to a positive number, pam_pwquality will grant +1 additional
length credit for each digit. Modify the dcredit setting in
/etc/security/pwquality.conf to require the use of a digit in passwords. Rationale:Use of a complex password helps to increase the time and resources required
to compromise the password. Password complexity, or strength, is a measure of
the effectiveness of a password in resisting attempts at guessing and brute-force
attacks.
Password complexity is one factor of several that determines how long it takes
to crack a password. The more complex the password, the greater the number of
possble combinations that need to be tested before the password is compromised.
Requiring digits makes password guessing attacks more difficult by ensuring a larger
search space. Remediation Shell script: (show)
var_password_pam_dcredit="-1"
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/security/pwquality.conf' '^dcredit' $var_password_pam_dcredit 'CCE-27214-6' '%s = %s'
Remediation Ansible snippet: (show)
- name: XCCDF Value var_password_pam_dcredit # promote to variable
set_fact:
var_password_pam_dcredit: -1
tags:
- always
- name: Ensure PAM variable dcredit is set accordingly
lineinfile:
create=yes
dest="/etc/security/pwquality.conf"
regexp="^dcredit"
line="dcredit = {{ var_password_pam_dcredit }}"
tags:
- accounts_password_pam_dcredit
- medium_severity
- CCE-27214-6
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(b)
- NIST-800-53-IA-5(c)
- NIST-800-53-194
- PCI-DSS-Req-8.2.3
- DISA-STIG-RHEL-07-010140
|
Set Password Strength Minimum Different Categories
[ref]ruleThe pam_pwquality module's minclass parameter controls
requirements for usage of different character classes, or types, of character
that must exist in a password before it is considered valid. For example,
setting this value to three (3) requires that any password must have characters
from at least three different categories in order to be approved. The default
value is zero (0), meaning there are no required classes. There are four
categories available:
* Upper-case characters
* Lower-case characters
* Digits
* Special characters (for example, punctuation)
Modify the minclass setting in /etc/security/pwquality.conf entry to require 4
differing categories of characters when changing passwords.Rationale:Use of a complex password helps to increase the time and resources required to compromise the password.
Password complexity, or strength, is a measure of the effectiveness of a password in resisting attempts
at guessing and brute-force attacks.
Password complexity is one factor of several that determines how long it takes to crack a password. The
more complex the password, the greater the number of possible combinations that need to be tested before
the password is compromised.
Requiring a minimum number of character categories makes password guessing attacks more difficult
by ensuring a larger search space. Remediation Shell script: (show)
var_password_pam_minclass="4"
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/security/pwquality.conf' '^minclass' $var_password_pam_minclass 'CCE-27115-5' '%s = %s'
Remediation Ansible snippet: (show)
- name: XCCDF Value var_password_pam_minclass # promote to variable
set_fact:
var_password_pam_minclass: 4
tags:
- always
- name: Ensure PAM variable minclass is set accordingly
lineinfile:
create=yes
dest="/etc/security/pwquality.conf"
regexp="^minclass"
line="minclass = {{ var_password_pam_minclass }}"
tags:
- accounts_password_pam_minclass
- medium_severity
- CCE-27115-5
- NIST-800-53-IA-5
- DISA-STIG-RHEL-07-010170
|
Set Password Strength Minimum Different Characters
[ref]ruleThe pam_pwquality module's difok parameter sets the number of characters
in a password that must not be present in and old password during a password change.
Modify the difok setting in /etc/security/pwquality.conf
to equal 8 to require differing characters
when changing passwords. Rationale:Use of a complex password helps to increase the time and resources
required to compromise the password. Password complexity, or strength,
is a measure of the effectiveness of a password in resisting attempts
at guessing and brute–force attacks.
Password complexity is one factor of several that determines how long
it takes to crack a password. The more complex the password, the
greater the number of possible combinations that need to be tested
before the password is compromised.
Requiring a minimum number of different characters during password changes ensures that
newly changed passwords should not resemble previously compromised ones.
Note that passwords which are changed on compromised systems will still be compromised, however. Remediation Shell script: (show)
var_password_pam_difok="8"
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/security/pwquality.conf' '^difok' $var_password_pam_difok 'CCE-26631-2' '%s = %s'
Remediation Ansible snippet: (show)
- name: XCCDF Value var_password_pam_difok # promote to variable
set_fact:
var_password_pam_difok: 8
tags:
- always
- name: Ensure PAM variable difok is set accordingly
lineinfile:
create=yes
dest="/etc/security/pwquality.conf"
regexp="^difok"
line="difok = {{ var_password_pam_difok }}"
tags:
- accounts_password_pam_difok
- medium_severity
- CCE-26631-2
- NIST-800-53-IA-5(b)
- NIST-800-53-IA-5(c)
- NIST-800-53-IA-5(1)(b)
- CJIS-5.6.2.1.1
- DISA-STIG-RHEL-07-010160
|
Set Password Strength Minimum Special Characters
[ref]ruleThe pam_pwquality module's ocredit= parameter controls requirements for
usage of special (or "other") characters in a password. When set to a negative number, any password will be
required to contain that many special characters. When set to a positive number, pam_pwquality will grant +1
additional length credit for each special character. Modify the ocredit setting in
/etc/security/pwquality.conf to equal -1 to require use of a special character in passwords. Rationale:Use of a complex password helps to increase the time and resources required
to compromise the password. Password complexity, or strength, is a measure of
the effectiveness of a password in resisting attempts at guessing and brute-force
attacks.
Password complexity is one factor of several that determines how long it takes
to crack a password. The more complex the password, the greater the number of
possble combinations that need to be tested before the password is compromised.
Requiring a minimum number of special characters makes password guessing attacks
more difficult by ensuring a larger search space. Remediation Shell script: (show)
var_password_pam_ocredit="-1"
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/security/pwquality.conf' '^ocredit' $var_password_pam_ocredit 'CCE-27360-7' '%s = %s'
Remediation Ansible snippet: (show)
- name: XCCDF Value var_password_pam_ocredit # promote to variable
set_fact:
var_password_pam_ocredit: -1
tags:
- always
- name: Ensure PAM variable ocredit is set accordingly
lineinfile:
create=yes
dest="/etc/security/pwquality.conf"
regexp="^ocredit"
line="ocredit = {{ var_password_pam_ocredit }}"
tags:
- accounts_password_pam_ocredit
- medium_severity
- CCE-27360-7
- NIST-800-53-IA-5(b)
- NIST-800-53-IA-5(c)
- NIST-800-53-IA-5(1)(a)
- DISA-STIG-RHEL-07-010150
|
Set Password Strength Minimum Lowercase Characters
[ref]ruleThe pam_pwquality module's lcredit parameter controls requirements for
usage of lowercase letters in a password. When set to a negative number, any password will be required to
contain that many lowercase characters. When set to a positive number, pam_pwquality will grant +1 additional
length credit for each lowercase character. Modify the lcredit setting in
/etc/security/pwquality.conf to require the use of a lowercase character in passwords. Rationale:Use of a complex password helps to increase the time and resources required
to compromise the password. Password complexity, or strength, is a measure of
the effectiveness of a password in resisting attempts at guessing and brute-force
attacks.
Password complexity is one factor of several that determines how long it takes
to crack a password. The more complex the password, the greater the number of
possble combinations that need to be tested before the password is compromised.
Requiring a minimum number of lowercase characters makes password guessing attacks
more difficult by ensuring a larger search space. Remediation Shell script: (show)
var_password_pam_lcredit="-1"
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/security/pwquality.conf' '^lcredit' $var_password_pam_lcredit 'CCE-27345-8' '%s = %s'
Remediation Ansible snippet: (show)
- name: XCCDF Value var_password_pam_lcredit # promote to variable
set_fact:
var_password_pam_lcredit: -1
tags:
- always
- name: Ensure PAM variable lcredit is set accordingly
lineinfile:
create=yes
dest="/etc/security/pwquality.conf"
regexp="^lcredit"
line="lcredit = {{ var_password_pam_lcredit }}"
tags:
- accounts_password_pam_lcredit
- medium_severity
- CCE-27345-8
- NIST-800-53-IA-5(b)
- NIST-800-53-IA-5(c)
- NIST-800-53-IA-5(1)(a)
- PCI-DSS-Req-8.2.3
- DISA-STIG-RHEL-07-010130
|
Set Password Strength Minimum Uppercase Characters
[ref]ruleThe pam_pwquality module's ucredit= parameter controls requirements for
usage of uppercase letters in a password. When set to a negative number, any password will be required to
contain that many uppercase characters. When set to a positive number, pam_pwquality will grant +1 additional
length credit for each uppercase character. Modify the ucredit setting in
/etc/security/pwquality.conf to require the use of an uppercase character in passwords. Rationale:Use of a complex password helps to increase the time and resources reuiqred to compromise the password.
Password complexity, or strength, is a measure of the effectiveness of a password in resisting attempts
at guessing and brute-force attacks.
Password complexity is one factor of several that determines how long it takes to crack a password. The more
complex the password, the greater the number of possible combinations that need to be tested before
the password is compromised. Remediation Shell script: (show)
var_password_pam_ucredit="-1"
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/security/pwquality.conf' '^ucredit' $var_password_pam_ucredit 'CCE-27200-5' '%s = %s'
Remediation Ansible snippet: (show)
- name: XCCDF Value var_password_pam_ucredit # promote to variable
set_fact:
var_password_pam_ucredit: -1
tags:
- always
- name: Ensure PAM variable ucredit is set accordingly
lineinfile:
create=yes
dest="/etc/security/pwquality.conf"
regexp="^ucredit"
line="ucredit = {{ var_password_pam_ucredit }}"
tags:
- accounts_password_pam_ucredit
- medium_severity
- CCE-27200-5
- NIST-800-53-IA-5(b)
- NIST-800-53-IA-5(c)
- NIST-800-53-IA-5(1)(a)
- PCI-DSS-Req-8.2.3
- DISA-STIG-RHEL-07-010120
|
Set Password Retry Prompts Permitted Per-Session
[ref]ruleTo configure the number of retry prompts that are permitted per-session:
Edit the pam_pwquality.so statement in /etc/pam.d/system-auth to
show retry=3 , or a lower value if site policy is more restrictive.
The DoD requirement is a maximum of 3 prompts per session. Rationale:Setting the password retry prompts that are permitted on a per-session basis to a low value
requires some software, such as SSH, to re-connect. This can slow down and
draw additional attention to some types of password-guessing attacks. Note that this
is different from account lockout, which is provided by the pam_faillock module. Remediation Shell script: (show)
var_password_pam_retry="3"
if grep -q "retry=" /etc/pam.d/system-auth; then
sed -i --follow-symlinks "s/\(retry *= *\).*/\1$var_password_pam_retry/" /etc/pam.d/system-auth
else
sed -i --follow-symlinks "/pam_pwquality.so/ s/$/ retry=$var_password_pam_retry/" /etc/pam.d/system-auth
fi
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Strategy: | configure |
---|
- name: XCCDF Value var_password_pam_retry # promote to variable
set_fact:
var_password_pam_retry: 3
tags:
- always
- name: "Set Password Retry Prompts Permitted Per-Session - system-auth (change)"
replace:
dest: /etc/pam.d/system-auth
follow: yes
regexp: '(^.*\spam_pwquality.so\s.*retry\s*=\s*)(\S+)(.*$)'
replace: '\g<1>{{ var_password_pam_retry }}\g<3>'
tags:
- accounts_password_pam_retry
- unknown_severity
- configure_strategy
- low_complexity
- medium_disruption
- CCE-27160-1
- NIST-800-53-CM-6(b)
- NIST-800-53-IA-5(c)
- CJIS-5.5.3
- DISA-STIG-RHEL-07-010119
- name: "Set Password Retry Prompts Permitted Per-Session - system-auth (add)"
replace:
dest: /etc/pam.d/system-auth
follow: yes
regexp: '^.*\spam_pwquality.so\s(?!.*retry\s*=\s*).*$'
replace: '\g<0> retry={{ var_password_pam_retry }}'
tags:
- accounts_password_pam_retry
- unknown_severity
- configure_strategy
- low_complexity
- medium_disruption
- CCE-27160-1
- NIST-800-53-CM-6(b)
- NIST-800-53-IA-5(c)
- CJIS-5.5.3
- DISA-STIG-RHEL-07-010119
|
Set Last Logon/Access Notification
[ref]ruleTo configure the system to notify users of last logon/access
using pam_lastlog , add or correct the pam_lastlog settings in
/etc/pam.d/postlogin to read as follows:
session [success=1 default=ignore] pam_succeed_if.so service !~ gdm* service !~ su* quiet
session [default=1] pam_lastlog.so nowtmp showfailed
session optional pam_lastlog.so silent noupdate showfailed Rationale:Users need to be aware of activity that occurs regarding
their account. Providing users with information regarding the number
of unsuccessful attempts that were made to login to their account
allows the user to determine if any unauthorized activity has occurred
and gives them an opportunity to notify administrators. Remediation Shell script: (show)
if $(grep -q "^session.*pam_lastlog.so" /etc/pam.d/postlogin) ; then
sed -i --follow-symlinks "/pam_lastlog.so/d" /etc/pam.d/postlogin
fi
echo "session [default=1] pam_lastlog.so nowtmp showfailed" >> /etc/pam.d/postlogin
echo "session optional pam_lastlog.so silent noupdate showfailed" >> /etc/pam.d/postlogin
|
Warning Banners for System Accesses
[ref]groupEach system should expose as little information about
itself as possible.
System banners, which are typically displayed just before a
login prompt, give out information about the service or the host's
operating system. This might include the distribution name and the
system kernel version, and the particular version of a network
service. This information can assist intruders in gaining access to
the system as it can reveal whether the system is running
vulnerable software. Most network services can be configured to
limit what information is displayed.
Many organizations implement security policies that require a
system banner provide notice of the system's ownership, provide
warning to unauthorized users, and remind authorized users of their
consent to monitoring. |
contains 3 rules |
Implement a GUI Warning Banner
[ref]groupIn the default graphical environment, users logging
directly into the system are greeted with a login screen provided
by the GNOME3 Display Manager (GDM). The warning banner should be
displayed in this graphical environment for these users.
The following sections describe how to configure the GDM login
banner. |
contains 2 rules |
Enable GNOME3 Login Warning Banner
[ref]ruleIn the default graphical environment, displaying a login warning banner
in the GNOME Display Manager's login screen can be enabled on the login
screen by setting banner-message-enable to true .
To enable, add or edit banner-message-enable to
/etc/dconf/db/gdm.d/00-security-settings . For example:
[org/gnome/login-screen]
banner-message-enable=true
Once the setting has been added, add a lock to
/etc/dconf/db/gdm.d/locks/00-security-settings-lock to prevent user modification.
For example:
/org/gnome/login-screen/banner-message-enable
After the settings have been set, run dconf update .
The banner text must also be set.Rationale:Display of a standardized and approved use notification before granting access to the operating system
ensures privacy and security notification verbiage used is consistent with applicable federal laws,
Executive Orders, directives, policies, regulations, standards, and guidance.
For U.S. Government systems, system use notifications are required only for access via login interfaces
with human users and are not required when such human interfaces do not exist. References:
FMT_MOF_EXT.1, SV-86483r3_rule, 1.7.2, 3.1.9, CCI-000048, AC-8(a), AC-8(b), AC-8(c)(1), AC-8(c)(2), AC-8(c)(3), OS-SRG-000023-GPOS-00006, SRG-OS-000024-GPOS-00007, SRG-OS-000228-GPOS-00088 Remediation Shell script: (show)
function include_dconf_settings {
:
}
# Function to configure DConf settings for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_settings 'org/gnome/login-screen' 'banner-message-enable' 'true' 'local.d' '10-banner'
#
function dconf_settings {
local _path=$1 _key=$2 _value=$3 _db=$4 _settingFile=$5
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: dconf_settings 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_settingsfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
SETTINGSFILES=($(grep -r "\[${_path}]" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1))
DCONFFILE="/etc/dconf/db/${_db}/${_settingFile}"
DBDIR="/etc/dconf/db/${_db}"
mkdir -p "${DBDIR}"
if [[ -z "${SETTINGSFILES[@]}" ]]
then
[ ! -z ${DCONFFILE} ] || $(echo "" >> ${DCONFFILE})
echo "[${_path}]" >> ${DCONFFILE}
echo "${_key}=${_value}" >> ${DCONFFILE}
else
if grep -q "${_key}" ${SETTINGSFILES[@]}
then
sed -i "s/${_key}\s*=\s*.*/${_key}=${_value}/g" ${SETTINGSFILES[@]}
else
sed -i "\|\[${_path}]|a\\${_key}=${_value}" ${SETTINGSFILES[@]}
fi
fi
}
# Function to configure DConf locks for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_lock 'org/gnome/login-screen' 'banner-message-enable' 'local.d' 'banner'
#
function dconf_lock {
local _key=$1 _setting=$2 _db=$3 _lockFile=$4
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: dconf_lock 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_lockfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/${_key}/${_setting}$" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/${_db}/locks"
mkdir -p "${LOCKSFOLDER}"
if [[ -z "${LOCKFILES}" ]]
then
echo "/${_key}/${_setting}" >> "/etc/dconf/db/${_db}/locks/${_lockFile}"
fi
}
include_dconf_settings
dconf_settings 'org/gnome/login-screen' 'banner-message-enable' 'true' 'gdm.d' '00-security-settings'
dconf_lock 'org/gnome/login-screen' 'banner-message-enable' 'gdm.d' '00-security-settings-lock'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
- name: "Enable GNOME3 Login Warning Banner"
ini_file:
dest: "/etc/dconf/db/local.d/00-security-settings"
section: "org/gnome/login-screen"
option: banner-message-enable
value: "true"
create: yes
tags:
- dconf_gnome_banner_enabled
- medium_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-26970-4
- NIST-800-53-AC-8(a)
- NIST-800-53-AC-8(b)
- NIST-800-53-AC-8(c)(1)
- NIST-800-53-AC-8(c)(2)
- NIST-800-53-AC-8(c)(3)
- NIST-800-171-3.1.9
- DISA-STIG-RHEL-07-010030
- name: "Prevent user modification of GNOME banner-message-enabled"
lineinfile:
path: /etc/dconf/db/local.d/locks/00-security-settings-lock
regexp: '^/org/gnome/login-screen/banner-message-enable'
line: '/org/gnome/login-screen/banner-message-enable'
create: yes
tags:
- dconf_gnome_banner_enabled
- medium_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-26970-4
- NIST-800-53-AC-8(a)
- NIST-800-53-AC-8(b)
- NIST-800-53-AC-8(c)(1)
- NIST-800-53-AC-8(c)(2)
- NIST-800-53-AC-8(c)(3)
- NIST-800-171-3.1.9
- DISA-STIG-RHEL-07-010030
|
Set the GNOME3 Login Warning Banner Text
[ref]ruleIn the default graphical environment, configuring the login warning banner text
in the GNOME Display Manager's login screen can be configured on the login
screen by setting banner-message-text to string 'APPROVED_BANNER'
where APPROVED_BANNER is the approved banner for your environment.
To enable, add or edit banner-message-text to
/etc/dconf/db/gdm.d/00-security-settings . For example:
[org/gnome/login-screen]
banner-message-text=string 'APPROVED_BANNER'
Once the setting has been added, add a lock to
/etc/dconf/db/gdm.d/locks/00-security-settings-lock to prevent user modification.
For example:
/org/gnome/login-screen/banner-message-text
After the settings have been set, run dconf update .
When entering a warning banner that spans several lines, remember
to begin and end the string with ' and use \n for new lines.Rationale:An appropriate warning message reinforces policy awareness during the logon
process and facilitates possible legal action against attackers. References:
FMT_MOF_EXT.1, SV-86485r3_rule, 1.7.2, 3.1.9, CCI-000048, AC-8(a), AC-8(b), AC-8(c), SRG-OS-000023-GPOS-00006, SRG-OS-000024-GPOS-00007, SRG-OS-000228-GPOS-00088 Remediation Shell script: (show)
login_banner_text="--[\s\n]+WARNING[\s\n]+--[\s\n]*This[\s\n]+system[\s\n]+is[\s\n]+for[\s\n]+the[\s\n]+use[\s\n]+of[\s\n]+authorized[\s\n]+users[\s\n]+only.[\s\n]+Individuals[\s\n]*using[\s\n]+this[\s\n]+computer[\s\n]+system[\s\n]+without[\s\n]+authority[\s\n]+or[\s\n]+in[\s\n]+excess[\s\n]+of[\s\n]+their[\s\n]*authority[\s\n]+are[\s\n]+subject[\s\n]+to[\s\n]+having[\s\n]+all[\s\n]+their[\s\n]+activities[\s\n]+on[\s\n]+this[\s\n]+system[\s\n]*monitored[\s\n]+and[\s\n]+recorded[\s\n]+by[\s\n]+system[\s\n]+personnel.[\s\n]+Anyone[\s\n]+using[\s\n]+this[\s\n]*system[\s\n]+expressly[\s\n]+consents[\s\n]+to[\s\n]+such[\s\n]+monitoring[\s\n]+and[\s\n]+is[\s\n]+advised[\s\n]+that[\s\n]*if[\s\n]+such[\s\n]+monitoring[\s\n]+reveals[\s\n]+possible[\s\n]+evidence[\s\n]+of[\s\n]+criminal[\s\n]+activity[\s\n]*system[\s\n]+personal[\s\n]+may[\s\n]+provide[\s\n]+the[\s\n]+evidence[\s\n]+of[\s\n]+such[\s\n]+monitoring[\s\n]+to[\s\n]+law[\s\n]*enforcement[\s\n]+officials."
function include_dconf_settings {
:
}
# Function to configure DConf settings for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_settings 'org/gnome/login-screen' 'banner-message-enable' 'true' 'local.d' '10-banner'
#
function dconf_settings {
local _path=$1 _key=$2 _value=$3 _db=$4 _settingFile=$5
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: dconf_settings 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_settingsfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
SETTINGSFILES=($(grep -r "\[${_path}]" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1))
DCONFFILE="/etc/dconf/db/${_db}/${_settingFile}"
DBDIR="/etc/dconf/db/${_db}"
mkdir -p "${DBDIR}"
if [[ -z "${SETTINGSFILES[@]}" ]]
then
[ ! -z ${DCONFFILE} ] || $(echo "" >> ${DCONFFILE})
echo "[${_path}]" >> ${DCONFFILE}
echo "${_key}=${_value}" >> ${DCONFFILE}
else
if grep -q "${_key}" ${SETTINGSFILES[@]}
then
sed -i "s/${_key}\s*=\s*.*/${_key}=${_value}/g" ${SETTINGSFILES[@]}
else
sed -i "\|\[${_path}]|a\\${_key}=${_value}" ${SETTINGSFILES[@]}
fi
fi
}
# Function to configure DConf locks for RHEL and Fedora systems.
#
# Example Call(s):
#
# dconf_lock 'org/gnome/login-screen' 'banner-message-enable' 'local.d' 'banner'
#
function dconf_lock {
local _key=$1 _setting=$2 _db=$3 _lockFile=$4
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: dconf_lock 'dconf_path' 'dconf_setting' 'dconf_db' 'dconf_lockfile'"
echo "Aborting."
exit 1
fi
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/${_key}/${_setting}$" "/etc/dconf/db/" | grep -v "distro\|ibus" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/${_db}/locks"
mkdir -p "${LOCKSFOLDER}"
if [[ -z "${LOCKFILES}" ]]
then
echo "/${_key}/${_setting}" >> "/etc/dconf/db/${_db}/locks/${_lockFile}"
fi
}
include_dconf_settings
expanded=$(echo "$login_banner_text" | sed 's/(\\\\\x27)\*/\\\x27/g;s/(\\\x27)\*//g;s/(\\\\\x27)/tamere/g;s/\^(\(.*\)|.*$/\1/g;s/\[\\s\\n\][+*]/ /g;s/\\//g;s/(n)\*/\\n/g;s/\x27/\\\x27/g;')
dconf_settings 'org/gnome/login-screen' 'banner-message-text' "string '${expanded}'" 'gdm.d' '00-security-settings'
dconf_lock 'org/gnome/login-screen' 'banner-message-text' 'gdm.d' '00-security-settings-lock'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
- name: XCCDF Value login_banner_text # promote to variable
set_fact:
login_banner_text: --[\s\n]+WARNING[\s\n]+--[\s\n]*This[\s\n]+system[\s\n]+is[\s\n]+for[\s\n]+the[\s\n]+use[\s\n]+of[\s\n]+authorized[\s\n]+users[\s\n]+only.[\s\n]+Individuals[\s\n]*using[\s\n]+this[\s\n]+computer[\s\n]+system[\s\n]+without[\s\n]+authority[\s\n]+or[\s\n]+in[\s\n]+excess[\s\n]+of[\s\n]+their[\s\n]*authority[\s\n]+are[\s\n]+subject[\s\n]+to[\s\n]+having[\s\n]+all[\s\n]+their[\s\n]+activities[\s\n]+on[\s\n]+this[\s\n]+system[\s\n]*monitored[\s\n]+and[\s\n]+recorded[\s\n]+by[\s\n]+system[\s\n]+personnel.[\s\n]+Anyone[\s\n]+using[\s\n]+this[\s\n]*system[\s\n]+expressly[\s\n]+consents[\s\n]+to[\s\n]+such[\s\n]+monitoring[\s\n]+and[\s\n]+is[\s\n]+advised[\s\n]+that[\s\n]*if[\s\n]+such[\s\n]+monitoring[\s\n]+reveals[\s\n]+possible[\s\n]+evidence[\s\n]+of[\s\n]+criminal[\s\n]+activity[\s\n]*system[\s\n]+personal[\s\n]+may[\s\n]+provide[\s\n]+the[\s\n]+evidence[\s\n]+of[\s\n]+such[\s\n]+monitoring[\s\n]+to[\s\n]+law[\s\n]*enforcement[\s\n]+officials.
tags:
- always
- name: "Set the GNOME3 Login Warning Banner Text"
file:
path: "/etc/dconf/db/{{ item }}"
owner: root
group: root
mode: 0755
state: directory
with_items:
- gdm.d
- gdm.d/locks
tags:
- dconf_gnome_login_banner_text
- medium_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-26892-0
- NIST-800-53-AC-8(a)
- NIST-800-53-AC-8(b)
- NIST-800-53-AC-8(c)
- NIST-800-171-3.1.9
- DISA-STIG-RHEL-07-010040
- name: "Set the GNOME3 Login Warning Banner Text"
file:
path: "/etc/dconf/db/gdm.d/{{ item }}"
owner: root
group: root
mode: 0644
state: touch
with_items:
- 00-security-settings
- locks/00-security-settings-lock
tags:
- dconf_gnome_login_banner_text
- medium_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-26892-0
- NIST-800-53-AC-8(a)
- NIST-800-53-AC-8(b)
- NIST-800-53-AC-8(c)
- NIST-800-171-3.1.9
- DISA-STIG-RHEL-07-010040
- name: "Set the GNOME3 Login Warning Banner Text"
ini_file:
dest: /etc/dconf/db/gdm.d/00-security-settings
section: org/gnome/login-screen
option: banner-message-text
value: string '{{ login_banner_text }}'
create: yes
tags:
- dconf_gnome_login_banner_text
- medium_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-26892-0
- NIST-800-53-AC-8(a)
- NIST-800-53-AC-8(b)
- NIST-800-53-AC-8(c)
- NIST-800-171-3.1.9
- DISA-STIG-RHEL-07-010040
- name: "Prevent user modification of the GNOME3 Login Warning Banner Text"
lineinfile:
path: '/etc/dconf/db/gdm.d/locks/00-security-settings-lock'
regexp: '^org/gnome/login-screen/banner-message-text$'
line: 'org/gnome/login-screen/banner-message-text'
state: present
tags:
- dconf_gnome_login_banner_text
- medium_severity
- unknown_strategy
- low_complexity
- medium_disruption
- CCE-26892-0
- NIST-800-53-AC-8(a)
- NIST-800-53-AC-8(b)
- NIST-800-53-AC-8(c)
- NIST-800-171-3.1.9
- DISA-STIG-RHEL-07-010040
|
Modify the System Login Banner
[ref]ruleTo configure the system login banner edit /etc/issue . Replace
the default text with a message compliant with the local site policy
or a legal disclaimer.
The DoD required text is either:
You are accessing a U.S. Government (USG) Information System (IS) that is
provided for USG-authorized use only. By using this IS (which includes any
device attached to this IS), you consent to the following conditions:
-The USG routinely intercepts and monitors communications on this IS for purposes
including, but not limited to, penetration testing, COMSEC monitoring, network
operations and defense, personnel misconduct (PM), law enforcement (LE), and
counterintelligence (CI) investigations.
-At any time, the USG may inspect and seize data stored on this IS.
-Communications using, or data stored on, this IS are not private, are subject
to routine monitoring, interception, and search, and may be disclosed or used
for any USG-authorized purpose.
-This IS includes security measures (e.g., authentication and access controls)
to protect USG interests -- not for your personal benefit or privacy.
-Notwithstanding the above, using this IS does not constitute consent to PM, LE or CI investigative
searching or monitoring of the content of privileged communications, or work
product, related to personal representation or services by attorneys,
psychotherapists, or clergy, and their assistants. Such communications and work
product are private and confidential. See User Agreement for details.
OR:
I've read & consent to terms in IS user agreem't. Rationale:Display of a standardized and approved use notification before granting access to the operating system
ensures privacy and security notification verbiage used is consistent with applicable federal laws,
Executive Orders, directives, policies, regulations, standards, and guidance.
System use notifications are required only for access via login interfaces with human users and
are not required when such human interfaces do not exist. References:
FMT_MOF_EXT.1, SV-86487r2_rule, 1.7.1.2, 3.1.9, CCI-000048, AC-8(a), AC-8(b), AC-8(c)(1), AC-8(c)(2), AC-8(c)(3), SRG-OS-000023-GPOS-00006, SRG-OS-000024-GPOS-00007 Remediation Shell script: (show)
login_banner_text="--[\s\n]+WARNING[\s\n]+--[\s\n]*This[\s\n]+system[\s\n]+is[\s\n]+for[\s\n]+the[\s\n]+use[\s\n]+of[\s\n]+authorized[\s\n]+users[\s\n]+only.[\s\n]+Individuals[\s\n]*using[\s\n]+this[\s\n]+computer[\s\n]+system[\s\n]+without[\s\n]+authority[\s\n]+or[\s\n]+in[\s\n]+excess[\s\n]+of[\s\n]+their[\s\n]*authority[\s\n]+are[\s\n]+subject[\s\n]+to[\s\n]+having[\s\n]+all[\s\n]+their[\s\n]+activities[\s\n]+on[\s\n]+this[\s\n]+system[\s\n]*monitored[\s\n]+and[\s\n]+recorded[\s\n]+by[\s\n]+system[\s\n]+personnel.[\s\n]+Anyone[\s\n]+using[\s\n]+this[\s\n]*system[\s\n]+expressly[\s\n]+consents[\s\n]+to[\s\n]+such[\s\n]+monitoring[\s\n]+and[\s\n]+is[\s\n]+advised[\s\n]+that[\s\n]*if[\s\n]+such[\s\n]+monitoring[\s\n]+reveals[\s\n]+possible[\s\n]+evidence[\s\n]+of[\s\n]+criminal[\s\n]+activity[\s\n]*system[\s\n]+personal[\s\n]+may[\s\n]+provide[\s\n]+the[\s\n]+evidence[\s\n]+of[\s\n]+such[\s\n]+monitoring[\s\n]+to[\s\n]+law[\s\n]*enforcement[\s\n]+officials."
# There was a regular-expression matching various banners, needs to be expanded
# When there are multiple banners in login_banner_text, the first banner should be the one for RHEL7
expanded=$(echo "$login_banner_text" | sed 's/(\\\\\x27)\*/\\\x27/g;s/(\\\x27)\*//g;s/\^(\(.*\)|.*$/\1/g;s/\[\\s\\n\][+*]/ /g;s/\\//g;s/[^-]- /\n\n-/g;s/(n)\**//g')
formatted=$(echo "$expanded" | fold -sw 80)
cat <<EOF >/etc/issue
$formatted
EOF
printf "\n" >> /etc/issue
|
Secure Session Configuration Files for Login Accounts
[ref]groupWhen a user logs into a Unix account, the system
configures the user's session by reading a number of files. Many of
these files are located in the user's home directory, and may have
weak permissions as a result of user error or misconfiguration. If
an attacker can modify or even read certain types of account
configuration information, they can often gain full access to the
affected user's account. Therefore, it is important to test and
correct configuration file permissions for interactive accounts,
particularly those of privileged users such as root or system
administrators. |
contains 4 rules |
Ensure that Users Have Sensible Umask Values
[ref]groupThe umask setting controls the default permissions
for the creation of new files.
With a default umask setting of 077, files and directories
created by users will not be readable by any other user on the
system. Users who wish to make specific files group- or
world-readable can accomplish this by using the chmod command.
Additionally, users can make all their files readable to their
group by default by setting a umask of 027 in their shell
configuration files. If default per-user groups exist (that is, if
every user has a default group whose name is the same as that
user's username and whose only member is the user), then it may
even be safe for users to select a umask of 007, making it very
easy to intentionally share files with groups of which the user is
a member.
|
contains 1 rule |
Ensure the Default Umask is Set Correctly in login.defs
[ref]ruleTo ensure the default umask controlled by /etc/login.defs is set properly,
add or correct the UMASK setting in /etc/login.defs to read as follows:
UMASK 077 Rationale:The umask value influences the permissions assigned to files when they are created.
A misconfigured umask value could result in files with excessive permissions that can be read and
written to by unauthorized users. Remediation Shell script: (show)
var_accounts_user_umask="077"
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/login.defs' '^UMASK' "$var_accounts_user_umask" 'CCE-80205-8' '%s %s'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: XCCDF Value var_accounts_user_umask # promote to variable
set_fact:
var_accounts_user_umask: 077
tags:
- always
- name: Ensure the Default UMASK is Set Correctly
lineinfile:
create: yes
dest: /etc/login.defs
regexp: ^UMASK
line: "UMASK {{ var_accounts_user_umask }}"
tags:
- accounts_umask_etc_login_defs
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80205-8
- NIST-800-53-CM-6(b)
- NIST-800-53-SA-8
- DISA-STIG-RHEL-07-020240
|
Set Interactive Session Timeout
[ref]ruleSetting the TMOUT option in /etc/profile ensures that
all user sessions will terminate based on inactivity. The TMOUT
setting in /etc/profile should read as follows:
TMOUT=600 Rationale:Terminating an idle session within a short time period reduces
the window of opportunity for unauthorized personnel to take control of a
management session enabled on the console or console port that has been
left unattended. Remediation Shell script: (show)
var_accounts_tmout="600"
if grep --silent ^TMOUT /etc/profile ; then
sed -i "s/^TMOUT.*/TMOUT=$var_accounts_tmout/g" /etc/profile
else
echo -e "\n# Set TMOUT to $var_accounts_tmout per security requirements" >> /etc/profile
echo "TMOUT=$var_accounts_tmout" >> /etc/profile
fi
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: XCCDF Value var_accounts_tmout # promote to variable
set_fact:
var_accounts_tmout: 600
tags:
- always
- name: Set Interactive Session Timeout
lineinfile:
create: yes
dest: /etc/profile
regexp: ^#?TMOUT
line: "TMOUT={{ var_accounts_tmout }}"
tags:
- accounts_tmout
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27557-8
- NIST-800-53-AC-12
- NIST-800-53-SC-10
- NIST-800-171-3.1.11
- DISA-STIG-RHEL-07-040160
|
Ensure the Logon Failure Delay is Set Correctly in login.defs
[ref]ruleTo ensure the logon failure delay controlled by /etc/login.defs is set properly,
add or correct the FAIL_DELAY setting in /etc/login.defs to read as follows:
FAIL_DELAY 4 Rationale:Increasing the time between a failed authentication attempt and re-prompting to
enter credentials helps to slow a single-threaded brute force attack. Remediation Shell script: (show)
# Set variables
var_accounts_fail_delay="4"
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/login.defs' '^FAIL_DELAY' "$var_accounts_fail_delay" 'CCE-80352-8' '%s %s'
Remediation Ansible snippet: (show)
- name: XCCDF Value var_accounts_fail_delay # promote to variable
set_fact:
var_accounts_fail_delay: 4
tags:
- always
- name: Set accounts logon fail delay
lineinfile:
dest: /etc/login.defs
regexp: ^FAIL_DELAY
line: "FAIL_DELAY {{ var_accounts_fail_delay }}"
tags:
- accounts_logon_fail_delay
- unknown_severity
- CCE-80352-8
- NIST-800-53-CM-6(b)
- DISA-STIG-RHEL-07-010430
|
Limit the Number of Concurrent Login Sessions Allowed Per User
[ref]ruleLimiting the number of allowed users and sessions per user can limit risks related to Denial of
Service attacks. This addresses concurrent sessions for a single account and does not address
concurrent sessions by a single user via multiple accounts. To set the number of concurrent
sessions per user add the following line in /etc/security/limits.conf :
* hard maxlogins 10 Rationale:Limiting simultaneous user logins can insulate the system from denial of service
problems caused by excessive logins. Automated login processes operating improperly or
maliciously may result in an exceptional number of simultaneous login sessions. Remediation Shell script: (show)
var_accounts_max_concurrent_login_sessions="10"
if grep -q '^[^#]*\<maxlogins\>' /etc/security/limits.d/*.conf; then
sed -i "/^[^#]*\<maxlogins\>/ s/maxlogins.*/maxlogins $var_accounts_max_concurrent_login_sessions/" /etc/security/limits.d/*.conf
elif grep -q '^[^#]*\<maxlogins\>' /etc/security/limits.conf; then
sed -i "/^[^#]*\<maxlogins\>/ s/maxlogins.*/maxlogins $var_accounts_max_concurrent_login_sessions/" /etc/security/limits.conf
else
echo "* hard maxlogins $var_accounts_max_concurrent_login_sessions" >> /etc/security/limits.conf
fi
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: XCCDF Value var_accounts_max_concurrent_login_sessions # promote to variable
set_fact:
var_accounts_max_concurrent_login_sessions: 10
tags:
- always
- name: "Limit the Number of Concurrent Login Sessions Allowed Per User"
lineinfile:
state: present
dest: /etc/security/limits.conf
insertbefore: "^# End of file"
regexp: "^#?\\*.*maxlogins"
line: "* hard maxlogins {{ var_accounts_max_concurrent_login_sessions }}"
tags:
- accounts_max_concurrent_login_sessions
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27081-9
- NIST-800-53-AC-10
- CJIS-5.5.2.2
- DISA-STIG-RHEL-07-040000
|
System Accounting with <tt>auditd</tt>
[ref]groupThe audit service provides substantial capabilities
for recording system activities. By default, the service audits about
SELinux AVC denials and certain types of security-relevant events
such as system logins, account modifications, and authentication
events performed by programs such as sudo.
Under its default configuration, auditd has modest disk space
requirements, and should not noticeably impact system performance.
NOTE: The Linux Audit daemon auditd can be configured to use
the augenrules program to read audit rules files (*.rules )
located in /etc/audit/rules.d location and compile them to create
the resulting form of the /etc/audit/audit.rules configuration file
during the daemon startup (default configuration). Alternatively, the auditd
daemon can use the auditctl utility to read audit rules from the
/etc/audit/audit.rules configuration file during daemon startup,
and load them into the kernel. The expected behavior is configured via the
appropriate ExecStartPost directive setting in the
/usr/lib/systemd/system/auditd.service configuration file.
To instruct the auditd daemon to use the augenrules program
to read audit rules (default configuration), use the following setting:
ExecStartPost=-/sbin/augenrules --load
in the /usr/lib/systemd/system/auditd.service configuration file.
In order to instruct the auditd daemon to use the auditctl
utility to read audit rules, use the following setting:
ExecStartPost=-/sbin/auditctl -R /etc/audit/audit.rules
in the /usr/lib/systemd/system/auditd.service configuration file.
Refer to [Service] section of the /usr/lib/systemd/system/auditd.service
configuration file for further details.
Government networks often have substantial auditing
requirements and auditd can be configured to meet these
requirements.
Examining some example audit records demonstrates how the Linux audit system
satisfies common requirements.
The following example from Fedora Documentation available at
https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/SELinux_Users_and_Administrators_Guide/sect-Security-Enhanced_Linux-Troubleshooting-Fixing_Problems.html#sect-Security-Enhanced_Linux-Fixing_Problems-Raw_Audit_Messages
shows the substantial amount of information captured in a
two typical "raw" audit messages, followed by a breakdown of the most important
fields. In this example the message is SELinux-related and reports an AVC
denial (and the associated system call) that occurred when the Apache HTTP
Server attempted to access the /var/www/html/file1 file (labeled with
the samba_share_t type):
type=AVC msg=audit(1226874073.147:96): avc: denied { getattr } for pid=2465 comm="httpd"
path="/var/www/html/file1" dev=dm-0 ino=284133 scontext=unconfined_u:system_r:httpd_t:s0
tcontext=unconfined_u:object_r:samba_share_t:s0 tclass=file
type=SYSCALL msg=audit(1226874073.147:96): arch=40000003 syscall=196 success=no exit=-13
a0=b98df198 a1=bfec85dc a2=54dff4 a3=2008171 items=0 ppid=2463 pid=2465 auid=502 uid=48
gid=48 euid=48 suid=48 fsuid=48 egid=48 sgid=48 fsgid=48 tty=(none) ses=6 comm="httpd"
exe="/usr/sbin/httpd" subj=unconfined_u:system_r:httpd_t:s0 key=(null)
msg=audit(1226874073.147:96) - The number in parentheses is the unformatted time stamp (Epoch time)
for the event, which can be converted to standard time by using the
date command.
{ getattr } - The item in braces indicates the permission that was denied.
getattr
indicates the source process was trying to read the target file's status information.
This occurs before reading files. This action is denied due to the file being
accessed having the wrong label. Commonly seen permissions include getattr ,
read , and write .
comm="httpd" - The executable that launched the process. The full path of the executable is
found in the
exe= section of the system call (SYSCALL ) message,
which in this case, is exe="/usr/sbin/httpd" .
path="/var/www/html/file1" - The path to the object (target) the process attempted to access.
scontext="unconfined_u:system_r:httpd_t:s0" - The SELinux context of the process that attempted the denied action. In
this case, it is the SELinux context of the Apache HTTP Server, which is running
in the
httpd_t domain.
tcontext="unconfined_u:object_r:samba_share_t:s0" - The SELinux context of the object (target) the process attempted to access.
In this case, it is the SELinux context of
file1 . Note: the samba_share_t
type is not accessible to processes running in the httpd_t domain.
- From the system call (
SYSCALL ) message, two items are of interest:
success=no : indicates whether the denial (AVC) was enforced or not.
success=no indicates the system call was not successful (SELinux denied
access). success=yes indicates the system call was successful - this can
be seen for permissive domains or unconfined domains, such as initrc_t
and kernel_t .
exe="/usr/sbin/httpd" : the full path to the executable that launched
the process, which in this case, is exe="/usr/sbin/httpd" .
|
contains 83 rules |
Configure <tt>auditd</tt> Data Retention
[ref]groupThe audit system writes data to /var/log/audit/audit.log . By default,
auditd rotates 5 logs by size (6MB), retaining a maximum of 30MB of
data in total, and refuses to write entries when the disk is too
full. This minimizes the risk of audit data filling its partition
and impacting other services. This also minimizes the risk of the audit
daemon temporarily disabling the system if it cannot write audit log (which
it can be configured to do).
For a busy
system or a system which is thoroughly auditing system activity, the default settings
for data retention may be
insufficient. The log file size needed will depend heavily on what types
of events are being audited. First configure auditing to log all the events of
interest. Then monitor the log size manually for awhile to determine what file
size will allow you to keep the required data for the correct time period.
Using a dedicated partition for /var/log/audit prevents the
auditd logs from disrupting system functionality if they fill, and,
more importantly, prevents other activity in /var from filling the
partition and stopping the audit trail. (The audit logs are size-limited and
therefore unlikely to grow without bound unless configured to do so.) Some
machines may have requirements that no actions occur which cannot be audited.
If this is the case, then auditd can be configured to halt the machine
if it runs out of space. Note: Since older logs are rotated,
configuring auditd this way does not prevent older logs from being
rotated away before they can be viewed.
If your system is configured to halt when logging cannot be performed, make
sure this can never happen under normal circumstances! Ensure that
/var/log/audit is on its own partition, and that this partition is
larger than the maximum amount of data auditd will retain
normally. |
contains 8 rules |
Configure auditd mail_acct Action on Low Disk Space
[ref]ruleThe auditd service can be configured to send email to
a designated account in certain situations. Add or correct the following line
in /etc/audit/auditd.conf to ensure that administrators are notified
via email for those situations:
action_mail_acct = root Rationale:Email sent to the root account is typically aliased to the
administrators of the system, who can take appropriate action. References:
SV-86717r2_rule, 5.2.1.2, 5.4.1.1, 3.3.1, CCI-001855, 164.312(a)(2)(ii), A.12.3.1, AU-1(b), AU-4, AU-5(1), AU-5(a), IR-5, Req-10.7.a, SRG-OS-000343-GPOS-00134 Remediation Shell script: (show)
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
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: XCCDF Value var_auditd_action_mail_acct # promote to variable
set_fact:
var_auditd_action_mail_acct: root
tags:
- always
- name: Configure auditd mail_acct Action on Low Disk Space
lineinfile:
dest: /etc/audit/auditd.conf
line: "action_mail_acct = {{ var_auditd_action_mail_acct }}"
state: present
#notify: reload auditd
tags:
- auditd_data_retention_action_mail_acct
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27394-6
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-4
- NIST-800-53-AU-5(1)
- NIST-800-53-AU-5(a)
- NIST-800-53-IR-5
- NIST-800-171-3.3.1
- PCI-DSS-Req-10.7.a
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030350
|
Configure auditd flush priority
[ref]ruleThe auditd service can be configured to
synchronously write audit event data to disk. Add or correct the following
line in /etc/audit/auditd.conf to ensure that audit event data is
fully synchronized with the log files on the disk:
flush = data Rationale:Audit data should be synchronously written to disk to ensure
log integrity. These parameters assure that all audit event data is fully
synchronized with the log files on the disk. References:
3.3.1, CCI-001576, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AU-9, AU-12(1) Remediation Shell script: (show)
var_auditd_flush="data"
AUDITCONFIG=/etc/audit/auditd.conf
# if flush is present, flush param edited to var_auditd_flush
# else flush param is defined by var_auditd_flush
#
# the freq param is only used value 'incremental' and will be
# commented out if flush != incremental
#
# if flush == incremental && freq param is not defined, it
# will be defined as the package-default value of 20
grep -q ^flush $AUDITCONFIG && \
sed -i 's/^flush.*/flush = '"$var_auditd_flush"'/g' $AUDITCONFIG
if ! [ $? -eq 0 ]; then
echo "flush = $var_auditd_flush" >> $AUDITCONFIG
fi
if ! [ "$var_auditd_flush" == "incremental" ]; then
sed -i 's/^freq/##freq/g' $AUDITCONFIG
elif [ "$var_auditd_flush" == "incremental" ]; then
grep -q freq $AUDITCONFIG && \
sed -i 's/^#\+freq/freq/g' $AUDITCONFIG
if ! [ $? -eq 0 ]; then
echo "freq = 20" >> $AUDITCONFIG
fi
fi
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: Configure auditd Flush Priority
lineinfile:
dest: /etc/audit/auditd.conf
regexp: '.*flush.*'
line: flush = data
#notify: reload auditd
tags:
- auditd_data_retention_flush
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27331-8
- NIST-800-53-AU-9
- NIST-800-53-AU-12(1)
- NIST-800-171-3.3.1
|
Configure auditd Max Log File Size
[ref]ruleDetermine 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. Remediation Shell script: (show)
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
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: XCCDF Value var_auditd_max_log_file # promote to variable
set_fact:
var_auditd_max_log_file: 6
tags:
- always
- name: Configure auditd Max Log File Size
lineinfile:
dest: /etc/audit/auditd.conf
line: "max_log_file {{ var_auditd_max_log_file }}"
state: present
#notify: reload auditd
tags:
- auditd_data_retention_max_log_file
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27319-3
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-11
- NIST-800-53-IR-5
- PCI-DSS-Req-10.7
- CJIS-5.4.1.1
|
Configure auditd space_left Action on Low Disk Space
[ref]ruleThe auditd service can be configured to take an action
when disk space starts to run low.
Edit the file /etc/audit/auditd.conf . Modify the following line,
substituting ACTION appropriately:
space_left_action = ACTION
Possible values for ACTION are described in the auditd.conf man page.
These include:
syslog email exec suspend single halt
Set this to email (instead of the default,
which is suspend ) as it is more likely to get prompt attention. Acceptable values
also include suspend , single , and halt .Rationale:Notifying administrators of an impending disk space problem may
allow them to take corrective action prior to any disruption. References:
5.2.1.2, 5.4.1.1, 3.3.1, CCI-001855, 164.312(a)(2)(ii), A.12.3.1, AU-1(b), AU-4, AU-5(1), AU-5(b), IR-5, Req-10.7, SRG-OS-000343-GPOS-00134 Remediation Shell script: (show)
var_auditd_space_left_action="email"
grep -q ^space_left_action /etc/audit/auditd.conf && \
sed -i "s/space_left_action.*/space_left_action = $var_auditd_space_left_action/g" /etc/audit/auditd.conf
if ! [ $? -eq 0 ]; then
echo "space_left_action = $var_auditd_space_left_action" >> /etc/audit/auditd.conf
fi
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: XCCDF Value var_auditd_space_left_action # promote to variable
set_fact:
var_auditd_space_left_action: email
tags:
- always
- name: Configure auditd space_left Action on Low Disk Space
lineinfile:
dest: /etc/audit/auditd.conf
line: "space_left_action = {{ var_auditd_space_left_action }}"
regexp: ^space_left_action*
#notify: reload auditd
tags:
- auditd_data_retention_space_left_action
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27375-5
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-4
- NIST-800-53-AU-5(1)
- NIST-800-53-AU-5(b)
- NIST-800-53-IR-5
- NIST-800-171-3.3.1
- PCI-DSS-Req-10.7
- CJIS-5.4.1.1
|
Configure auditd to use audispd's syslog plugin
[ref]ruleTo 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 References:
FAU_GEN.1.1.c, 5.4.1.1, 3.3.1, CCI-000136, 164.308(a)(1)(ii)(D), 164.308(a)(5)(ii)(B), 164.308(a)(5)(ii)(C), 164.308(a)(6)(ii), 164.308(a)(8), 164.310(d)(2)(iii), 164.312(b), 164.314(a)(2)(i)(C), 164.314(a)(2)(iii), AU-1(b), AU-3(2), IR-5, Req-10.5.3 Remediation Shell script: (show)
grep -q ^active /etc/audisp/plugins.d/syslog.conf && \
sed -i "s/active.*/active = yes/g" /etc/audisp/plugins.d/syslog.conf
if ! [ $? -eq 0 ]; then
echo "active = yes" >> /etc/audisp/plugins.d/syslog.conf
fi
|
Configure auditd admin_space_left Action on Low Disk Space
[ref]ruleThe auditd service can be configured to take an action
when disk space is running low but prior to running out of space completely.
Edit the file /etc/audit/auditd.conf . Add or modify the following line,
substituting 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. References:
SV-86715r1_rule, 5.2.1.2, 5.4.1.1, 3.3.1, CCI-000140, CCI-001343, 164.312(a)(2)(ii), A.12.3.1, AU-1(b), AU-4, AU-5(b), IR-5, Req-10.7 Remediation Shell script: (show)
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
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: XCCDF Value var_auditd_admin_space_left_action # promote to variable
set_fact:
var_auditd_admin_space_left_action: single
tags:
- always
- name: Configure auditd admin_space_left Action on Low Disk Space
lineinfile:
dest: /etc/audit/auditd.conf
line: "admin_space_left_action = {{ var_auditd_admin_space_left_action }}"
regexp: "^admin_space_left_action*"
#notify: reload auditd
tags:
- auditd_data_retention_admin_space_left_action
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27370-6
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-4
- NIST-800-53-AU-5(b)
- NIST-800-53-IR-5
- NIST-800-171-3.3.1
- PCI-DSS-Req-10.7
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030340
|
Configure auditd Number of Logs Retained
[ref]ruleDetermine 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. Remediation Shell script: (show)
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_action Upon Reaching Maximum Log Size
[ref]ruleThe 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:
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. Remediation Shell script: (show)
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
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: XCCDF Value var_auditd_max_log_file_action # promote to variable
set_fact:
var_auditd_max_log_file_action: rotate
tags:
- always
- name: Configure auditd max_log_file_action Upon Reaching Maximum Log Size
lineinfile:
dest: /etc/audit/auditd.conf
line: "max_log_file_action {{ var_auditd_max_log_file_action }}"
state: present
#notify: reload auditd
tags:
- auditd_data_retention_max_log_file_action
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27231-0
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-4
- NIST-800-53-AU-11
- NIST-800-53-IR-5
- PCI-DSS-Req-10.7
- CJIS-5.4.1.1
|
Configure <tt>auditd</tt> Rules for Comprehensive Auditing
[ref]groupThe auditd program can perform comprehensive
monitoring of system activity. This section describes recommended
configuration settings for comprehensive auditing, but a full
description of the auditing system's capabilities is beyond the
scope of this guide. The mailing list linux-audit@redhat.com exists
to facilitate community discussion of the auditing system.
The audit subsystem supports extensive collection of events, including:
- Tracing of arbitrary system calls (identified by name or number)
on entry or exit.
- Filtering by PID, UID, call success, system call argument (with
some limitations), etc.
- Monitoring of specific files for modifications to the file's
contents or metadata.
Auditing rules at startup are controlled by the file /etc/audit/audit.rules .
Add rules to it to meet the auditing requirements for your organization.
Each line in /etc/audit/audit.rules represents a series of arguments
that can be passed to auditctl and can be individually tested
during runtime. See documentation in /usr/share/doc/audit-VERSION and
in the related man pages for more details.
If copying any example audit rulesets from /usr/share/doc/audit-VERSION ,
be sure to comment out the
lines containing arch= which are not appropriate for your system's
architecture. Then review and understand the following rules,
ensuring rules are activated as needed for the appropriate
architecture.
After reviewing all the rules, reading the following sections, and
editing as needed, the new rules can be activated as follows:
$ sudo service auditd restart |
contains 73 rules |
Record Information on Kernel Modules Loading and Unloading
[ref]groupTo capture kernel module loading and unloading events, use following lines, setting ARCH to
either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-w /usr/sbin/insmod -p x -k modules
-w /usr/sbin/rmmod -p x -k modules
-w /usr/sbin/modprobe -p x -k modules
-a always,exit -F arch=ARCH -S init_module,delete_module -F key=modules
Place to add the lines depends on a way auditd daemon is configured. If it is configured
to use the augenrules program (the default), add the lines to a file with suffix
.rules in the directory /etc/audit/rules.d .
If the auditd daemon is configured to use the auditctl utility,
add the lines to file /etc/audit/audit.rules . |
contains 5 rules |
Ensure auditd Collects Information on Kernel Module Unloading - rmmod
[ref]ruleTo capture invocation of rmmod, utility used to remove modules from kernel,
add the following line:
-w /usr/sbin/rmmod -p x -k modules
Place to add the line depends on a way auditd daemon is configured. If it is configured
to use the augenrules program (the default), add the line to a file with suffix
.rules in the directory /etc/audit/rules.d .
If the auditd daemon is configured to use the auditctl utility,
add the line to file /etc/audit/audit.rules .Rationale:The removal of kernel modules can be used to alter the behavior of
the kernel and potentially introduce malicious code into kernel space. It is important
to have an audit trail of modules that have been introduced into the kernel. References:
FAU_GEN.1.1.c, SV-86817r3_rule, 5.2.17, 3.1.7, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.7, SRG-OS-000471-GPOS-00216, SRG-OS-000477-GPOS-00222 Remediation Shell script: (show)
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix audit file system object watch rule for given path:
# * if rule exists, also verifies the -w bits match the requirements
# * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
# audit rules file, depending on the tool which was used to load audit rules
#
# Expects four arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules'
# * path value of -w audit rule's argument
# * required access bits value of -p audit rule's argument
# * key value of -k audit rule's argument
#
# Example call:
#
# fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
#
function fix_audit_watch_rule {
# Load function arguments into local variables
local tool="$1"
local path="$2"
local required_access_bits="$3"
local key="$4"
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
declare -a files_to_inspect
# Check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
exit 1
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
elif [ "$tool" == 'augenrules' ]
then
# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
# Get pair -- filepath : matching_row into @matches array
IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
# Reset IFS back to default
unset IFS
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
files_to_inspect="/etc/audit/rules.d/$key.rules"
# If the $key.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Escape slashes in path for use in sed pattern below
local esc_path=${path//$'/'/$'\/'}
# Define BRE whitespace class shortcut
local sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "$required_access_bits" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
fi
done
}
fix_audit_watch_rule "auditctl" "/usr/sbin/rmmod" "x" "modules"
fix_audit_watch_rule "augenrules" "/usr/sbin/rmmod" "x" "modules"
|
Ensure auditd Collects Information on Kernel Module Loading and Unloading - modprobe
[ref]ruleTo capture invocation of modprobe, utility used to insert / remove modules from kernel,
add the following line:
-w /usr/sbin/modprobe -p x -k modules
Place to add the line depends on a way auditd daemon is configured. If it is configured
to use the augenrules program (the default), add the line to a file with suffix
.rules in the directory /etc/audit/rules.d .
If the auditd daemon is configured to use the auditctl utility,
add the line to file /etc/audit/audit.rules .Rationale:The addition/removal of kernel modules can be used to alter the behavior of
the kernel and potentially introduce malicious code into kernel space. It is important
to have an audit trail of modules that have been introduced into the kernel. References:
FAU_GEN.1.1.c, SV-86819r3_rule, 5.2.17, 3.1.7, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.7, SRG-OS-000471-GPOS-00216, SRG-OS-000477-GPOS-00222 Remediation Shell script: (show)
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix audit file system object watch rule for given path:
# * if rule exists, also verifies the -w bits match the requirements
# * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
# audit rules file, depending on the tool which was used to load audit rules
#
# Expects four arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules'
# * path value of -w audit rule's argument
# * required access bits value of -p audit rule's argument
# * key value of -k audit rule's argument
#
# Example call:
#
# fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
#
function fix_audit_watch_rule {
# Load function arguments into local variables
local tool="$1"
local path="$2"
local required_access_bits="$3"
local key="$4"
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
declare -a files_to_inspect
# Check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
exit 1
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
elif [ "$tool" == 'augenrules' ]
then
# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
# Get pair -- filepath : matching_row into @matches array
IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
# Reset IFS back to default
unset IFS
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
files_to_inspect="/etc/audit/rules.d/$key.rules"
# If the $key.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Escape slashes in path for use in sed pattern below
local esc_path=${path//$'/'/$'\/'}
# Define BRE whitespace class shortcut
local sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "$required_access_bits" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
fi
done
}
fix_audit_watch_rule "auditctl" "/usr/sbin/modprobe" "x" "modules"
fix_audit_watch_rule "augenrules" "/usr/sbin/modprobe" "x" "modules"
|
Ensure auditd Collects Information on Kernel Module Unloading - delete_module
[ref]ruleTo capture kernel module unloading events, use following line, setting ARCH to
either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -S delete_module -F key=modules
Place to add the line depends on a way auditd daemon is configured. If it is configured
to use the augenrules program (the default), add the line to a file with suffix
.rules in the directory /etc/audit/rules.d .
If the auditd daemon is configured to use the auditctl utility,
add the line to file /etc/audit/audit.rules .Rationale:The removal of kernel modules can be used to alter the behavior of
the kernel and potentially introduce malicious code into kernel space. It is important
to have an audit trail of modules that have been introduced into the kernel. References:
FAU_GEN.1.1.c, SV-86813r3_rule, 5.2.17, 3.1.7, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.7, SRG-OS-000471-GPOS-00216, SRG-OS-000477-GPOS-00222 Remediation Shell script: (show)
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
# Note: 32-bit and 64-bit kernel syscall numbers not always line up =>
# it's required on a 64-bit system to check also for the presence
# of 32-bit's equivalent of the corresponding rule.
# (See `man 7 audit.rules` for details )
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S delete_module \(-F key=\|-k \).*"
GROUP="modules"
FULL_RULE="-a always,exit -F arch=$ARCH -S delete_module -k modules"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
|
Ensure auditd Collects Information on Kernel Module Loading - insmod
[ref]ruleTo capture invocation of insmod, utility used to insert modules into kernel,
use the following line:
-w /usr/sbin/insmod -p x -k modules
Place to add the line depends on a way auditd daemon is configured. If it is configured
to use the augenrules program (the default), add the line to a file with suffix
.rules in the directory /etc/audit/rules.d .
If the auditd daemon is configured to use the auditctl utility,
add the line to file /etc/audit/audit.rules .Rationale:The addition of kernel modules can be used to alter the behavior of
the kernel and potentially introduce malicious code into kernel space. It is important
to have an audit trail of modules that have been introduced into the kernel. References:
FAU_GEN.1.1.c, SV-86815r3_rule, 5.2.17, 3.1.7, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.7, SRG-OS-000471-GPOS-00216, SRG-OS-000477-GPOS-00222 Remediation Shell script: (show)
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix audit file system object watch rule for given path:
# * if rule exists, also verifies the -w bits match the requirements
# * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
# audit rules file, depending on the tool which was used to load audit rules
#
# Expects four arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules'
# * path value of -w audit rule's argument
# * required access bits value of -p audit rule's argument
# * key value of -k audit rule's argument
#
# Example call:
#
# fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
#
function fix_audit_watch_rule {
# Load function arguments into local variables
local tool="$1"
local path="$2"
local required_access_bits="$3"
local key="$4"
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
declare -a files_to_inspect
# Check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
exit 1
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
elif [ "$tool" == 'augenrules' ]
then
# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
# Get pair -- filepath : matching_row into @matches array
IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
# Reset IFS back to default
unset IFS
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
files_to_inspect="/etc/audit/rules.d/$key.rules"
# If the $key.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Escape slashes in path for use in sed pattern below
local esc_path=${path//$'/'/$'\/'}
# Define BRE whitespace class shortcut
local sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "$required_access_bits" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
fi
done
}
fix_audit_watch_rule "auditctl" "/usr/sbin/insmod" "x" "modules"
fix_audit_watch_rule "augenrules" "/usr/sbin/insmod" "x" "modules"
|
Ensure auditd Collects Information on Kernel Module Loading - init_module
[ref]ruleTo capture kernel module loading events, use following line, setting ARCH to
either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -S init_module -F key=modules
Place to add the line depends on a way auditd daemon is configured. If it is configured
to use the augenrules program (the default), add the line to a file with suffix
.rules in the directory /etc/audit/rules.d .
If the auditd daemon is configured to use the auditctl utility,
add the line to file /etc/audit/audit.rules .Rationale:The addition of kernel modules can be used to alter the behavior of
the kernel and potentially introduce malicious code into kernel space. It is important
to have an audit trail of modules that have been introduced into the kernel. References:
FAU_GEN.1.1.c, SV-86811r3_rule, 5.2.17, 3.1.7, CCI-000172, AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.7, SRG-OS-000471-GPOS-00216, SRG-OS-000477-GPOS-00222 Remediation Shell script: (show)
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
# Note: 32-bit and 64-bit kernel syscall numbers not always line up =>
# it's required on a 64-bit system to check also for the presence
# of 32-bit's equivalent of the corresponding rule.
# (See `man 7 audit.rules` for details )
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S init_module \(-F key=\|-k \).*"
GROUP="modules"
FULL_RULE="-a always,exit -F arch=$ARCH -S init_module -k modules"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
|
Record Attempts to Alter Logon and Logout Events
[ref]groupThe audit system already collects login information for all users
and root. If the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following lines to a file with suffix .rules in the
directory /etc/audit/rules.d in order to watch for attempted manual
edits of files involved in storing logon events:
-w /var/log/tallylog -p wa -k logins
-w /var/run/faillock/ -p wa -k logins
-w /var/log/lastlog -p wa -k logins
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file in order to watch for unattempted manual
edits of files involved in storing logon events:
-w /var/log/tallylog -p wa -k logins
-w /var/run/faillock/ -p wa -k logins
-w /var/log/lastlog -p wa -k logins |
contains 3 rules |
Record Attempts to Alter Logon and Logout Events - lastlog
[ref]ruleThe audit system already collects login information for all users
and root. If the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following lines to a file with suffix .rules in the
directory /etc/audit/rules.d in order to watch for attempted manual
edits of files involved in storing logon events:
-w /var/log/lastlog -p wa -k logins
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file in order to watch for unattempted manual
edits of files involved in storing logon events:
-w /var/log/lastlog -p wa -k logins Rationale:Manual editing of these files may indicate nefarious activity, such
as an attacker attempting to remove evidence of an intrusion. References:
FAU_GEN.1.1.c, SV-86771r2_rule, 5.2.8, 3.1.7, CCI-000172, CCI-002884, CCI-000126, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-12(a), AU-12(c), IR-5, Req-10.2.3, SRG-OS-000392-GPOS-00172, SRG-OS-000470-GPOS-00214, SRG-OS-000473-GPOS-00218 Remediation Shell script: (show)
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix audit file system object watch rule for given path:
# * if rule exists, also verifies the -w bits match the requirements
# * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
# audit rules file, depending on the tool which was used to load audit rules
#
# Expects four arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules'
# * path value of -w audit rule's argument
# * required access bits value of -p audit rule's argument
# * key value of -k audit rule's argument
#
# Example call:
#
# fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
#
function fix_audit_watch_rule {
# Load function arguments into local variables
local tool="$1"
local path="$2"
local required_access_bits="$3"
local key="$4"
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
declare -a files_to_inspect
# Check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
exit 1
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
elif [ "$tool" == 'augenrules' ]
then
# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
# Get pair -- filepath : matching_row into @matches array
IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
# Reset IFS back to default
unset IFS
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
files_to_inspect="/etc/audit/rules.d/$key.rules"
# If the $key.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Escape slashes in path for use in sed pattern below
local esc_path=${path//$'/'/$'\/'}
# Define BRE whitespace class shortcut
local sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "$required_access_bits" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
fi
done
}
fix_audit_watch_rule "auditctl" "/var/log/lastlog" "wa" "logins"
fix_audit_watch_rule "augenrules" "/var/log/lastlog" "wa" "logins"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
#
# What architecture are we on?
#
- name: Set architecture for audit lastlog tasks
set_fact:
audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"
#
# Inserts/replaces the rule in /etc/audit/rules.d
#
- name: Search /etc/audit/rules.d for other user/group modification audit rules
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "-k logins$"
patterns: "*.rules"
register: find_lastlog
- name: If existing user/group modification ruleset not found, use /etc/audit/rules.d/logins.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/logins.rules
when: find_lastlog.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_lastlog.files | map(attribute='path') | list | first }}"
when: find_lastlog.matched > 0
- name: Inserts/replaces the lastlog rule in rules.d when on x86
lineinfile:
path: "{{ all_files[0] }}"
line: "-w /var/log/lastlog -p wa -k logins"
create: yes
tags:
- audit_rules_login_events_lastlog
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80384-1
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.3
- DISA-STIG-RHEL-07-030620
#
# Inserts/replaces the rule in /etc/audit/audit.rules
#
- name: Inserts/replaces the lastlog rule in /etc/audit/audit.rules
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
with_items:
- "-w /var/log/lastlog -p wa -k logins"
tags:
- audit_rules_login_events_lastlog
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80384-1
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.3
- DISA-STIG-RHEL-07-030620
|
Record Attempts to Alter Logon and Logout Events - faillock
[ref]ruleThe audit system already collects login information for all users
and root. If the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following lines to a file with suffix .rules in the
directory /etc/audit/rules.d in order to watch for attempted manual
edits of files involved in storing logon events:
-w /var/run/faillock/ -p wa -k logins
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file in order to watch for unattempted manual
edits of files involved in storing logon events:
-w /var/run/faillock/ -p wa -k logins Rationale:Manual editing of these files may indicate nefarious activity, such
as an attacker attempting to remove evidence of an intrusion. References:
FAU_GEN.1.1.c, SV-86769r3_rule, 5.2.8, 3.1.7, CCI-000172, CCI-002884, CCI-000126, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-12(a), AU-12(c), IR-5, Req-10.2.3, SRG-OS-000392-GPOS-00172, SRG-OS-000470-GPOS-00214, SRG-OS-000473-GPOS-00218 Remediation Shell script: (show)
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix audit file system object watch rule for given path:
# * if rule exists, also verifies the -w bits match the requirements
# * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
# audit rules file, depending on the tool which was used to load audit rules
#
# Expects four arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules'
# * path value of -w audit rule's argument
# * required access bits value of -p audit rule's argument
# * key value of -k audit rule's argument
#
# Example call:
#
# fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
#
function fix_audit_watch_rule {
# Load function arguments into local variables
local tool="$1"
local path="$2"
local required_access_bits="$3"
local key="$4"
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
declare -a files_to_inspect
# Check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
exit 1
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
elif [ "$tool" == 'augenrules' ]
then
# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
# Get pair -- filepath : matching_row into @matches array
IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
# Reset IFS back to default
unset IFS
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
files_to_inspect="/etc/audit/rules.d/$key.rules"
# If the $key.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Escape slashes in path for use in sed pattern below
local esc_path=${path//$'/'/$'\/'}
# Define BRE whitespace class shortcut
local sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "$required_access_bits" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
fi
done
}
fix_audit_watch_rule "auditctl" "/var/run/faillock" "wa" "logins"
fix_audit_watch_rule "augenrules" "/var/run/faillock" "wa" "logins"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
#
# What architecture are we on?
#
- name: Set architecture for audit faillock tasks
set_fact:
audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"
#
# Inserts/replaces the rule in /etc/audit/rules.d
#
- name: Search /etc/audit/rules.d for other user/group modification audit rules
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "-k logins$"
patterns: "*.rules"
register: find_faillock
- name: If existing user/group modification ruleset not found, use /etc/audit/rules.d/logins.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/logins.rules
when: find_faillock.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_faillock.files | map(attribute='path') | list | first }}"
when: find_faillock.matched > 0
- name: Inserts/replaces the faillock rule in rules.d when on x86
lineinfile:
path: "{{ all_files[0] }}"
line: "-w /var/run/faillock -p wa -k logins"
create: yes
tags:
- audit_rules_login_events_faillock
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80383-3
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.3
- DISA-STIG-RHEL-07-030610
#
# Inserts/replaces the rule in /etc/audit/audit.rules
#
- name: Inserts/replaces the faillock rule in /etc/audit/audit.rules
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
with_items:
- "-w /var/run/faillock -p wa -k logins"
tags:
- audit_rules_login_events_faillock
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80383-3
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.3
- DISA-STIG-RHEL-07-030610
|
Record Attempts to Alter Logon and Logout Events - tallylog
[ref]ruleThe audit system already collects login information for all users
and root. If the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following lines to a file with suffix .rules in the
directory /etc/audit/rules.d in order to watch for attempted manual
edits of files involved in storing logon events:
-w /var/log/tallylog -p wa -k logins
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file in order to watch for unattempted manual
edits of files involved in storing logon events:
-w /var/log/tallylog -p wa -k logins Rationale:Manual editing of these files may indicate nefarious activity, such
as an attacker attempting to remove evidence of an intrusion. References:
FAU_GEN.1.1.c, SV-86767r2_rule, 5.2.8, 3.1.7, CCI-000172, CCI-002884, CCI-000126, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-12(a), AU-12(c), IR-5, Req-10.2.3, SRG-OS-000392-GPOS-00172, SRG-OS-000470-GPOS-00214, SRG-OS-000473-GPOS-00218 Remediation Shell script: (show)
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix audit file system object watch rule for given path:
# * if rule exists, also verifies the -w bits match the requirements
# * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
# audit rules file, depending on the tool which was used to load audit rules
#
# Expects four arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules'
# * path value of -w audit rule's argument
# * required access bits value of -p audit rule's argument
# * key value of -k audit rule's argument
#
# Example call:
#
# fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
#
function fix_audit_watch_rule {
# Load function arguments into local variables
local tool="$1"
local path="$2"
local required_access_bits="$3"
local key="$4"
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
declare -a files_to_inspect
# Check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
exit 1
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
elif [ "$tool" == 'augenrules' ]
then
# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
# Get pair -- filepath : matching_row into @matches array
IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
# Reset IFS back to default
unset IFS
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
files_to_inspect="/etc/audit/rules.d/$key.rules"
# If the $key.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Escape slashes in path for use in sed pattern below
local esc_path=${path//$'/'/$'\/'}
# Define BRE whitespace class shortcut
local sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "$required_access_bits" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
fi
done
}
fix_audit_watch_rule "auditctl" "/var/log/tallylog" "wa" "logins"
fix_audit_watch_rule "augenrules" "/var/log/tallylog" "wa" "logins"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
#
# What architecture are we on?
#
- name: Set architecture for audit tallylog tasks
set_fact:
audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"
#
# Inserts/replaces the rule in /etc/audit/rules.d
#
- name: Search /etc/audit/rules.d for other user/group modification audit rules
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "-k logins$"
patterns: "*.rules"
register: find_tallylog
- name: If existing user/group modification ruleset not found, use /etc/audit/rules.d/logins.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/logins.rules
when: find_tallylog.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_tallylog.files | map(attribute='path') | list | first }}"
when: find_tallylog.matched > 0
- name: Inserts/replaces the tallylog rule in rules.d when on x86
lineinfile:
path: "{{ all_files[0] }}"
line: "-w /var/log/tallylog -p wa -k logins"
create: yes
tags:
- audit_rules_login_events_tallylog
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80382-5
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.3
- DISA-STIG-RHEL-07-030600
#
# Inserts/replaces the rule in /etc/audit/audit.rules
#
- name: Inserts/replaces the tallylog rule in /etc/audit/audit.rules
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
with_items:
- "-w /var/log/tallylog -p wa -k logins"
tags:
- audit_rules_login_events_tallylog
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80382-5
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.3
- DISA-STIG-RHEL-07-030600
|
Records Events that Modify Date and Time Information
[ref]groupArbitrary changes to the system time can be used to obfuscate
nefarious activities in log files, as well as to confuse network services that
are highly dependent upon an accurate system time. All changes to the system
time should be audited. |
contains 5 rules |
Record Attempts to Alter the localtime File
[ref]ruleIf the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the default),
add the following line to a file with suffix .rules in the directory
/etc/audit/rules.d :
-w /etc/localtime -p wa -k audit_time_rules
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-w /etc/localtime -p wa -k audit_time_rules
The -k option allows for the specification of a key in string form that can
be used for better reporting capability through ausearch and aureport and
should always be used.Rationale:Arbitrary changes to the system time can be used to obfuscate
nefarious activities in log files, as well as to confuse network services that
are highly dependent upon an accurate system time (such as sshd). All changes
to the system time should be audited. References:
5.2.4, 5.4.1.1, 3.1.7, CCI-001487, CCI-000169, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(b), IR-5, Req-10.4.2.b Remediation Shell script: (show)
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix audit file system object watch rule for given path:
# * if rule exists, also verifies the -w bits match the requirements
# * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
# audit rules file, depending on the tool which was used to load audit rules
#
# Expects four arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules'
# * path value of -w audit rule's argument
# * required access bits value of -p audit rule's argument
# * key value of -k audit rule's argument
#
# Example call:
#
# fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
#
function fix_audit_watch_rule {
# Load function arguments into local variables
local tool="$1"
local path="$2"
local required_access_bits="$3"
local key="$4"
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
declare -a files_to_inspect
# Check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
exit 1
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
elif [ "$tool" == 'augenrules' ]
then
# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
# Get pair -- filepath : matching_row into @matches array
IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
# Reset IFS back to default
unset IFS
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
files_to_inspect="/etc/audit/rules.d/$key.rules"
# If the $key.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Escape slashes in path for use in sed pattern below
local esc_path=${path//$'/'/$'\/'}
# Define BRE whitespace class shortcut
local sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "$required_access_bits" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
fi
done
}
fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
fix_audit_watch_rule "augenrules" "/etc/localtime" "wa" "audit_time_rules"
|
Record Attempts to Alter Time Through stime
[ref]ruleIf the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following line to a file with suffix .rules in the
directory /etc/audit/rules.d for both 32 bit and 64 bit systems:
-a always,exit -F arch=b32 -S stime -F key=audit_time_rules
Since the 64 bit version of the "stime" system call is not defined in the audit
lookup table, the corresponding "-F arch=b64" form of this rule is not expected
to be defined on 64 bit systems (the aforementioned "-F arch=b32" stime rule
form itself is sufficient for both 32 bit and 64 bit systems). If the
auditd daemon is configured to use the auditctl utility to
read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file for both 32 bit and 64 bit systems:
-a always,exit -F arch=b32 -S stime -F key=audit_time_rules
Since the 64 bit version of the "stime" system call is not defined in the audit
lookup table, the corresponding "-F arch=b64" form of this rule is not expected
to be defined on 64 bit systems (the aforementioned "-F arch=b32" stime rule
form itself is sufficient for both 32 bit and 64 bit systems). The -k option
allows for the specification of a key in string form that can be used for
better reporting capability through ausearch and aureport. Multiple system
calls can be defined on the same line to save space if desired, but is not
required. See an example of multiple combined system calls:
-a always,exit -F arch=b64 -S adjtimex,settimeofday -F key=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. References:
5.4.1.1, 3.1.7, CCI-001487, CCI-000169, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.4.2.b Remediation Shell script: (show)
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
# Perform the remediation for the 'adjtimex', 'settimeofday', and 'stime' audit
# system calls on Red Hat Enterprise Linux 7 or Fedora OSes
function rhel7_fedora_perform_audit_adjtimex_settimeofday_stime_remediation {
# Retrieve hardware architecture of the underlying system
[ $(getconf LONG_BIT) = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=${ARCH} -S .* -k *"
# Create expected audit group and audit rule form for particular system call & architecture
if [ ${ARCH} = "b32" ]
then
# stime system call is known at 32-bit arch (see e.g "$ ausyscall i386 stime" 's output)
# so append it to the list of time group system calls to be audited
GROUP="\(adjtimex\|settimeofday\|stime\)"
FULL_RULE="-a always,exit -F arch=${ARCH} -S adjtimex -S settimeofday -S stime -k audit_time_rules"
elif [ ${ARCH} = "b64" ]
then
# stime system call isn't known at 64-bit arch (see "$ ausyscall x86_64 stime" 's output)
# therefore don't add it to the list of time group system calls to be audited
GROUP="\(adjtimex\|settimeofday\)"
FULL_RULE="-a always,exit -F arch=${ARCH} -S adjtimex -S settimeofday -k audit_time_rules"
fi
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
}
rhel7_fedora_perform_audit_adjtimex_settimeofday_stime_remediation
|
Record Attempts to Alter Time Through clock_settime
[ref]ruleIf the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following line to a file with suffix .rules in the
directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S clock_settime -F a0=0x0 -F key=time-change
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S clock_settime -F a0=0x0 -F key=time-change
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S clock_settime -F a0=0x0 -F key=time-change
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S clock_settime -F a0=0x0 -F key=time-change
The -k option allows for the specification of a key in string form that can
be used for better reporting capability through ausearch and aureport.
Multiple system calls can be defined on the same line to save space if
desired, but is not required. See an example of multiple combined syscalls:
-a always,exit -F arch=b64 -S adjtimex,settimeofday -F key=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. References:
5.2.4, 5.4.1.1, 3.1.7, CCI-001487, CCI-000169, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.4.2.b Remediation Shell script: (show)
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S clock_settime -F a0=.* \(-F key=\|-k \).*"
GROUP="clock_settime"
FULL_RULE="-a always,exit -F arch=$ARCH -S clock_settime -F a0=0x0 -k time-change"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
|
Record attempts to alter time through settimeofday
[ref]ruleIf the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following line to a file with suffix .rules in the
directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S settimeofday -F key=audit_time_rules
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S settimeofday -F key=audit_time_rules
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S settimeofday -F key=audit_time_rules
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S settimeofday -F key=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,settimeofday -F key=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. References:
5.2.4, 5.4.1.1, 3.1.7, CCI-001487, CCI-000169, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.4.2.b Remediation Shell script: (show)
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
# Perform the remediation for the 'adjtimex', 'settimeofday', and 'stime' audit
# system calls on Red Hat Enterprise Linux 7 or Fedora OSes
function rhel7_fedora_perform_audit_adjtimex_settimeofday_stime_remediation {
# Retrieve hardware architecture of the underlying system
[ $(getconf LONG_BIT) = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=${ARCH} -S .* -k *"
# Create expected audit group and audit rule form for particular system call & architecture
if [ ${ARCH} = "b32" ]
then
# stime system call is known at 32-bit arch (see e.g "$ ausyscall i386 stime" 's output)
# so append it to the list of time group system calls to be audited
GROUP="\(adjtimex\|settimeofday\|stime\)"
FULL_RULE="-a always,exit -F arch=${ARCH} -S adjtimex -S settimeofday -S stime -k audit_time_rules"
elif [ ${ARCH} = "b64" ]
then
# stime system call isn't known at 64-bit arch (see "$ ausyscall x86_64 stime" 's output)
# therefore don't add it to the list of time group system calls to be audited
GROUP="\(adjtimex\|settimeofday\)"
FULL_RULE="-a always,exit -F arch=${ARCH} -S adjtimex -S settimeofday -k audit_time_rules"
fi
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
}
rhel7_fedora_perform_audit_adjtimex_settimeofday_stime_remediation
|
Record attempts to alter time through adjtimex
[ref]ruleIf the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following line to a file with suffix .rules in the
directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S adjtimex -F key=audit_time_rules
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S adjtimex -F key=audit_time_rules
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S adjtimex -F key=audit_time_rules
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S adjtimex -F key=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,settimeofday -F key=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. References:
5.2.4, 5.4.1.1, 3.1.7, CCI-001487, CCI-000169, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.4.2.b Remediation Shell script: (show)
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
# Perform the remediation for the 'adjtimex', 'settimeofday', and 'stime' audit
# system calls on Red Hat Enterprise Linux 7 or Fedora OSes
function rhel7_fedora_perform_audit_adjtimex_settimeofday_stime_remediation {
# Retrieve hardware architecture of the underlying system
[ $(getconf LONG_BIT) = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=${ARCH} -S .* -k *"
# Create expected audit group and audit rule form for particular system call & architecture
if [ ${ARCH} = "b32" ]
then
# stime system call is known at 32-bit arch (see e.g "$ ausyscall i386 stime" 's output)
# so append it to the list of time group system calls to be audited
GROUP="\(adjtimex\|settimeofday\|stime\)"
FULL_RULE="-a always,exit -F arch=${ARCH} -S adjtimex -S settimeofday -S stime -k audit_time_rules"
elif [ ${ARCH} = "b64" ]
then
# stime system call isn't known at 64-bit arch (see "$ ausyscall x86_64 stime" 's output)
# therefore don't add it to the list of time group system calls to be audited
GROUP="\(adjtimex\|settimeofday\)"
FULL_RULE="-a always,exit -F arch=${ARCH} -S adjtimex -S settimeofday -k audit_time_rules"
fi
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
}
rhel7_fedora_perform_audit_adjtimex_settimeofday_stime_remediation
|
Record Events that Modify the System's Discretionary Access Controls
[ref]groupAt a minimum, the audit system should collect file permission
changes for all users and root. Note that the "-F arch=b32" lines should be
present even on a 64 bit system. These commands identify system calls for
auditing. Even if the system is 64 bit it can still execute 32 bit system
calls. Additionally, these rules can be configured in a number of ways while
still achieving the desired effect. An example of this is that the "-S" calls
could be split up and placed on separate lines, however, this is less efficient.
Add the following to /etc/audit/audit.rules :
-a always,exit -F arch=b32 -S chmod,fchmod,fchmodat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
-a always,exit -F arch=b32 -S chown,fchown,fchownat,lchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
-a always,exit -F arch=b32 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If your system is 64 bit then these lines should be duplicated and the
arch=b32 replaced with arch=b64 as follows:
-a always,exit -F arch=b64 -S chmod,fchmod,fchmodat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
-a always,exit -F arch=b64 -S chown,fchown,fchownat,lchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
-a always,exit -F arch=b64 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod |
contains 13 rules |
Record Events that Modify the System's Discretionary Access Controls - fchown
[ref]ruleAt a minimum, the audit system should collect file permission
changes for all users and root. If the auditd daemon is configured
to use the augenrules program to read audit rules during daemon
startup (the default), add the following line to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S fchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S fchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod Rationale:The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. References:
FAU_GEN.1.1.c, SV-86723r3_rule, 5.2.10, 5.4.1.1, 3.1.7, CCI-000126, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219 Remediation Shell script: (show)
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S fchown.*"
GROUP="perm_mod"
FULL_RULE="-a always,exit -F arch=$ARCH -S fchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
#
# What architecture are we on?
#
- name: Set architecture for audit fchown tasks
set_fact:
audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"
#
# Inserts/replaces the rule in /etc/audit/rules.d
#
- name: Search /etc/audit/rules.d for other DAC audit rules
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "-F key=perm_mod$"
patterns: "*.rules"
register: find_fchown
- name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_fchown.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_fchown.files | map(attribute='path') | list | first }}"
when: find_fchown.matched > 0
- name: Inserts/replaces the fchown rule in rules.d when on x86
lineinfile:
path: "{{ all_files[0] }}"
line: "-a always,exit -F arch=b32 -S fchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
create: yes
tags:
- audit_rules_dac_modification_fchown
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27356-5
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030380
- name: Inserts/replaces the fchown rule in rules.d when on x86_64
lineinfile:
path: "{{ all_files[0] }}"
line: "-a always,exit -F arch=b64 -S fchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
create: yes
when: audit_arch == 'b64'
tags:
- audit_rules_dac_modification_fchown
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27356-5
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030380
#
# Inserts/replaces the rule in /etc/audit/audit.rules
#
- name: Inserts/replaces the fchown rule in /etc/audit/audit.rules when on x86
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
with_items:
- "-a always,exit -F arch=b32 -S fchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
tags:
- audit_rules_dac_modification_fchown
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27356-5
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030380
- name: Inserts/replaces the fchown rule in audit.rules when on x86_64
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
create: yes
with_items:
- "-a always,exit -F arch=b64 -S fchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
when: audit_arch == 'b64'
tags:
- audit_rules_dac_modification_fchown
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27356-5
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030380
|
Record Events that Modify the System's Discretionary Access Controls - setxattr
[ref]ruleAt a minimum, the audit system should collect file permission
changes for all users and root. If the auditd daemon is configured
to use the augenrules program to read audit rules during daemon
startup (the default), add the following line to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S setxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S setxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S setxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S setxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod Rationale:The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. References:
FAU_GEN.1.1.c, SV-86735r3_rule, 5.2.10, 5.4.1.1, 3.1.7, CCI-000126, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203 Remediation Shell script: (show)
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S setxattr.*"
GROUP="perm_mod"
FULL_RULE="-a always,exit -F arch=$ARCH -S setxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
#
# What architecture are we on?
#
- name: Set architecture for audit setxattr tasks
set_fact:
audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"
#
# Inserts/replaces the rule in /etc/audit/rules.d
#
- name: Search /etc/audit/rules.d for other DAC audit rules
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "-F key=perm_mod$"
patterns: "*.rules"
register: find_setxattr
- name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_setxattr.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_setxattr.files | map(attribute='path') | list | first }}"
when: find_setxattr.matched > 0
- name: Inserts/replaces the setxattr rule in rules.d when on x86
lineinfile:
path: "{{ all_files[0] }}"
line: "-a always,exit -F arch=b32 -S setxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
create: yes
tags:
- audit_rules_dac_modification_setxattr
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27213-8
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030440
- name: Inserts/replaces the setxattr rule in rules.d when on x86_64
lineinfile:
path: "{{ all_files[0] }}"
line: "-a always,exit -F arch=b64 -S setxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
create: yes
when: audit_arch == 'b64'
tags:
- audit_rules_dac_modification_setxattr
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27213-8
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030440
#
# Inserts/replaces the rule in /etc/audit/audit.rules
#
- name: Inserts/replaces the setxattr rule in /etc/audit/audit.rules when on x86
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
with_items:
- "-a always,exit -F arch=b32 -S setxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
tags:
- audit_rules_dac_modification_setxattr
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27213-8
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030440
- name: Inserts/replaces the setxattr rule in audit.rules when on x86_64
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
create: yes
with_items:
- "-a always,exit -F arch=b64 -S setxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
when: audit_arch == 'b64'
tags:
- audit_rules_dac_modification_setxattr
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27213-8
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030440
|
Record Events that Modify the System's Discretionary Access Controls - fsetxattr
[ref]ruleAt a minimum, the audit system should collect file permission
changes for all users and root. If the auditd daemon is configured
to use the augenrules program to read audit rules during daemon
startup (the default), add the following line to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S fsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S fsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod Rationale:The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. References:
FAU_GEN.1.1.c, SV-86737r3_rule, 5.2.10, 5.4.1.1, 3.1.7, CCI-000126, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203 Remediation Shell script: (show)
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S fsetxattr.*"
GROUP="perm_mod"
FULL_RULE="-a always,exit -F arch=$ARCH -S fsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
#
# What architecture are we on?
#
- name: Set architecture for audit fsetxattr tasks
set_fact:
audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"
#
# Inserts/replaces the rule in /etc/audit/rules.d
#
- name: Search /etc/audit/rules.d for other DAC audit rules
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "-F key=perm_mod$"
patterns: "*.rules"
register: find_fsetxattr
- name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_fsetxattr.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_fsetxattr.files | map(attribute='path') | list | first }}"
when: find_fsetxattr.matched > 0
- name: Inserts/replaces the fsetxattr rule in rules.d when on x86
lineinfile:
path: "{{ all_files[0] }}"
line: "-a always,exit -F arch=b32 -S fsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
create: yes
tags:
- audit_rules_dac_modification_fsetxattr
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27389-6
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030450
- name: Inserts/replaces the fsetxattr rule in rules.d when on x86_64
lineinfile:
path: "{{ all_files[0] }}"
line: "-a always,exit -F arch=b64 -S fsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
create: yes
when: audit_arch == 'b64'
tags:
- audit_rules_dac_modification_fsetxattr
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27389-6
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030450
#
# Inserts/replaces the rule in /etc/audit/audit.rules
#
- name: Inserts/replaces the fsetxattr rule in /etc/audit/audit.rules when on x86
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
with_items:
- "-a always,exit -F arch=b32 -S fsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
tags:
- audit_rules_dac_modification_fsetxattr
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27389-6
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030450
- name: Inserts/replaces the fsetxattr rule in audit.rules when on x86_64
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
create: yes
with_items:
- "-a always,exit -F arch=b64 -S fsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
when: audit_arch == 'b64'
tags:
- audit_rules_dac_modification_fsetxattr
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27389-6
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030450
|
Record Events that Modify the System's Discretionary Access Controls - removexattr
[ref]ruleAt a minimum, the audit system should collect file permission
changes for all users and root.
If the auditd daemon is configured to use the augenrules
program to read audit rules during daemon startup (the default), add the
following line to a file with suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S removexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S removexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S removexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S removexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod Rationale:The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. References:
FAU_GEN.1.1.c, SV-86741r3_rule, 5.2.10, 5.4.1.1, 3.1.7, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203 Remediation Shell script: (show)
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S removexattr.*"
GROUP="perm_mod"
FULL_RULE="-a always,exit -F arch=$ARCH -S removexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
#
# What architecture are we on?
#
- name: Set architecture for audit removexattr tasks
set_fact:
audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"
#
# Inserts/replaces the rule in /etc/audit/rules.d
#
- name: Search /etc/audit/rules.d for other DAC audit rules
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "-F key=perm_mod$"
patterns: "*.rules"
register: find_removexattr
- name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_removexattr.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_removexattr.files | map(attribute='path') | list | first }}"
when: find_removexattr.matched > 0
- name: Inserts/replaces the removexattr rule in rules.d when on x86
lineinfile:
path: "{{ all_files[0] }}"
line: "-a always,exit -F arch=b32 -S removexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
create: yes
tags:
- audit_rules_dac_modification_removexattr
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27367-2
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030470
- name: Inserts/replaces the removexattr rule in rules.d when on x86_64
lineinfile:
path: "{{ all_files[0] }}"
line: "-a always,exit -F arch=b64 -S removexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
create: yes
when: audit_arch == 'b64'
tags:
- audit_rules_dac_modification_removexattr
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27367-2
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030470
#
# Inserts/replaces the rule in /etc/audit/audit.rules
#
- name: Inserts/replaces the removexattr rule in /etc/audit/audit.rules when on x86
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
with_items:
- "-a always,exit -F arch=b32 -S removexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
tags:
- audit_rules_dac_modification_removexattr
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27367-2
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030470
- name: Inserts/replaces the removexattr rule in audit.rules when on x86_64
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
create: yes
with_items:
- "-a always,exit -F arch=b64 -S removexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
when: audit_arch == 'b64'
tags:
- audit_rules_dac_modification_removexattr
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27367-2
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030470
|
Record Events that Modify the System's Discretionary Access Controls - lsetxattr
[ref]ruleAt a minimum, the audit system should collect file permission
changes for all users and root. If the auditd daemon is configured
to use the augenrules program to read audit rules during daemon
startup (the default), add the following line to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S lsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S lsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S lsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S lsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod Rationale:The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. References:
FAU_GEN.1.1.c, SV-86739r3_rule, 5.2.10, 5.4.1.1, 3.1.7, CCI-000126, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219 Remediation Shell script: (show)
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S lsetxattr.*"
GROUP="perm_mod"
FULL_RULE="-a always,exit -F arch=$ARCH -S lsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
#
# What architecture are we on?
#
- name: Set architecture for audit lsetxattr tasks
set_fact:
audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"
#
# Inserts/replaces the rule in /etc/audit/rules.d
#
- name: Search /etc/audit/rules.d for other DAC audit rules
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "-F key=perm_mod$"
patterns: "*.rules"
register: find_lsetxattr
- name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_lsetxattr.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_lsetxattr.files | map(attribute='path') | list | first }}"
when: find_lsetxattr.matched > 0
- name: Inserts/replaces the lsetxattr rule in rules.d when on x86
lineinfile:
path: "{{ all_files[0] }}"
line: "-a always,exit -F arch=b32 -S lsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
create: yes
tags:
- audit_rules_dac_modification_lsetxattr
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27280-7
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030460
- name: Inserts/replaces the lsetxattr rule in rules.d when on x86_64
lineinfile:
path: "{{ all_files[0] }}"
line: "-a always,exit -F arch=b64 -S lsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
create: yes
when: audit_arch == 'b64'
tags:
- audit_rules_dac_modification_lsetxattr
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27280-7
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030460
#
# Inserts/replaces the rule in /etc/audit/audit.rules
#
- name: Inserts/replaces the lsetxattr rule in /etc/audit/audit.rules when on x86
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
with_items:
- "-a always,exit -F arch=b32 -S lsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
tags:
- audit_rules_dac_modification_lsetxattr
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27280-7
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030460
- name: Inserts/replaces the lsetxattr rule in audit.rules when on x86_64
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
create: yes
with_items:
- "-a always,exit -F arch=b64 -S lsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
when: audit_arch == 'b64'
tags:
- audit_rules_dac_modification_lsetxattr
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27280-7
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030460
|
Record Events that Modify the System's Discretionary Access Controls - lchown
[ref]ruleAt a minimum, the audit system should collect file permission
changes for all users and root. If the auditd daemon is configured
to use the augenrules program to read audit rules during daemon
startup (the default), add the following line to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S lchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S lchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S lchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S lchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod Rationale:The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. References:
FAU_GEN.1.1.c, SV-86725r3_rule, 5.2.10, 5.4.1.1, 3.1.7, CCI-000126, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219 Remediation Shell script: (show)
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S lchown.*"
GROUP="perm_mod"
FULL_RULE="-a always,exit -F arch=$ARCH -S lchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
#
# What architecture are we on?
#
- name: Set architecture for audit lchown tasks
set_fact:
audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"
#
# Inserts/replaces the rule in /etc/audit/rules.d
#
- name: Search /etc/audit/rules.d for other DAC audit rules
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "-F key=perm_mod$"
patterns: "*.rules"
register: find_lchown
- name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_lchown.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_lchown.files | map(attribute='path') | list | first }}"
when: find_lchown.matched > 0
- name: Inserts/replaces the lchown rule in rules.d when on x86
lineinfile:
path: "{{ all_files[0] }}"
line: "-a always,exit -F arch=b32 -S lchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
create: yes
tags:
- audit_rules_dac_modification_lchown
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27083-5
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030390
- name: Inserts/replaces the lchown rule in rules.d when on x86_64
lineinfile:
path: "{{ all_files[0] }}"
line: "-a always,exit -F arch=b64 -S lchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
create: yes
when: audit_arch == 'b64'
tags:
- audit_rules_dac_modification_lchown
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27083-5
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030390
#
# Inserts/replaces the rule in /etc/audit/audit.rules
#
- name: Inserts/replaces the lchown rule in /etc/audit/audit.rules when on x86
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
with_items:
- "-a always,exit -F arch=b32 -S lchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
tags:
- audit_rules_dac_modification_lchown
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27083-5
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030390
- name: Inserts/replaces the lchown rule in audit.rules when on x86_64
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
create: yes
with_items:
- "-a always,exit -F arch=b64 -S lchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
when: audit_arch == 'b64'
tags:
- audit_rules_dac_modification_lchown
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27083-5
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030390
|
Record Events that Modify the System's Discretionary Access Controls - chmod
[ref]ruleAt a minimum, the audit system should collect file permission
changes for all users and root. If the auditd daemon is configured to
use the augenrules program to read audit rules during daemon startup
(the default), add the following line to a file with suffix .rules in
the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S chmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S chmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S chmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S chmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod Rationale:The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. References:
FAU_GEN.1.1.c, SV-86729r3_rule, 5.2.10, 5.4.1.1, 3.1.7, CCI-000126, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203 Remediation Shell script: (show)
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S chmod.*"
GROUP="perm_mod"
FULL_RULE="-a always,exit -F arch=$ARCH -S chmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
#
# What architecture are we on?
#
- name: Set architecture for audit chmod tasks
set_fact:
audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"
#
# Inserts/replaces the rule in /etc/audit/rules.d
#
- name: Search /etc/audit/rules.d for other DAC audit rules
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "-F key=perm_mod$"
patterns: "*.rules"
register: find_chmod
- name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_chmod.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_chmod.files | map(attribute='path') | list | first }}"
when: find_chmod.matched > 0
- name: Inserts/replaces the chmod rule in rules.d when on x86
lineinfile:
path: "{{ all_files[0] }}"
line: "-a always,exit -F arch=b32 -S chmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
create: yes
tags:
- audit_rules_dac_modification_chmod
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27339-1
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030410
- name: Inserts/replaces the chmod rule in rules.d when on x86_64
lineinfile:
path: "{{ all_files[0] }}"
line: "-a always,exit -F arch=b64 -S chmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
create: yes
when: audit_arch == 'b64'
tags:
- audit_rules_dac_modification_chmod
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27339-1
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030410
#
# Inserts/replaces the rule in /etc/audit/audit.rules
#
- name: Inserts/replaces the chmod rule in /etc/audit/audit.rules when on x86
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
with_items:
- "-a always,exit -F arch=b32 -S chmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
tags:
- audit_rules_dac_modification_chmod
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27339-1
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030410
- name: Inserts/replaces the chmod rule in audit.rules when on x86_64
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
create: yes
with_items:
- "-a always,exit -F arch=b64 -S chmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
when: audit_arch == 'b64'
tags:
- audit_rules_dac_modification_chmod
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27339-1
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030410
|
Record Events that Modify the System's Discretionary Access Controls - chown
[ref]ruleAt a minimum, the audit system should collect file permission
changes for all users and root. If the auditd daemon is configured to
use the augenrules program to read audit rules during daemon startup
(the default), add the following line to a file with suffix .rules in
the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S chown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S chown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S chown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S chown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod Rationale:The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. References:
FAU_GEN.1.1.c, SV-86721r3_rule, 5.2.10, 5.4.1.1, 3.1.7, CCI-000126, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219 Remediation Shell script: (show)
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S chown.*"
GROUP="perm_mod"
FULL_RULE="-a always,exit -F arch=$ARCH -S chown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
#
# What architecture are we on?
#
- name: Set architecture for audit chown tasks
set_fact:
audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"
#
# Inserts/replaces the rule in /etc/audit/rules.d
#
- name: Search /etc/audit/rules.d for other DAC audit rules
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "-F key=perm_mod$"
patterns: "*.rules"
register: find_chown
- name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_chown.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_chown.files | map(attribute='path') | list | first }}"
when: find_chown.matched > 0
- name: Inserts/replaces the chown rule in rules.d when on x86
lineinfile:
path: "{{ all_files[0] }}"
line: "-a always,exit -F arch=b32 -S chown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
create: yes
tags:
- audit_rules_dac_modification_chown
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27364-9
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030370
- name: Inserts/replaces the chown rule in rules.d when on x86_64
lineinfile:
path: "{{ all_files[0] }}"
line: "-a always,exit -F arch=b64 -S chown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
create: yes
when: audit_arch == 'b64'
tags:
- audit_rules_dac_modification_chown
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27364-9
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030370
#
# Inserts/replaces the rule in /etc/audit/audit.rules
#
- name: Inserts/replaces the chown rule in /etc/audit/audit.rules when on x86
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
with_items:
- "-a always,exit -F arch=b32 -S chown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
tags:
- audit_rules_dac_modification_chown
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27364-9
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030370
- name: Inserts/replaces the chown rule in audit.rules when on x86_64
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
create: yes
with_items:
- "-a always,exit -F arch=b64 -S chown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
when: audit_arch == 'b64'
tags:
- audit_rules_dac_modification_chown
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27364-9
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030370
|
Record Events that Modify the System's Discretionary Access Controls - fremovexattr
[ref]ruleAt a minimum, the audit system should collect file permission
changes for all users and root.
If the auditd daemon is configured
to use the augenrules program to read audit rules during daemon
startup (the default), add the following line to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S fremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S fremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod Rationale:The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. References:
FAU_GEN.1.1.c, SV-86743r3_rule, 5.2.10, 5.4.1.1, 3.1.7, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203 Remediation Shell script: (show)
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S fremovexattr.*"
GROUP="perm_mod"
FULL_RULE="-a always,exit -F arch=$ARCH -S fremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
#
# What architecture are we on?
#
- name: Set architecture for audit fremovexattr tasks
set_fact:
audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"
#
# Inserts/replaces the rule in /etc/audit/rules.d
#
- name: Search /etc/audit/rules.d for other DAC audit rules
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "-F key=perm_mod$"
patterns: "*.rules"
register: find_fremovexattr
- name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_fremovexattr.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_fremovexattr.files | map(attribute='path') | list | first }}"
when: find_fremovexattr.matched > 0
- name: Inserts/replaces the fremovexattr rule in rules.d when on x86
lineinfile:
path: "{{ all_files[0] }}"
line: "-a always,exit -F arch=b32 -S fremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
create: yes
tags:
- audit_rules_dac_modification_fremovexattr
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27353-2
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030480
- name: Inserts/replaces the fremovexattr rule in rules.d when on x86_64
lineinfile:
path: "{{ all_files[0] }}"
line: "-a always,exit -F arch=b64 -S fremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
create: yes
when: audit_arch == 'b64'
tags:
- audit_rules_dac_modification_fremovexattr
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27353-2
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030480
#
# Inserts/replaces the rule in /etc/audit/audit.rules
#
- name: Inserts/replaces the fremovexattr rule in /etc/audit/audit.rules when on x86
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
with_items:
- "-a always,exit -F arch=b32 -S fremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
tags:
- audit_rules_dac_modification_fremovexattr
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27353-2
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030480
- name: Inserts/replaces the fremovexattr rule in audit.rules when on x86_64
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
create: yes
with_items:
- "-a always,exit -F arch=b64 -S fremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
when: audit_arch == 'b64'
tags:
- audit_rules_dac_modification_fremovexattr
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27353-2
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030480
|
Record Events that Modify the System's Discretionary Access Controls - fchownat
[ref]ruleAt a minimum, the audit system should collect file permission
changes for all users and root. If the auditd daemon is configured
to use the augenrules program to read audit rules during daemon
startup (the default), add the following line to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S fchownat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fchownat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S fchownat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fchownat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod Rationale:The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. References:
FAU_GEN.1.1.c, SV-86727r3_rule, 5.2.10, 5.4.1.1, 3.1.7, CCI-000126, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219 Remediation Shell script: (show)
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S fchownat.*"
GROUP="perm_mod"
FULL_RULE="-a always,exit -F arch=$ARCH -S fchownat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
#
# What architecture are we on?
#
- name: Set architecture for audit fchownat tasks
set_fact:
audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"
#
# Inserts/replaces the rule in /etc/audit/rules.d
#
- name: Search /etc/audit/rules.d for other DAC audit rules
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "-F key=perm_mod$"
patterns: "*.rules"
register: find_fchownat
- name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_fchownat.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_fchownat.files | map(attribute='path') | list | first }}"
when: find_fchownat.matched > 0
- name: Inserts/replaces the fchownat rule in rules.d when on x86
lineinfile:
path: "{{ all_files[0] }}"
line: "-a always,exit -F arch=b32 -S fchownat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
create: yes
tags:
- audit_rules_dac_modification_fchownat
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27387-0
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030400
- name: Inserts/replaces the fchownat rule in rules.d when on x86_64
lineinfile:
path: "{{ all_files[0] }}"
line: "-a always,exit -F arch=b64 -S fchownat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
create: yes
when: audit_arch == 'b64'
tags:
- audit_rules_dac_modification_fchownat
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27387-0
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030400
#
# Inserts/replaces the rule in /etc/audit/audit.rules
#
- name: Inserts/replaces the fchownat rule in /etc/audit/audit.rules when on x86
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
with_items:
- "-a always,exit -F arch=b32 -S fchownat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
tags:
- audit_rules_dac_modification_fchownat
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27387-0
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030400
- name: Inserts/replaces the fchownat rule in audit.rules when on x86_64
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
create: yes
with_items:
- "-a always,exit -F arch=b64 -S fchownat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
when: audit_arch == 'b64'
tags:
- audit_rules_dac_modification_fchownat
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27387-0
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030400
|
Record Events that Modify the System's Discretionary Access Controls - fchmod
[ref]ruleAt a minimum, the audit system should collect file permission
changes for all users and root. If the auditd daemon is configured to
use the augenrules program to read audit rules during daemon startup
(the default), add the following line to a file with suffix .rules in
the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S fchmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fchmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S fchmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fchmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod Rationale:The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. References:
FAU_GEN.1.1.c, SV-86731r3_rule, 5.2.10, 5.4.1.1, 3.1.7, CCI-000126, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203 Remediation Shell script: (show)
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S fchmod.*"
GROUP="perm_mod"
FULL_RULE="-a always,exit -F arch=$ARCH -S fchmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
#
# What architecture are we on?
#
- name: Set architecture for audit fchmod tasks
set_fact:
audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"
#
# Inserts/replaces the rule in /etc/audit/rules.d
#
- name: Search /etc/audit/rules.d for other DAC audit rules
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "-F key=perm_mod$"
patterns: "*.rules"
register: find_fchmod
- name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_fchmod.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_fchmod.files | map(attribute='path') | list | first }}"
when: find_fchmod.matched > 0
- name: Inserts/replaces the fchmod rule in rules.d when on x86
lineinfile:
path: "{{ all_files[0] }}"
line: "-a always,exit -F arch=b32 -S fchmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
create: yes
tags:
- audit_rules_dac_modification_fchmod
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27393-8
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030420
- name: Inserts/replaces the fchmod rule in rules.d when on x86_64
lineinfile:
path: "{{ all_files[0] }}"
line: "-a always,exit -F arch=b64 -S fchmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
create: yes
when: audit_arch == 'b64'
tags:
- audit_rules_dac_modification_fchmod
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27393-8
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030420
#
# Inserts/replaces the rule in /etc/audit/audit.rules
#
- name: Inserts/replaces the fchmod rule in /etc/audit/audit.rules when on x86
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
with_items:
- "-a always,exit -F arch=b32 -S fchmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
tags:
- audit_rules_dac_modification_fchmod
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27393-8
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030420
- name: Inserts/replaces the fchmod rule in audit.rules when on x86_64
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
create: yes
with_items:
- "-a always,exit -F arch=b64 -S fchmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
when: audit_arch == 'b64'
tags:
- audit_rules_dac_modification_fchmod
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27393-8
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030420
|
Record Events that Modify the System's Discretionary Access Controls - lremovexattr
[ref]ruleAt a minimum, the audit system should collect file permission
changes for all users and root.
If the auditd daemon is configured
to use the augenrules program to read audit rules during daemon
startup (the default), add the following line to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S lremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S lremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S lremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S lremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod Rationale:The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. References:
FAU_GEN.1.1.c, SV-86745r3_rule, 5.2.10, 5.4.1.1, 3.1.7, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203 Remediation Shell script: (show)
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S lremovexattr.*"
GROUP="perm_mod"
FULL_RULE="-a always,exit -F arch=$ARCH -S lremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
#
# What architecture are we on?
#
- name: Set architecture for audit lremovexattr tasks
set_fact:
audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"
#
# Inserts/replaces the rule in /etc/audit/rules.d
#
- name: Search /etc/audit/rules.d for other DAC audit rules
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "-F key=perm_mod$"
patterns: "*.rules"
register: find_lremovexattr
- name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_lremovexattr.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_lremovexattr.files | map(attribute='path') | list | first }}"
when: find_lremovexattr.matched > 0
- name: Inserts/replaces the lremovexattr rule in rules.d when on x86
lineinfile:
path: "{{ all_files[0] }}"
line: "-a always,exit -F arch=b32 -S lremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
create: yes
tags:
- audit_rules_dac_modification_lremovexattr
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27410-0
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030490
- name: Inserts/replaces the lremovexattr rule in rules.d when on x86_64
lineinfile:
path: "{{ all_files[0] }}"
line: "-a always,exit -F arch=b64 -S lremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
create: yes
when: audit_arch == 'b64'
tags:
- audit_rules_dac_modification_lremovexattr
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27410-0
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030490
#
# Inserts/replaces the rule in /etc/audit/audit.rules
#
- name: Inserts/replaces the lremovexattr rule in /etc/audit/audit.rules when on x86
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
with_items:
- "-a always,exit -F arch=b32 -S lremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
tags:
- audit_rules_dac_modification_lremovexattr
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27410-0
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030490
- name: Inserts/replaces the lremovexattr rule in audit.rules when on x86_64
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
create: yes
with_items:
- "-a always,exit -F arch=b64 -S lremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
when: audit_arch == 'b64'
tags:
- audit_rules_dac_modification_lremovexattr
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27410-0
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030490
|
Record Events that Modify the System's Discretionary Access Controls - fchmodat
[ref]ruleAt a minimum, the audit system should collect file permission
changes for all users and root. If the auditd daemon is configured to
use the augenrules program to read audit rules during daemon startup
(the default), add the following line to a file with suffix .rules in
the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S fchmodat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fchmodat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S fchmodat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
If the system is 64 bit then also add the following line:
-a always,exit -F arch=b64 -S fchmodat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod Rationale:The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. References:
FAU_GEN.1.1.c, SV-86733r3_rule, 5.2.10, 5.4.1.1, 3.1.7, CCI-000126, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203 Remediation Shell script: (show)
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S fchmodat.*"
GROUP="perm_mod"
FULL_RULE="-a always,exit -F arch=$ARCH -S fchmodat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
#
# What architecture are we on?
#
- name: Set architecture for audit fchmodat tasks
set_fact:
audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"
#
# Inserts/replaces the rule in /etc/audit/rules.d
#
- name: Search /etc/audit/rules.d for other DAC audit rules
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "-F key=perm_mod$"
patterns: "*.rules"
register: find_fchmodat
- name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_fchmodat.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_fchmodat.files | map(attribute='path') | list | first }}"
when: find_fchmodat.matched > 0
- name: Inserts/replaces the fchmodat rule in rules.d when on x86
lineinfile:
path: "{{ all_files[0] }}"
line: "-a always,exit -F arch=b32 -S fchmodat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
create: yes
tags:
- audit_rules_dac_modification_fchmodat
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27388-8
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030430
- name: Inserts/replaces the fchmodat rule in rules.d when on x86_64
lineinfile:
path: "{{ all_files[0] }}"
line: "-a always,exit -F arch=b64 -S fchmodat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
create: yes
when: audit_arch == 'b64'
tags:
- audit_rules_dac_modification_fchmodat
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27388-8
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030430
#
# Inserts/replaces the rule in /etc/audit/audit.rules
#
- name: Inserts/replaces the fchmodat rule in /etc/audit/audit.rules when on x86
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
with_items:
- "-a always,exit -F arch=b32 -S fchmodat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
tags:
- audit_rules_dac_modification_fchmodat
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27388-8
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030430
- name: Inserts/replaces the fchmodat rule in audit.rules when on x86_64
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
create: yes
with_items:
- "-a always,exit -F arch=b64 -S fchmodat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
when: audit_arch == 'b64'
tags:
- audit_rules_dac_modification_fchmodat
- unknown_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27388-8
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.5.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030430
|
Record Execution Attempts to Run SELinux Privileged Commands
[ref]groupAt a minimum, the audit system should collect the execution of
SELinux privileged commands for all users and root. |
contains 4 rules |
Record Any Attempts to Run restorecon
[ref]ruleAt a minimum, the audit system should collect any execution attempt
of the restorecon command for all users and root. If the auditd
daemon is configured to use the augenrules program to read audit rules
during daemon startup (the default), add the following lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/sbin/restorecon -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged-priv_change
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F path=/usr/sbin/restorecon -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged-priv_change Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threast.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
FAU_GEN.1.1.c, 3.1.7, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AU-12(c), SRG-OS-000392-GPOS-00172, SRG-OS-000463-GPOS-00207, SRG-OS-000465-GPOS-00209 Remediation Shell script: (show)
PATTERN="-a always,exit -F path=/usr/sbin/restorecon\\s*.*"
GROUP="privileged"
FULL_RULE="-a always,exit -F path=/usr/sbin/restorecon -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
# Inserts/replaces the rule in /etc/audit/rules.d
- name: Search /etc/audit/rules.d for audit rule entries
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "^.*path=/usr/sbin/restorecon.*$"
patterns: "*.rules"
register: find_restorecon
- name: Use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_restorecon.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_restorecon.files | map(attribute='path') | list | first }}"
when: find_restorecon.matched > 0
- name: Inserts/replaces the restorecon rule in rules.d
lineinfile:
path: "{{ all_files[0] }}"
line: '-a always,exit -F path=/usr/sbin/restorecon -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_execution_restorecon
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80394-0
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
# Inserts/replaces the restorecon rule in /etc/audit/audit.rules
- name: Inserts/replaces the restorecon rule in audit.rules
lineinfile:
path: /etc/audit/audit.rules
line: '-a always,exit -F path=/usr/sbin/restorecon -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_execution_restorecon
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80394-0
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
|
Record Any Attempts to Run setsebool
[ref]ruleAt a minimum, the audit system should collect any execution attempt
of the setsebool command for all users and root. If the auditd
daemon is configured to use the augenrules program to read audit rules
during daemon startup (the default), add the following lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/sbin/setsebool -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged-priv_change
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F path=/usr/sbin/setsebool -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged-priv_change Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threast.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
FAU_GEN.1.1.c, SV-86761r3_rule, 3.1.7, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AU-12(c), SRG-OS-000392-GPOS-00172, SRG-OS-000463-GPOS-00207, SRG-OS-000465-GPOS-00209 Remediation Shell script: (show)
PATTERN="-a always,exit -F path=/usr/sbin/setsebool\\s*.*"
GROUP="privileged"
FULL_RULE="-a always,exit -F path=/usr/sbin/setsebool -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
# Inserts/replaces the rule in /etc/audit/rules.d
- name: Search /etc/audit/rules.d for audit rule entries
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "^.*path=/usr/sbin/setsebool.*$"
patterns: "*.rules"
register: find_setsebool
- name: Use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_setsebool.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_setsebool.files | map(attribute='path') | list | first }}"
when: find_setsebool.matched > 0
- name: Inserts/replaces the setsebool rule in rules.d
lineinfile:
path: "{{ all_files[0] }}"
line: '-a always,exit -F path=/usr/sbin/setsebool -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_execution_setsebool
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80392-4
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030570
# Inserts/replaces the setsebool rule in /etc/audit/audit.rules
- name: Inserts/replaces the setsebool rule in audit.rules
lineinfile:
path: /etc/audit/audit.rules
line: '-a always,exit -F path=/usr/sbin/setsebool -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_execution_setsebool
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80392-4
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030570
|
Record Any Attempts to Run semanage
[ref]ruleAt a minimum, the audit system should collect any execution attempt
of the semanage command for all users and root. If the auditd
daemon is configured to use the augenrules program to read audit rules
during daemon startup (the default), add the following lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/sbin/semanage -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged-priv_change
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F path=/usr/sbin/semanage -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged-priv_change Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threast.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
FAU_GEN.1.1.c, SV-86759r3_rule, 3.1.7, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AU-12(c), SRG-OS-000392-GPOS-00172, SRG-OS-000463-GPOS-00207, SRG-OS-000465-GPOS-00209 Remediation Shell script: (show)
PATTERN="-a always,exit -F path=/usr/sbin/semanage\\s*.*"
GROUP="privileged"
FULL_RULE="-a always,exit -F path=/usr/sbin/semanage -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
# Inserts/replaces the rule in /etc/audit/rules.d
- name: Search /etc/audit/rules.d for audit rule entries
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "^.*path=/usr/sbin/semanage.*$"
patterns: "*.rules"
register: find_semanage
- name: Use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_semanage.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_semanage.files | map(attribute='path') | list | first }}"
when: find_semanage.matched > 0
- name: Inserts/replaces the semanage rule in rules.d
lineinfile:
path: "{{ all_files[0] }}"
line: '-a always,exit -F path=/usr/sbin/semanage -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_execution_semanage
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80391-6
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030560
# Inserts/replaces the semanage rule in /etc/audit/audit.rules
- name: Inserts/replaces the semanage rule in audit.rules
lineinfile:
path: /etc/audit/audit.rules
line: '-a always,exit -F path=/usr/sbin/semanage -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_execution_semanage
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80391-6
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030560
|
Record Any Attempts to Run chcon
[ref]ruleAt a minimum, the audit system should collect any execution attempt
of the chcon command for all users and root. If the auditd
daemon is configured to use the augenrules program to read audit rules
during daemon startup (the default), add the following lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged-priv_change
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged-priv_change Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threast.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
FAU_GEN.1.1.c, SV-86763r3_rule, 3.1.7, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AU-12(c), SRG-OS-000392-GPOS-00172, SRG-OS-000463-GPOS-00207, SRG-OS-000465-GPOS-00209 Remediation Shell script: (show)
PATTERN="-a always,exit -F path=/usr/bin/chcon\\s*.*"
GROUP="privileged"
FULL_RULE="-a always,exit -F path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
# Inserts/replaces the rule in /etc/audit/rules.d
- name: Search /etc/audit/rules.d for audit rule entries
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "^.*path=/usr/bin/chcon.*$"
patterns: "*.rules"
register: find_chcon
- name: Use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_chcon.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_chcon.files | map(attribute='path') | list | first }}"
when: find_chcon.matched > 0
- name: Inserts/replaces the chcon rule in rules.d
lineinfile:
path: "{{ all_files[0] }}"
line: '-a always,exit -F path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_execution_chcon
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80393-2
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030580
# Inserts/replaces the chcon rule in /etc/audit/audit.rules
- name: Inserts/replaces the chcon rule in audit.rules
lineinfile:
path: /etc/audit/audit.rules
line: '-a always,exit -F path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_execution_chcon
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80393-2
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030580
|
Record File Deletion Events by User
[ref]groupAt a minimum, the audit system should collect file deletion events
for all users and root. If the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following line to a file with suffix .rules in the
directory /etc/audit/rules.d , setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S rmdir,unlink,unlinkat,rename,renameat -F auid>=1000 -F auid!=4294967295 -F key=delete
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file, setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S rmdir,unlink,unlinkat,rename,renameat -F auid>=1000 -F auid!=4294967295 -F key=delete |
contains 6 rules |
Ensure auditd Collects File Deletion Events by User - rmdir
[ref]ruleAt a minimum, the audit system should collect file deletion events
for all users and root. If the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following line to a file with suffix .rules in the
directory /etc/audit/rules.d , setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S rmdir -F auid>=1000 -F auid!=4294967295 -F key=delete
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file, setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S rmdir -F auid>=1000 -F auid!=4294967295 -F key=delete Rationale:Auditing file deletions will create an audit trail for files that are removed
from the system. The audit trail could aid in system troubleshooting, as well as, detecting
malicious processes that attempt to delete log files to conceal their presence. References:
FAU_GEN.1.1.c, SV-86827r3_rule, 5.2.14, 3.1.7, CCI-000366, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, MA-4(1)(a), Req-10.2.7, SRG-OS-000466-GPOS-00210, SRG-OS-000467-GPOS-00210, SRG-OS-000468-GPOS-00212, SRG-OS-000392-GPOS-00172 Remediation Shell script: (show)
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S rmdir.*"
GROUP="delete"
FULL_RULE="-a always,exit -F arch=$ARCH -S rmdir -F auid>=1000 -F auid!=4294967295 -F key=delete"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
|
Ensure auditd Collects File Deletion Events by User - unlinkat
[ref]ruleAt a minimum, the audit system should collect file deletion events
for all users and root. If the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following line to a file with suffix .rules in the
directory /etc/audit/rules.d , setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S unlinkat -F auid>=1000 -F auid!=4294967295 -F key=delete
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file, setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S unlinkat -F auid>=1000 -F auid!=4294967295 -F key=delete Rationale:Auditing file deletions will create an audit trail for files that are removed
from the system. The audit trail could aid in system troubleshooting, as well as, detecting
malicious processes that attempt to delete log files to conceal their presence. References:
FAU_GEN.1.1.c, SV-86831r3_rule, 5.2.14, 3.1.7, CCI-000366, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, MA-4(1)(a), Req-10.2.7, SRG-OS-000466-GPOS-00210, SRG-OS-000467-GPOS-00210, SRG-OS-000468-GPOS-00212, SRG-OS-000392-GPOS-00172 Remediation Shell script: (show)
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S unlinkat.*"
GROUP="delete"
FULL_RULE="-a always,exit -F arch=$ARCH -S unlinkat -F auid>=1000 -F auid!=4294967295 -F key=delete"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
|
Ensure auditd Collects File Deletion Events by User
[ref]ruleAt a minimum the audit system should collect file deletion events
for all users and root. If the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following line to a file with suffix .rules in the
directory /etc/audit/rules.d , setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S rmdiri,unlink,unlinkat,rename,renameat -F auid>=1000 -F auid!=4294967295 -F key=delete
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file, setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S rmdir,unlink,unlinkat,rename -S renameat -F auid>=1000 -F auid!=4294967295 -F key=delete Rationale:Auditing file deletions will create an audit trail for files that are removed
from the system. The audit trail could aid in system troubleshooting, as well as, detecting
malicious processes that attempt to delete log files to conceal their presence. References:
FAU_GEN.1.1.c, 5.2.14, 5.4.1.1, 3.1.7, CCI-000366, CCI-000172, CCI-002884, AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.7 Remediation Shell script: (show)
# Perform the remediation for the syscall rule
# Retrieve hardware architecture of the underlying system
[ $(getconf LONG_BIT) = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S .* -F auid>=1000 -F auid!=4294967295 -k *"
# Use escaped BRE regex to specify rule group
GROUP="\(rmdir\|unlink\|rename\)"
FULL_RULE="-a always,exit -F arch=$ARCH -S rmdir -S unlink -S unlinkat -S rename -S renameat -F auid>=1000 -F auid!=4294967295 -k delete"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
|
Ensure auditd Collects File Deletion Events by User - rename
[ref]ruleAt a minimum, the audit system should collect file deletion events
for all users and root. If the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following line to a file with suffix .rules in the
directory /etc/audit/rules.d , setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S rename -F auid>=1000 -F auid!=4294967295 -F key=delete
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file, setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S rename -F auid>=1000 -F auid!=4294967295 -F key=delete Rationale:Auditing file deletions will create an audit trail for files that are removed
from the system. The audit trail could aid in system troubleshooting, as well as, detecting
malicious processes that attempt to delete log files to conceal their presence. References:
FAU_GEN.1.1.c, SV-86823r3_rule, 5.2.14, 3.1.7, CCI-000366, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, MA-4(1)(a), Req-10.2.7, SRG-OS-000466-GPOS-00210, SRG-OS-000467-GPOS-00210, SRG-OS-000468-GPOS-00212, SRG-OS-000392-GPOS-00172 Remediation Shell script: (show)
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S rename.*"
GROUP="delete"
FULL_RULE="-a always,exit -F arch=$ARCH -S rename -F auid>=1000 -F auid!=4294967295 -F key=delete"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
|
Ensure auditd Collects File Deletion Events by User - renameat
[ref]ruleAt a minimum, the audit system should collect file deletion events
for all users and root. If the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following line to a file with suffix .rules in the
directory /etc/audit/rules.d , setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S renameat -F auid>=1000 -F auid!=4294967295 -F key=delete
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file, setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S renameat -F auid>=1000 -F auid!=4294967295 -F key=delete Rationale:Auditing file deletions will create an audit trail for files that are removed
from the system. The audit trail could aid in system troubleshooting, as well as, detecting
malicious processes that attempt to delete log files to conceal their presence. References:
FAU_GEN.1.1.c, SV-86825r3_rule, 5.2.14, 3.1.7, CCI-000366, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, MA-4(1)(a), Req-10.2.7, SRG-OS-000466-GPOS-00210, SRG-OS-000467-GPOS-00210, SRG-OS-000468-GPOS-00212, SRG-OS-000392-GPOS-00172 Remediation Shell script: (show)
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S renameat.*"
GROUP="delete"
FULL_RULE="-a always,exit -F arch=$ARCH -S renameat -F auid>=1000 -F auid!=4294967295 -F key=delete"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
|
Ensure auditd Collects File Deletion Events by User - unlink
[ref]ruleAt a minimum, the audit system should collect file deletion events
for all users and root. If the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following line to a file with suffix .rules in the
directory /etc/audit/rules.d , setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S unlink -F auid>=1000 -F auid!=4294967295 -F key=delete
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file, setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S unlink -F auid>=1000 -F auid!=4294967295 -F key=delete Rationale:Auditing file deletions will create an audit trail for files that are removed
from the system. The audit trail could aid in system troubleshooting, as well as, detecting
malicious processes that attempt to delete log files to conceal their presence. References:
FAU_GEN.1.1.c, SV-86829r3_rule, 5.2.14, 3.1.7, CCI-000366, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, MA-4(1)(a), Req-10.2.7, SRG-OS-000466-GPOS-00210, SRG-OS-000467-GPOS-00210, SRG-OS-000468-GPOS-00212, SRG-OS-000392-GPOS-00172 Remediation Shell script: (show)
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S unlink.*"
GROUP="delete"
FULL_RULE="-a always,exit -F arch=$ARCH -S unlink -F auid>=1000 -F auid!=4294967295 -F key=delete"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
|
Record Information on the Use of Privileged Commands
[ref]groupAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. |
contains 17 rules |
Ensure auditd Collects Information on the Use of Privileged Commands - gpasswd
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/gpasswd -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/gpasswd -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threast.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
FAU_GEN.1.1.c, SV-86777r3_rule, 3.1.7, CCI-000135, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AU-3(1), AU-12(c), SRG-OS-000042-GPOS-00020, SRG-OS-000392-GPOS-00172, SRG-OS-000471-GPOS-00215 Remediation Shell script: (show)
PATTERN="-a always,exit -F path=/usr/bin/gpasswd\\s*.*"
GROUP="privileged"
FULL_RULE="-a always,exit -F path=/usr/bin/gpasswd -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
# Inserts/replaces the rule in /etc/audit/rules.d
- name: Search /etc/audit/rules.d for audit rule entries
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "^.*path=/usr/bin/gpasswd.*$"
patterns: "*.rules"
register: find_gpasswd
- name: Use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_gpasswd.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_gpasswd.files | map(attribute='path') | list | first }}"
when: find_gpasswd.matched > 0
- name: Inserts/replaces the gpasswd rule in rules.d
lineinfile:
path: "{{ all_files[0] }}"
line: '-a always,exit -F path=/usr/bin/gpasswd -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_gpasswd
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80397-3
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030650
# Inserts/replaces the gpasswd rule in /etc/audit/audit.rules
- name: Inserts/replaces the gpasswd rule in audit.rules
lineinfile:
path: /etc/audit/audit.rules
line: '-a always,exit -F path=/usr/bin/gpasswd -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_gpasswd
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80397-3
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030650
|
Ensure auditd Collects Information on the Use of Privileged Commands - sudo
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/sudo -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/sudo -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threast.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
FAU_GEN.1.1.c, SV-86785r3_rule, 3.1.7, CCI-000135, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AU-3(1), AU-12(c), SRG-OS-000042-GPOS-00020, SRG-OS-000392-GPOS-00172, SRG-OS-000471-GPOS-00215 Remediation Shell script: (show)
PATTERN="-a always,exit -F path=/usr/bin/sudo\\s*.*"
GROUP="privileged"
FULL_RULE="-a always,exit -F path=/usr/bin/sudo -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
# Inserts/replaces the rule in /etc/audit/rules.d
- name: Search /etc/audit/rules.d for audit rule entries
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "^.*path=/usr/bin/sudo.*$"
patterns: "*.rules"
register: find_sudo
- name: Use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_sudo.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_sudo.files | map(attribute='path') | list | first }}"
when: find_sudo.matched > 0
- name: Inserts/replaces the sudo rule in rules.d
lineinfile:
path: "{{ all_files[0] }}"
line: '-a always,exit -F path=/usr/bin/sudo -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_sudo
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80401-3
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030690
# Inserts/replaces the sudo rule in /etc/audit/audit.rules
- name: Inserts/replaces the sudo rule in audit.rules
lineinfile:
path: /etc/audit/audit.rules
line: '-a always,exit -F path=/usr/bin/sudo -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_sudo
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80401-3
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030690
|
Ensure auditd Collects Information on the Use of Privileged Commands - passwd
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/passwd -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/passwd -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threast.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
FAU_GEN.1.1.c, SV-86773r3_rule, 3.1.7, CCI-000135, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AU-3(1), AU-12(c), SRG-OS-000042-GPOS-00020, SRG-OS-000392-GPOS-00172, SRG-OS-000471-GPOS-00215 Remediation Shell script: (show)
PATTERN="-a always,exit -F path=/usr/bin/passwd\\s*.*"
GROUP="privileged"
FULL_RULE="-a always,exit -F path=/usr/bin/passwd -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
# Inserts/replaces the rule in /etc/audit/rules.d
- name: Search /etc/audit/rules.d for audit rule entries
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "^.*path=/usr/bin/passwd.*$"
patterns: "*.rules"
register: find_passwd
- name: Use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_passwd.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_passwd.files | map(attribute='path') | list | first }}"
when: find_passwd.matched > 0
- name: Inserts/replaces the passwd rule in rules.d
lineinfile:
path: "{{ all_files[0] }}"
line: '-a always,exit -F path=/usr/bin/passwd -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_passwd
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80395-7
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030630
# Inserts/replaces the passwd rule in /etc/audit/audit.rules
- name: Inserts/replaces the passwd rule in audit.rules
lineinfile:
path: /etc/audit/audit.rules
line: '-a always,exit -F path=/usr/bin/passwd -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_passwd
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80395-7
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030630
|
Ensure auditd Collects Information on the Use of Privileged Commands - unix_chkpwd
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/unix_chkpwd -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/unix_chkpwd -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threast.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
FAU_GEN.1.1.c, SV-86775r4_rule, 3.1.7, CCI-000135, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AU-3(1), AU-12(c), SRG-OS-000042-GPOS-00020, SRG-OS-000392-GPOS-00172, SRG-OS-000471-GPOS-00215 Remediation Shell script: (show)
PATTERN="-a always,exit -F path=/usr/sbin/unix_chkpwd\\s*.*"
GROUP="privileged"
FULL_RULE="-a always,exit -F path=/usr/sbin/unix_chkpwd -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
# Inserts/replaces the rule in /etc/audit/rules.d
- name: Search /etc/audit/rules.d for audit rule entries
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "^.*path=/usr/sbin/unix_chkpwd.*$"
patterns: "*.rules"
register: find_unix_chkpwd
- name: Use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_unix_chkpwd.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_unix_chkpwd.files | map(attribute='path') | list | first }}"
when: find_unix_chkpwd.matched > 0
- name: Inserts/replaces the unix_chkpwd rule in rules.d
lineinfile:
path: "{{ all_files[0] }}"
line: '-a always,exit -F path=/usr/sbin/unix_chkpwd -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_unix_chkpwd
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80396-5
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030640
# Inserts/replaces the unix_chkpwd rule in /etc/audit/audit.rules
- name: Inserts/replaces the unix_chkpwd rule in audit.rules
lineinfile:
path: /etc/audit/audit.rules
line: '-a always,exit -F path=/usr/sbin/unix_chkpwd -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_unix_chkpwd
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80396-5
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030640
|
Ensure auditd Collects Information on the Use of Privileged Commands - postqueue
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/sbin/postqueue -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/sbin/postqueue -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threast.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
SV-86801r2_rule, 3.1.7, CCI-000135, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AU-3(1), AU-12(c), SRG-OS-000042-GPOS-00020, SRG-OS-000392-GPOS-00172, SRG-OS-000471-GPOS-00215 Remediation Shell script: (show)
PATTERN="-a always,exit -F path=/usr/sbin/postqueue\\s*.*"
GROUP="privileged"
FULL_RULE="-a always,exit -F path=/usr/sbin/postqueue -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
# Inserts/replaces the rule in /etc/audit/rules.d
- name: Search /etc/audit/rules.d for audit rule entries
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "^.*path=/usr/sbin/postqueue.*$"
patterns: "*.rules"
register: find_postqueue
- name: Use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_postqueue.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_postqueue.files | map(attribute='path') | list | first }}"
when: find_postqueue.matched > 0
- name: Inserts/replaces the postqueue rule in rules.d
lineinfile:
path: "{{ all_files[0] }}"
line: '-a always,exit -F path=/usr/sbin/postqueue -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_postqueue
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80407-0
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030770
# Inserts/replaces the postqueue rule in /etc/audit/audit.rules
- name: Inserts/replaces the postqueue rule in audit.rules
lineinfile:
path: /etc/audit/audit.rules
line: '-a always,exit -F path=/usr/sbin/postqueue -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_postqueue
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80407-0
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030770
|
Ensure auditd Collects Information on the Use of Privileged Commands - chage
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/chage -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/chage -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threast.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
SV-86779r3_rule, 3.1.7, CCI-000135, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AU-3(1), AU-12(c), SRG-OS-000042-GPOS-00020, SRG-OS-000392-GPOS-00172, SRG-OS-000471-GPOS-00215 Remediation Shell script: (show)
PATTERN="-a always,exit -F path=/usr/bin/chage\\s*.*"
GROUP="privileged"
FULL_RULE="-a always,exit -F path=/usr/bin/chage -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
# Inserts/replaces the rule in /etc/audit/rules.d
- name: Search /etc/audit/rules.d for audit rule entries
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "^.*path=/usr/bin/chage.*$"
patterns: "*.rules"
register: find_chage
- name: Use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_chage.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_chage.files | map(attribute='path') | list | first }}"
when: find_chage.matched > 0
- name: Inserts/replaces the chage rule in rules.d
lineinfile:
path: "{{ all_files[0] }}"
line: '-a always,exit -F path=/usr/bin/chage -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_chage
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80398-1
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030660
# Inserts/replaces the chage rule in /etc/audit/audit.rules
- name: Inserts/replaces the chage rule in audit.rules
lineinfile:
path: /etc/audit/audit.rules
line: '-a always,exit -F path=/usr/bin/chage -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_chage
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80398-1
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030660
|
Ensure auditd Collects Information on the Use of Privileged Commands
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. To find the relevant setuid /
setgid programs, run the following command for each local partition
PART:
$ sudo find PART -xdev -type f -perm -4000 -o -type f -perm -2000 2>/dev/null
If the auditd daemon is configured to use the augenrules
program to read audit rules during daemon startup (the default), add a line of
the following form to a file with suffix .rules in the directory
/etc/audit/rules.d for each setuid / setgid program on the system,
replacing the SETUID_PROG_PATH part with the full path of that setuid /
setgid program in the list:
-a always,exit -F path=SETUID_PROG_PATH -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules for each setuid / setgid program on the
system, replacing the SETUID_PROG_PATH part with the full path of that
setuid / setgid program in the list:
-a always,exit -F path=SETUID_PROG_PATH -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threast.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
SV-86719r5_rule, 5.2.10, 5.4.1.1, 3.1.7, CCI-002234, AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-2(4), AU-6(9), AU-12(a), AU-12(c), IR-5, Req-10.2.2, SRG-OS-000327-GPOS-00127 Remediation Shell script: (show)
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to perform remediation for 'audit_rules_privileged_commands' rule
#
# Expects two arguments:
#
# audit_tool tool used to load audit rules
# One of 'auditctl' or 'augenrules'
#
# min_auid Minimum original ID the user logged in with
# '500' for RHEL-6 and before, '1000' for RHEL-7 and after.
#
# Example Call(s):
#
# perform_audit_rules_privileged_commands_remediation "auditctl" "500"
# perform_audit_rules_privileged_commands_remediation "augenrules" "1000"
#
function perform_audit_rules_privileged_commands_remediation {
#
# Load function arguments into local variables
local tool="$1"
local min_auid="$2"
# Check sanity of the input
if [ $# -ne "2" ]
then
echo "Usage: perform_audit_rules_privileged_commands_remediation 'auditctl | augenrules' '500 | 1000'"
echo "Aborting."
exit 1
fi
declare -a files_to_inspect=()
# Check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
exit 1
# If the audit tool is 'auditctl', then:
# * add '/etc/audit/audit.rules'to the list of files to be inspected,
# * specify '/etc/audit/audit.rules' as the output audit file, where
# missing rules should be inserted
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("/etc/audit/audit.rules")
output_audit_file="/etc/audit/audit.rules"
#
# If the audit tool is 'augenrules', then:
# * add '/etc/audit/rules.d/*.rules' to the list of files to be inspected
# (split by newline),
# * specify /etc/audit/rules.d/privileged.rules' as the output file, where
# missing rules should be inserted
elif [ "$tool" == 'augenrules' ]
then
IFS=$'\n' files_to_inspect=($(find /etc/audit/rules.d -maxdepth 1 -type f -name '*.rules' -print))
output_audit_file="/etc/audit/rules.d/privileged.rules"
fi
# Obtain the list of SUID/SGID binaries on the particular system (split by newline)
# into privileged_binaries array
IFS=$'\n' privileged_binaries=($(find / -xdev -type f -perm -4000 -o -type f -perm -2000 2>/dev/null))
# Keep list of SUID/SGID binaries that have been already handled within some previous iteration
declare -a sbinaries_to_skip=()
# For each found sbinary in privileged_binaries list
for sbinary in "${privileged_binaries[@]}"
do
# Check if this sbinary wasn't already handled in some of the previous iterations
# Return match only if whole sbinary definition matched (not in the case just prefix matched!!!)
if [[ $(sed -ne "\|${sbinary}|p" <<< "${sbinaries_to_skip[*]}") ]]
then
# If so, don't process it second time & go to process next sbinary
continue
fi
# Reset the counter of inspected files when starting to check
# presence of existing audit rule for new sbinary
local count_of_inspected_files=0
# Define expected rule form for this binary
expected_rule="-a always,exit -F path=${sbinary} -F perm=x -F auid>=${min_auid} -F auid!=4294967295 -k privileged"
# If list of audit rules files to be inspected is empty, just add new rule and move on to next binary
if [[ ${#files_to_inspect[@]} -eq 0 ]]; then
echo "$expected_rule" >> "$output_audit_file"
continue
fi
# Replace possible slash '/' character in sbinary definition so we could use it in sed expressions below
sbinary_esc=${sbinary//$'/'/$'\/'}
# For each audit rules file from the list of files to be inspected
for afile in "${files_to_inspect[@]}"
do
# Search current audit rules file's content for match. Match criteria:
# * existing rule is for the same SUID/SGID binary we are currently processing (but
# can contain multiple -F path= elements covering multiple SUID/SGID binaries)
# * existing rule contains all arguments from expected rule form (though can contain
# them in arbitrary order)
base_search=$(sed -e '/-a always,exit/!d' -e '/-F path='"${sbinary_esc}"'/!d' \
-e '/-F path=[^[:space:]]\+/!d' -e '/-F perm=.*/!d' \
-e '/-F auid>='"${min_auid}"'/!d' -e '/-F auid!=4294967295/!d' \
-e '/-k privileged/!d' "$afile")
# Increase the count of inspected files for this sbinary
count_of_inspected_files=$((count_of_inspected_files + 1))
# Require execute access type to be set for existing audit rule
exec_access='x'
# Search current audit rules file's content for presence of rule pattern for this sbinary
if [[ $base_search ]]
then
# Current audit rules file already contains rule for this binary =>
# Store the exact form of found rule for this binary for further processing
concrete_rule=$base_search
# Select all other SUID/SGID binaries possibly also present in the found rule
IFS=$'\n' handled_sbinaries=($(grep -o -e "-F path=[^[:space:]]\+" <<< "$concrete_rule"))
IFS=$' ' handled_sbinaries=(${handled_sbinaries[@]//-F path=/})
# Merge the list of such SUID/SGID binaries found in this iteration with global list ignoring duplicates
sbinaries_to_skip=($(for i in "${sbinaries_to_skip[@]}" "${handled_sbinaries[@]}"; do echo "$i"; done | sort -du))
# Separate concrete_rule into three sections using hash '#'
# sign as a delimiter around rule's permission section borders
concrete_rule="$(echo "$concrete_rule" | sed -n "s/\(.*\)\+\(-F perm=[rwax]\+\)\+/\1#\2#/p")"
# Split concrete_rule into head, perm, and tail sections using hash '#' delimiter
IFS=$'#' read -r rule_head rule_perm rule_tail <<< "$concrete_rule"
# Extract already present exact access type [r|w|x|a] from rule's permission section
access_type=${rule_perm//-F perm=/}
# Verify current permission access type(s) for rule contain 'x' (execute) permission
if ! grep -q "$exec_access" <<< "$access_type"
then
# If not, append the 'x' (execute) permission to the existing access type bits
access_type="$access_type$exec_access"
# Reconstruct the permissions section for the rule
new_rule_perm="-F perm=$access_type"
# Update existing rule in current audit rules file with the new permission section
sed -i "s#${rule_head}\(.*\)${rule_tail}#${rule_head}${new_rule_perm}${rule_tail}#" "$afile"
fi
# If the required audit rule for particular sbinary wasn't found yet, insert it under following conditions:
#
# * in the "auditctl" mode of operation insert particular rule each time
# (because in this mode there's only one file -- /etc/audit/audit.rules to be inspected for presence of this rule),
#
# * in the "augenrules" mode of operation insert particular rule only once and only in case we have already
# searched all of the files from /etc/audit/rules.d/*.rules location (since that audit rule can be defined
# in any of those files and if not, we want it to be inserted only once into /etc/audit/rules.d/privileged.rules file)
#
elif [ "$tool" == "auditctl" ] || [[ "$tool" == "augenrules" && $count_of_inspected_files -eq "${#files_to_inspect[@]}" ]]
then
# Current audit rules file's content doesn't contain expected rule for this
# SUID/SGID binary yet => append it
echo "$expected_rule" >> "$output_audit_file"
continue
fi
done
done
}
perform_audit_rules_privileged_commands_remediation "auditctl" "1000"
perform_audit_rules_privileged_commands_remediation "augenrules" "1000"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
- name: Search for privileged commands
shell: "find / -xdev -type f -perm -4000 -o -type f -perm -2000 2>/dev/null | cat"
check_mode: no
register: find_result
tags:
- audit_rules_privileged_commands
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27437-3
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-2(4)
- NIST-800-53-AU-6(9)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.2
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030360
# Inserts/replaces the rule in /etc/audit/rules.d
- name: Search /etc/audit/rules.d for audit rule entries
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "^.*path={{ item }} .*$"
patterns: "*.rules"
with_items:
- "{{ find_result.stdout_lines }}"
register: files_result
tags:
- audit_rules_privileged_commands
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27437-3
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-2(4)
- NIST-800-53-AU-6(9)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.2
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030360
- name: Overwrites the rule in rules.d
lineinfile:
path: "{{ item.1.path }}"
line: '-a always,exit -F path={{ item.0.item }} -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: no
regexp: "^.*path={{ item.0.item }} .*$"
with_subelements:
- "{{ files_result.results }}"
- files
tags:
- audit_rules_privileged_commands
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27437-3
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-2(4)
- NIST-800-53-AU-6(9)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.2
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030360
- name: Adds the rule in rules.d
lineinfile:
path: /etc/audit/rules.d/privileged.rules
line: '-a always,exit -F path={{ item.item }} -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
with_items:
- "{{ files_result.results }}"
when: item.matched == 0
tags:
- audit_rules_privileged_commands
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27437-3
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-2(4)
- NIST-800-53-AU-6(9)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.2
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030360
# Adds/overwrites the rule in /etc/audit/audit.rules
- name: Inserts/replaces the rule in audit.rules
lineinfile:
path: /etc/audit/audit.rules
line: '-a always,exit -F path={{ item.item }} -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
regexp: "^.*path={{ item.item }} .*$"
with_items:
- "{{ files_result.results }}"
tags:
- audit_rules_privileged_commands
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27437-3
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-2(4)
- NIST-800-53-AU-6(9)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.2
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030360
|
Ensure auditd Collects Information on the Use of Privileged Commands - userhelper
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/userhelper -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/userhelper -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threast.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
FAU_GEN.1.1.c, SV-86781r3_rule, 3.1.7, CCI-000135, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AU-3(1), AU-12(c), SRG-OS-000042-GPOS-00020, SRG-OS-000392-GPOS-00172, SRG-OS-000471-GPOS-00215 Remediation Shell script: (show)
PATTERN="-a always,exit -F path=/usr/sbin/userhelper\\s*.*"
GROUP="privileged"
FULL_RULE="-a always,exit -F path=/usr/sbin/userhelper -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
# Inserts/replaces the rule in /etc/audit/rules.d
- name: Search /etc/audit/rules.d for audit rule entries
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "^.*path=/usr/sbin/userhelper.*$"
patterns: "*.rules"
register: find_userhelper
- name: Use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_userhelper.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_userhelper.files | map(attribute='path') | list | first }}"
when: find_userhelper.matched > 0
- name: Inserts/replaces the userhelper rule in rules.d
lineinfile:
path: "{{ all_files[0] }}"
line: '-a always,exit -F path=/usr/sbin/userhelper -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_userhelper
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80399-9
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030670
# Inserts/replaces the userhelper rule in /etc/audit/audit.rules
- name: Inserts/replaces the userhelper rule in audit.rules
lineinfile:
path: /etc/audit/audit.rules
line: '-a always,exit -F path=/usr/sbin/userhelper -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_userhelper
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80399-9
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030670
|
Ensure auditd Collects Information on the Use of Privileged Commands - ssh-keysign
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/libexec/openssh/ssh-keysign -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/libexec/openssh/key-sign -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threast.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
FAU_GEN.1.1.c, SV-86803r2_rule, 3.1.7, CCI-000135, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AU-3(1), AU-12(c), SRG-OS-000042-GPOS-00020, SRG-OS-000392-GPOS-00172, SRG-OS-000471-GPOS-00215 Remediation Shell script: (show)
PATTERN="-a always,exit -F path=/usr/libexec/openssh/ssh-keysign\\s*.*"
GROUP="privileged"
FULL_RULE="-a always,exit -F path=/usr/libexec/openssh/ssh-keysign -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
# Inserts/replaces the rule in /etc/audit/rules.d
- name: Search /etc/audit/rules.d for audit rule entries
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "^.*path=/usr/libexec/openssh/ssh-keysign.*$"
patterns: "*.rules"
register: find_ssh_keysign
- name: Use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_ssh_keysign.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_ssh_keysign.files | map(attribute='path') | list | first }}"
when: find_ssh_keysign.matched > 0
- name: Inserts/replaces the ssh_keysign rule in rules.d
lineinfile:
path: "{{ all_files[0] }}"
line: '-a always,exit -F path=/usr/libexec/openssh/ssh-keysign -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_ssh_keysign
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80408-8
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030780
# Inserts/replaces the ssh_keysign rule in /etc/audit/audit.rules
- name: Inserts/replaces the ssh_keysign rule in audit.rules
lineinfile:
path: /etc/audit/audit.rules
line: '-a always,exit -F path=/usr/libexec/openssh/ssh-keysign -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_ssh_keysign
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80408-8
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030780
|
Ensure auditd Collects Information on the Use of Privileged Commands - sudoedit
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/sudoedit -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/sudoedit -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threast.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
FAU_GEN.1.1.c, SV-86793r4_rule, 3.1.7, CCI-000135, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AU-3(1), AU-12(c), SRG-OS-000042-GPOS-00020, SRG-OS-000392-GPOS-00172, SRG-OS-000471-GPOS-00215 Remediation Shell script: (show)
PATTERN="-a always,exit -F path=/usr/bin/sudoedit\\s*.*"
GROUP="privileged"
FULL_RULE="-a always,exit -F path=/usr/bin/sudoedit -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
# Inserts/replaces the rule in /etc/audit/rules.d
- name: Search /etc/audit/rules.d for audit rule entries
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "^.*path=/usr/bin/sudoedit.*$"
patterns: "*.rules"
register: find_sudoedit
- name: Use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_sudoedit.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_sudoedit.files | map(attribute='path') | list | first }}"
when: find_sudoedit.matched > 0
- name: Inserts/replaces the sudoedit rule in rules.d
lineinfile:
path: "{{ all_files[0] }}"
line: '-a always,exit -F path=/usr/bin/sudoedit -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_sudoedit
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80402-1
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030730
# Inserts/replaces the sudoedit rule in /etc/audit/audit.rules
- name: Inserts/replaces the sudoedit rule in audit.rules
lineinfile:
path: /etc/audit/audit.rules
line: '-a always,exit -F path=/usr/bin/sudoedit -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_sudoedit
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80402-1
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030730
|
Ensure auditd Collects Information on the Use of Privileged Commands - pam_timestamp_check
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/sbin/pam_timestamp_check -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/sbin/pam_timestamp_check -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threast.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
SV-86809r3_rule, 3.1.7, CCI-000135, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AU-3(1), AU-12(c), SRG-OS-000042-GPOS-00020, SRG-OS-000392-GPOS-00172, SRG-OS-000471-GPOS-00215 Remediation Shell script: (show)
PATTERN="-a always,exit -F path=/usr/sbin/pam_timestamp_check\\s*.*"
GROUP="privileged"
FULL_RULE="-a always,exit -F path=/usr/sbin/pam_timestamp_check -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
# Inserts/replaces the rule in /etc/audit/rules.d
- name: Search /etc/audit/rules.d for audit rule entries
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "^.*path=/usr/sbin/pam_timestamp_check.*$"
patterns: "*.rules"
register: find_pam_timestamp_check
- name: Use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_pam_timestamp_check.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_pam_timestamp_check.files | map(attribute='path') | list | first }}"
when: find_pam_timestamp_check.matched > 0
- name: Inserts/replaces the pam_timestamp_check rule in rules.d
lineinfile:
path: "{{ all_files[0] }}"
line: '-a always,exit -F path=/usr/sbin/pam_timestamp_check -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_pam_timestamp_check
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80411-2
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030810
# Inserts/replaces the pam_timestamp_check rule in /etc/audit/audit.rules
- name: Inserts/replaces the pam_timestamp_check rule in audit.rules
lineinfile:
path: /etc/audit/audit.rules
line: '-a always,exit -F path=/usr/sbin/pam_timestamp_check -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_pam_timestamp_check
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80411-2
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030810
|
Ensure auditd Collects Information on the Use of Privileged Commands - su
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/su -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/su -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threast.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
FAU_GEN.1.1.c, SV-86783r4_rule, 3.1.7, CCI-000135, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AU-3(1), AU-12(c), SRG-OS-000042-GPOS-00020, SRG-OS-000392-GPOS-00172, SRG-OS-000471-GPOS-00215 Remediation Shell script: (show)
PATTERN="-a always,exit -F path=/usr/bin/su\\s*.*"
GROUP="privileged"
FULL_RULE="-a always,exit -F path=/usr/bin/su -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
# Inserts/replaces the rule in /etc/audit/rules.d
- name: Search /etc/audit/rules.d for audit rule entries
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "^.*path=/usr/bin/su.*$"
patterns: "*.rules"
register: find_su
- name: Use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_su.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_su.files | map(attribute='path') | list | first }}"
when: find_su.matched > 0
- name: Inserts/replaces the su rule in rules.d
lineinfile:
path: "{{ all_files[0] }}"
line: '-a always,exit -F path=/usr/bin/su -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_su
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80400-5
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030680
# Inserts/replaces the su rule in /etc/audit/audit.rules
- name: Inserts/replaces the su rule in audit.rules
lineinfile:
path: /etc/audit/audit.rules
line: '-a always,exit -F path=/usr/bin/su -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_su
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80400-5
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030680
|
Ensure auditd Collects Information on the Use of Privileged Commands - newgrp
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/newgrp -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/newgrp -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threast.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
FAU_GEN.1.1.c, SV-86789r3_rule, 3.1.7, CCI-000135, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AU-3(1), AU-12(c), SRG-OS-000042-GPOS-00020, SRG-OS-000392-GPOS-00172, SRG-OS-000471-GPOS-00215 Remediation Shell script: (show)
PATTERN="-a always,exit -F path=/usr/bin/newgrp\\s*.*"
GROUP="privileged"
FULL_RULE="-a always,exit -F path=/usr/bin/newgrp -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
# Inserts/replaces the rule in /etc/audit/rules.d
- name: Search /etc/audit/rules.d for audit rule entries
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "^.*path=/usr/bin/newgrp.*$"
patterns: "*.rules"
register: find_newgrp
- name: Use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_newgrp.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_newgrp.files | map(attribute='path') | list | first }}"
when: find_newgrp.matched > 0
- name: Inserts/replaces the newgrp rule in rules.d
lineinfile:
path: "{{ all_files[0] }}"
line: '-a always,exit -F path=/usr/bin/newgrp -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_newgrp
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80403-9
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030710
# Inserts/replaces the newgrp rule in /etc/audit/audit.rules
- name: Inserts/replaces the newgrp rule in audit.rules
lineinfile:
path: /etc/audit/audit.rules
line: '-a always,exit -F path=/usr/bin/newgrp -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_newgrp
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80403-9
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030710
|
Ensure auditd Collects Information on the Use of Privileged Commands - crontab
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/crontab -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/crontab -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threast.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
SV-86807r2_rule, 3.1.7, CCI-000135, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AU-3(1), AU-12(c), SRG-OS-000042-GPOS-00020, SRG-OS-000392-GPOS-00172, SRG-OS-000471-GPOS-00215 Remediation Shell script: (show)
PATTERN="-a always,exit -F path=/usr/bin/crontab\\s*.*"
GROUP="privileged"
FULL_RULE="-a always,exit -F path=/usr/bin/crontab -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
# Inserts/replaces the rule in /etc/audit/rules.d
- name: Search /etc/audit/rules.d for audit rule entries
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "^.*path=/usr/bin/crontab.*$"
patterns: "*.rules"
register: find_crontab
- name: Use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_crontab.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_crontab.files | map(attribute='path') | list | first }}"
when: find_crontab.matched > 0
- name: Inserts/replaces the crontab rule in rules.d
lineinfile:
path: "{{ all_files[0] }}"
line: '-a always,exit -F path=/usr/bin/crontab -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_crontab
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80410-4
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030800
# Inserts/replaces the crontab rule in /etc/audit/audit.rules
- name: Inserts/replaces the crontab rule in audit.rules
lineinfile:
path: /etc/audit/audit.rules
line: '-a always,exit -F path=/usr/bin/crontab -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_crontab
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80410-4
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030800
|
Ensure auditd Collects Information on the Use of Privileged Commands - umount
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/umount -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/umount -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threast.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
SV-86797r4_rule, 3.1.7, CCI-000135, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AU-3(1), AU-12(c), SRG-OS-000042-GPOS-00020, SRG-OS-000392-GPOS-00172, SRG-OS-000471-GPOS-00215 Remediation Shell script: (show)
PATTERN="-a always,exit -F path=/usr/bin/umount\\s*.*"
GROUP="privileged"
FULL_RULE="-a always,exit -F path=/usr/bin/umount -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
# Inserts/replaces the rule in /etc/audit/rules.d
- name: Search /etc/audit/rules.d for audit rule entries
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "^.*path=/usr/bin/umount.*$"
patterns: "*.rules"
register: find_umount
- name: Use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_umount.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_umount.files | map(attribute='path') | list | first }}"
when: find_umount.matched > 0
- name: Inserts/replaces the umount rule in rules.d
lineinfile:
path: "{{ all_files[0] }}"
line: '-a always,exit -F path=/usr/bin/umount -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_umount
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80405-4
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030750
# Inserts/replaces the umount rule in /etc/audit/audit.rules
- name: Inserts/replaces the umount rule in audit.rules
lineinfile:
path: /etc/audit/audit.rules
line: '-a always,exit -F path=/usr/bin/umount -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_umount
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80405-4
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030750
|
Ensure auditd Collects Information on the Use of Privileged Commands - postdrop
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/sbin/postdrop -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/sbin/postdrop -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threast.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
SV-86799r3_rule, 3.1.7, CCI-000135, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AU-3(1), AU-12(c), SRG-OS-000042-GPOS-00020, SRG-OS-000392-GPOS-00172, SRG-OS-000471-GPOS-00215 Remediation Shell script: (show)
PATTERN="-a always,exit -F path=/usr/sbin/postdrop\\s*.*"
GROUP="privileged"
FULL_RULE="-a always,exit -F path=/usr/sbin/postdrop -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
# Inserts/replaces the rule in /etc/audit/rules.d
- name: Search /etc/audit/rules.d for audit rule entries
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "^.*path=/usr/sbin/postdrop.*$"
patterns: "*.rules"
register: find_postdrop
- name: Use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_postdrop.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_postdrop.files | map(attribute='path') | list | first }}"
when: find_postdrop.matched > 0
- name: Inserts/replaces the postdrop rule in rules.d
lineinfile:
path: "{{ all_files[0] }}"
line: '-a always,exit -F path=/usr/sbin/postdrop -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_postdrop
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80406-2
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030760
# Inserts/replaces the postdrop rule in /etc/audit/audit.rules
- name: Inserts/replaces the postdrop rule in audit.rules
lineinfile:
path: /etc/audit/audit.rules
line: '-a always,exit -F path=/usr/sbin/postdrop -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_postdrop
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80406-2
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030760
|
Ensure auditd Collects Information on the Use of Privileged Commands - chsh
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/chsh -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/chsh -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have compromised system accounts,
is a serious and ongoing concern and can have significant adverse impacts on organizations.
Auditing the use of privileged functions is one way to detect such misuse and identify
the risk from insider and advanced persistent threast.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
SV-86791r3_rule, 3.1.7, CCI-000135, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AU-3(1), AU-12(c), SRG-OS-000042-GPOS-00020, SRG-OS-000392-GPOS-00172, SRG-OS-000471-GPOS-00215 Remediation Shell script: (show)
PATTERN="-a always,exit -F path=/usr/bin/chsh\\s*.*"
GROUP="privileged"
FULL_RULE="-a always,exit -F path=/usr/bin/chsh -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | restrict |
---|
# Inserts/replaces the rule in /etc/audit/rules.d
- name: Search /etc/audit/rules.d for audit rule entries
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "^.*path=/usr/bin/chsh.*$"
patterns: "*.rules"
register: find_chsh
- name: Use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_chsh.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_chsh.files | map(attribute='path') | list | first }}"
when: find_chsh.matched > 0
- name: Inserts/replaces the chsh rule in rules.d
lineinfile:
path: "{{ all_files[0] }}"
line: '-a always,exit -F path=/usr/bin/chsh -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_chsh
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80404-7
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030720
# Inserts/replaces the chsh rule in /etc/audit/audit.rules
- name: Inserts/replaces the chsh rule in audit.rules
lineinfile:
path: /etc/audit/audit.rules
line: '-a always,exit -F path=/usr/bin/chsh -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
create: yes
tags:
- audit_rules_privileged_commands_chsh
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80404-7
- NIST-800-53-AU-3(1)
- NIST-800-53-AU-12(c)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-030720
|
Record Unauthorized Access Attempts Events to Files (unsuccessful)
[ref]groupAt a minimum, the audit system should collect unauthorized file
accesses for all users and root. Note that the "-F arch=b32" lines should be
present even on a 64 bit system. These commands identify system calls for
auditing. Even if the system is 64 bit it can still execute 32 bit system
calls. Additionally, these rules can be configured in a number of ways while
still achieving the desired effect. An example of this is that the "-S" calls
could be split up and placed on separate lines, however, this is less efficient.
Add the following to /etc/audit/audit.rules :
-a always,exit -F arch=b32 -S creat,open,openat,open_by_handle_at,truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
-a always,exit -F arch=b32 -S creat,open,openat,open_by_handle_at,truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access
If your system is 64 bit then these lines should be duplicated and the
arch=b32 replaced with arch=b64 as follows:
-a always,exit -F arch=b64 -S creat,open,openat,open_by_handle_at,truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
-a always,exit -F arch=b64 -S creat,open,openat,open_by_handle_at,truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access |
contains 6 rules |
Record Unauthorized Access Attempts to Files (unsuccessful) - open_by_handle_at
[ref]ruleAt a minimum, the audit system should collect unauthorized file
accesses for all users and root. If the auditd daemon is configured
to use the augenrules program to read audit rules during daemon
startup (the default), add the following lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S open_by_handle_at -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
-a always,exit -F arch=b32 -S open_by_handle_at -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S open_by_handle_at -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
-a always,exit -F arch=b64 -S open_by_handle_at -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S open_by_handle_at,truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
-a always,exit -F arch=b32 -S open_by_handle_at,truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S open_by_handle_at,truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
-a always,exit -F arch=b64 -S open_by_handle_at,truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=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. References:
FAU_GEN.1.1.c, SV-86753r3_rule, 5.2.10, 3.1.7, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.4, Req-10.2.1, SRG-OS-000064-GPOS-00033, SRG-OS-000458-GPOS-00203, SRG-OS-000461-GPOS-00205, SRG-OS-000392-GPOS-00172 Remediation Shell script: (show)
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S open_by_handle_at -F exit=-EACCES.*"
GROUP="access"
FULL_RULE="-a always,exit -F arch=$ARCH -S open_by_handle_at -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S open_by_handle_at -F exit=-EPERM.*"
GROUP="access"
FULL_RULE="-a always,exit -F arch=$ARCH -S open_by_handle_at -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
#
# What architecture are we on?
#
- name: Set architecture for audit open_by_handle_at tasks
set_fact:
audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"
#
# Inserts/replaces the rule in /etc/audit/rules.d
#
- name: Search /etc/audit/rules.d for other DAC audit rules
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "-F key=perm_mod$"
patterns: "*.rules"
register: find_open_by_handle_at
- name: If existing DAC ruleset not found, use /etc/audit/rules.d/access.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/access.rules
when: find_open_by_handle_at.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_open_by_handle_at.files | map(attribute='path') | list | first }}"
when: find_open_by_handle_at.matched > 0
- name: Inserts/replaces the open_by_handle_at rule in rules.d when on x86
lineinfile:
path: "{{ all_files[0] }}"
line: "{{ item }}"
create: yes
with_items:
- "-a always,exit -F arch=b32 -S open_by_handle_at -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
- "-a always,exit -F arch=b32 -S open_by_handle_at -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
tags:
- audit_rules_unsuccessful_file_modification_open_by_handle_at
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80388-2
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.4
- PCI-DSS-Req-10.2.1
- DISA-STIG-RHEL-07-030530
- name: Inserts/replaces the open_by_handle_at rule in rules.d when on x86_64
lineinfile:
path: "{{ all_files[0] }}"
line: "{{ item }}"
create: yes
with_items:
- "-a always,exit -F arch=b64 -S open_by_handle_at -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
- "-a always,exit -F arch=b64 -S open_by_handle_at -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
when: audit_arch == 'b64'
tags:
- audit_rules_unsuccessful_file_modification_open_by_handle_at
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80388-2
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.4
- PCI-DSS-Req-10.2.1
- DISA-STIG-RHEL-07-030530
#
# Inserts/replaces the rule in /etc/audit/audit.rules
#
- name: Inserts/replaces the open_by_handle_at rule in /etc/audit/audit.rules when on x86
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
with_items:
- "-a always,exit -F arch=b32 -S open_by_handle_at -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
- "-a always,exit -F arch=b32 -S open_by_handle_at -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
tags:
- audit_rules_unsuccessful_file_modification_open_by_handle_at
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80388-2
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.4
- PCI-DSS-Req-10.2.1
- DISA-STIG-RHEL-07-030530
- name: Inserts/replaces the open_by_handle_at rule in audit.rules when on x86_64
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
create: yes
with_items:
- "-a always,exit -F arch=b64 -S open_by_handle_at -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
- "-a always,exit -F arch=b64 -S open_by_handle_at -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
when: audit_arch == 'b64'
tags:
- audit_rules_unsuccessful_file_modification_open_by_handle_at
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80388-2
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.4
- PCI-DSS-Req-10.2.1
- DISA-STIG-RHEL-07-030530
|
Record Unauthorized Access Attempts to Files (unsuccessful) - open
[ref]ruleAt a minimum, the audit system should collect unauthorized file
accesses for all users and root. If the auditd daemon is configured
to use the augenrules program to read audit rules during daemon
startup (the default), add the following lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S open -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
-a always,exit -F arch=b32 -S open -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S open -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
-a always,exit -F arch=b64 -S open -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S open -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
-a always,exit -F arch=b32 -S open -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S open -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
-a always,exit -F arch=b64 -S open -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=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. References:
FAU_GEN.1.1.c, SV-86749r3_rule, 5.2.10, 3.1.7, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.4, Req-10.2.1, SRG-OS-000064-GPOS-00033, SRG-OS-000458-GPOS-00203, SRG-OS-000461-GPOS-00205, SRG-OS-000392-GPOS-00172 Remediation Shell script: (show)
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S open -F exit=-EACCES.*"
GROUP="access"
FULL_RULE="-a always,exit -F arch=$ARCH -S open -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S open -F exit=-EPERM.*"
GROUP="access"
FULL_RULE="-a always,exit -F arch=$ARCH -S open -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
#
# What architecture are we on?
#
- name: Set architecture for audit open tasks
set_fact:
audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"
#
# Inserts/replaces the rule in /etc/audit/rules.d
#
- name: Search /etc/audit/rules.d for other DAC audit rules
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "-F key=perm_mod$"
patterns: "*.rules"
register: find_open
- name: If existing DAC ruleset not found, use /etc/audit/rules.d/access.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/access.rules
when: find_open.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_open.files | map(attribute='path') | list | first }}"
when: find_open.matched > 0
- name: Inserts/replaces the open rule in rules.d when on x86
lineinfile:
path: "{{ all_files[0] }}"
line: "{{ item }}"
create: yes
with_items:
- "-a always,exit -F arch=b32 -S open -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
- "-a always,exit -F arch=b32 -S open -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
tags:
- audit_rules_unsuccessful_file_modification_open
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80386-6
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.4
- PCI-DSS-Req-10.2.1
- DISA-STIG-RHEL-07-030510
- name: Inserts/replaces the open rule in rules.d when on x86_64
lineinfile:
path: "{{ all_files[0] }}"
line: "{{ item }}"
create: yes
with_items:
- "-a always,exit -F arch=b64 -S open -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
- "-a always,exit -F arch=b64 -S open -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
when: audit_arch == 'b64'
tags:
- audit_rules_unsuccessful_file_modification_open
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80386-6
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.4
- PCI-DSS-Req-10.2.1
- DISA-STIG-RHEL-07-030510
#
# Inserts/replaces the rule in /etc/audit/audit.rules
#
- name: Inserts/replaces the open rule in /etc/audit/audit.rules when on x86
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
with_items:
- "-a always,exit -F arch=b32 -S open -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
- "-a always,exit -F arch=b32 -S open -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
tags:
- audit_rules_unsuccessful_file_modification_open
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80386-6
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.4
- PCI-DSS-Req-10.2.1
- DISA-STIG-RHEL-07-030510
- name: Inserts/replaces the open rule in audit.rules when on x86_64
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
create: yes
with_items:
- "-a always,exit -F arch=b64 -S open -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
- "-a always,exit -F arch=b64 -S open -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
when: audit_arch == 'b64'
tags:
- audit_rules_unsuccessful_file_modification_open
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80386-6
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.4
- PCI-DSS-Req-10.2.1
- DISA-STIG-RHEL-07-030510
|
Record Unauthorized Access Attempts to Files (unsuccessful) - creat
[ref]ruleAt a minimum, the audit system should collect unauthorized file
accesses for all users and root. If the auditd daemon is configured
to use the augenrules program to read audit rules during daemon
startup (the default), add the following lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S creat -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
-a always,exit -F arch=b32 -S creat -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S creat -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
-a always,exit -F arch=b64 -S creat -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S creat -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
-a always,exit -F arch=b32 -S creat -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S creat -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
-a always,exit -F arch=b64 -S creat -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=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. References:
FAU_GEN.1.1.c, SV-86747r3_rule, 5.2.10, 3.1.7, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.4, Req-10.2.1, SRG-OS-000064-GPOS-00033, SRG-OS-000458-GPOS-00203, SRG-OS-000461-GPOS-00205, SRG-OS-000392-GPOS-00172 Remediation Shell script: (show)
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S creat -F exit=-EACCES.*"
GROUP="access"
FULL_RULE="-a always,exit -F arch=$ARCH -S creat -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S creat -F exit=-EPERM.*"
GROUP="access"
FULL_RULE="-a always,exit -F arch=$ARCH -S creat -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
#
# What architecture are we on?
#
- name: Set architecture for audit creat tasks
set_fact:
audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"
#
# Inserts/replaces the rule in /etc/audit/rules.d
#
- name: Search /etc/audit/rules.d for other DAC audit rules
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "-F key=perm_mod$"
patterns: "*.rules"
register: find_creat
- name: If existing DAC ruleset not found, use /etc/audit/rules.d/access.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/access.rules
when: find_creat.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_creat.files | map(attribute='path') | list | first }}"
when: find_creat.matched > 0
- name: Inserts/replaces the creat rule in rules.d when on x86
lineinfile:
path: "{{ all_files[0] }}"
line: "{{ item }}"
create: yes
with_items:
- "-a always,exit -F arch=b32 -S creat -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
- "-a always,exit -F arch=b32 -S creat -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
tags:
- audit_rules_unsuccessful_file_modification_creat
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80385-8
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.4
- PCI-DSS-Req-10.2.1
- DISA-STIG-RHEL-07-030500
- name: Inserts/replaces the creat rule in rules.d when on x86_64
lineinfile:
path: "{{ all_files[0] }}"
line: "{{ item }}"
create: yes
with_items:
- "-a always,exit -F arch=b64 -S creat -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
- "-a always,exit -F arch=b64 -S creat -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
when: audit_arch == 'b64'
tags:
- audit_rules_unsuccessful_file_modification_creat
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80385-8
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.4
- PCI-DSS-Req-10.2.1
- DISA-STIG-RHEL-07-030500
#
# Inserts/replaces the rule in /etc/audit/audit.rules
#
- name: Inserts/replaces the creat rule in /etc/audit/audit.rules when on x86
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
with_items:
- "-a always,exit -F arch=b32 -S creat -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
- "-a always,exit -F arch=b32 -S creat -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
tags:
- audit_rules_unsuccessful_file_modification_creat
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80385-8
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.4
- PCI-DSS-Req-10.2.1
- DISA-STIG-RHEL-07-030500
- name: Inserts/replaces the creat rule in audit.rules when on x86_64
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
create: yes
with_items:
- "-a always,exit -F arch=b64 -S creat -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
- "-a always,exit -F arch=b64 -S creat -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
when: audit_arch == 'b64'
tags:
- audit_rules_unsuccessful_file_modification_creat
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80385-8
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.4
- PCI-DSS-Req-10.2.1
- DISA-STIG-RHEL-07-030500
|
Record Unauthorized Access Attempts to Files (unsuccessful) - ftruncate
[ref]ruleAt a minimum, the audit system should collect unauthorized file
accesses for all users and root. If the auditd daemon is configured
to use the augenrules program to read audit rules during daemon
startup (the default), add the following lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
-a always,exit -F arch=b32 -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S ftruncate -F exiu=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
-a always,exit -F arch=b64 -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
-a always,exit -F arch=b32 -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
-a always,exit -F arch=b64 -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=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. References:
FAU_GEN.1.1.c, SV-86757r3_rule, 5.2.10, 3.1.7, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.4, Req-10.2.1, SRG-OS-000064-GPOS-00033, SRG-OS-000458-GPOS-00203, SRG-OS-000461-GPOS-00205, SRG-OS-000392-GPOS-00172 Remediation Shell script: (show)
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S ftruncate -F exit=-EACCES.*"
GROUP="access"
FULL_RULE="-a always,exit -F arch=$ARCH -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S ftruncate -F exit=-EPERM.*"
GROUP="access"
FULL_RULE="-a always,exit -F arch=$ARCH -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
#
# What architecture are we on?
#
- name: Set architecture for audit ftruncate tasks
set_fact:
audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"
#
# Inserts/replaces the rule in /etc/audit/rules.d
#
- name: Search /etc/audit/rules.d for other DAC audit rules
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "-F key=perm_mod$"
patterns: "*.rules"
register: find_ftruncate
- name: If existing DAC ruleset not found, use /etc/audit/rules.d/access.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/access.rules
when: find_ftruncate.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_ftruncate.files | map(attribute='path') | list | first }}"
when: find_ftruncate.matched > 0
- name: Inserts/replaces the ftruncate rule in rules.d when on x86
lineinfile:
path: "{{ all_files[0] }}"
line: "{{ item }}"
create: yes
with_items:
- "-a always,exit -F arch=b32 -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
- "-a always,exit -F arch=b32 -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
tags:
- audit_rules_unsuccessful_file_modification_ftruncate
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80390-8
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.4
- PCI-DSS-Req-10.2.1
- DISA-STIG-RHEL-07-030550
- name: Inserts/replaces the ftruncate rule in rules.d when on x86_64
lineinfile:
path: "{{ all_files[0] }}"
line: "{{ item }}"
create: yes
with_items:
- "-a always,exit -F arch=b64 -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
- "-a always,exit -F arch=b64 -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
when: audit_arch == 'b64'
tags:
- audit_rules_unsuccessful_file_modification_ftruncate
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80390-8
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.4
- PCI-DSS-Req-10.2.1
- DISA-STIG-RHEL-07-030550
#
# Inserts/replaces the rule in /etc/audit/audit.rules
#
- name: Inserts/replaces the ftruncate rule in /etc/audit/audit.rules when on x86
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
with_items:
- "-a always,exit -F arch=b32 -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
- "-a always,exit -F arch=b32 -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
tags:
- audit_rules_unsuccessful_file_modification_ftruncate
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80390-8
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.4
- PCI-DSS-Req-10.2.1
- DISA-STIG-RHEL-07-030550
- name: Inserts/replaces the ftruncate rule in audit.rules when on x86_64
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
create: yes
with_items:
- "-a always,exit -F arch=b64 -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
- "-a always,exit -F arch=b64 -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
when: audit_arch == 'b64'
tags:
- audit_rules_unsuccessful_file_modification_ftruncate
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80390-8
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.4
- PCI-DSS-Req-10.2.1
- DISA-STIG-RHEL-07-030550
|
Record Unauthorized Access Attempts to Files (unsuccessful) - truncate
[ref]ruleAt a minimum, the audit system should collect unauthorized file
accesses for all users and root. If the auditd daemon is configured
to use the augenrules program to read audit rules during daemon
startup (the default), add the following lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S truncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
-a always,exit -F arch=b32 -S truncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S truncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
-a always,exit -F arch=b64 -S truncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S truncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
-a always,exit -F arch=b32 -S truncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S truncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
-a always,exit -F arch=b64 -S truncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=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. References:
FAU_GEN.1.1.c, SV-86755r3_rule, 5.2.10, 3.1.7, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.4, Req-10.2.1, SRG-OS-000064-GPOS-00033, SRG-OS-000458-GPOS-00203, SRG-OS-000461-GPOS-00205, SRG-OS-000392-GPOS-00172 Remediation Shell script: (show)
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S truncate -F exit=-EACCES.*"
GROUP="access"
FULL_RULE="-a always,exit -F arch=$ARCH -S truncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S truncate -F exit=-EPERM.*"
GROUP="access"
FULL_RULE="-a always,exit -F arch=$ARCH -S truncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
#
# What architecture are we on?
#
- name: Set architecture for audit truncate tasks
set_fact:
audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"
#
# Inserts/replaces the rule in /etc/audit/rules.d
#
- name: Search /etc/audit/rules.d for other DAC audit rules
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "-F key=perm_mod$"
patterns: "*.rules"
register: find_truncate
- name: If existing DAC ruleset not found, use /etc/audit/rules.d/access.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/access.rules
when: find_truncate.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_truncate.files | map(attribute='path') | list | first }}"
when: find_truncate.matched > 0
- name: Inserts/replaces the truncate rule in rules.d when on x86
lineinfile:
path: "{{ all_files[0] }}"
line: "{{ item }}"
create: yes
with_items:
- "-a always,exit -F arch=b32 -S truncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
- "-a always,exit -F arch=b32 -S truncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
tags:
- audit_rules_unsuccessful_file_modification_truncate
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80389-0
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.4
- PCI-DSS-Req-10.2.1
- DISA-STIG-RHEL-07-030540
- name: Inserts/replaces the truncate rule in rules.d when on x86_64
lineinfile:
path: "{{ all_files[0] }}"
line: "{{ item }}"
create: yes
with_items:
- "-a always,exit -F arch=b64 -S truncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
- "-a always,exit -F arch=b64 -S truncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
when: audit_arch == 'b64'
tags:
- audit_rules_unsuccessful_file_modification_truncate
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80389-0
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.4
- PCI-DSS-Req-10.2.1
- DISA-STIG-RHEL-07-030540
#
# Inserts/replaces the rule in /etc/audit/audit.rules
#
- name: Inserts/replaces the truncate rule in /etc/audit/audit.rules when on x86
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
with_items:
- "-a always,exit -F arch=b32 -S truncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
- "-a always,exit -F arch=b32 -S truncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
tags:
- audit_rules_unsuccessful_file_modification_truncate
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80389-0
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.4
- PCI-DSS-Req-10.2.1
- DISA-STIG-RHEL-07-030540
- name: Inserts/replaces the truncate rule in audit.rules when on x86_64
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
create: yes
with_items:
- "-a always,exit -F arch=b64 -S truncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
- "-a always,exit -F arch=b64 -S truncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
when: audit_arch == 'b64'
tags:
- audit_rules_unsuccessful_file_modification_truncate
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80389-0
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.4
- PCI-DSS-Req-10.2.1
- DISA-STIG-RHEL-07-030540
|
Record Unauthorized Access Attempts to Files (unsuccessful) - openat
[ref]ruleAt a minimum, the audit system should collect unauthorized file
accesses for all users and root. If the auditd daemon is configured
to use the augenrules program to read audit rules during daemon
startup (the default), add the following lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S openat -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
-a always,exit -F arch=b32 -S openat -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S openat -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
-a always,exit -F arch=b64 -S openat -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S openat -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
-a always,exit -F arch=b32 -S openat -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S openat -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
-a always,exit -F arch=b64 -S openat -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=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. References:
FAU_GEN.1.1.c, SV-86751r3_rule, 5.2.10, 3.1.7, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.4, Req-10.2.1, SRG-OS-000064-GPOS-00033, SRG-OS-000458-GPOS-00203, SRG-OS-000461-GPOS-00205, SRG-OS-000392-GPOS-00172 Remediation Shell script: (show)
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S openat -F exit=-EACCES.*"
GROUP="access"
FULL_RULE="-a always,exit -F arch=$ARCH -S openat -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S openat -F exit=-EPERM.*"
GROUP="access"
FULL_RULE="-a always,exit -F arch=$ARCH -S openat -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
#
# What architecture are we on?
#
- name: Set architecture for audit openat tasks
set_fact:
audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"
#
# Inserts/replaces the rule in /etc/audit/rules.d
#
- name: Search /etc/audit/rules.d for other DAC audit rules
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "-F key=perm_mod$"
patterns: "*.rules"
register: find_openat
- name: If existing DAC ruleset not found, use /etc/audit/rules.d/access.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/access.rules
when: find_openat.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_openat.files | map(attribute='path') | list | first }}"
when: find_openat.matched > 0
- name: Inserts/replaces the openat rule in rules.d when on x86
lineinfile:
path: "{{ all_files[0] }}"
line: "{{ item }}"
create: yes
with_items:
- "-a always,exit -F arch=b32 -S openat -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
- "-a always,exit -F arch=b32 -S openat -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
tags:
- audit_rules_unsuccessful_file_modification_openat
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80387-4
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.4
- PCI-DSS-Req-10.2.1
- DISA-STIG-RHEL-07-030520
- name: Inserts/replaces the openat rule in rules.d when on x86_64
lineinfile:
path: "{{ all_files[0] }}"
line: "{{ item }}"
create: yes
with_items:
- "-a always,exit -F arch=b64 -S openat -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
- "-a always,exit -F arch=b64 -S openat -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
when: audit_arch == 'b64'
tags:
- audit_rules_unsuccessful_file_modification_openat
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80387-4
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.4
- PCI-DSS-Req-10.2.1
- DISA-STIG-RHEL-07-030520
#
# Inserts/replaces the rule in /etc/audit/audit.rules
#
- name: Inserts/replaces the openat rule in /etc/audit/audit.rules when on x86
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
with_items:
- "-a always,exit -F arch=b32 -S openat -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
- "-a always,exit -F arch=b32 -S openat -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
tags:
- audit_rules_unsuccessful_file_modification_openat
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80387-4
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.4
- PCI-DSS-Req-10.2.1
- DISA-STIG-RHEL-07-030520
- name: Inserts/replaces the openat rule in audit.rules when on x86_64
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
create: yes
with_items:
- "-a always,exit -F arch=b64 -S openat -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access"
- "-a always,exit -F arch=b64 -S openat -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access"
when: audit_arch == 'b64'
tags:
- audit_rules_unsuccessful_file_modification_openat
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80387-4
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.4
- PCI-DSS-Req-10.2.1
- DISA-STIG-RHEL-07-030520
|
Record Events that Modify User/Group Information - /etc/shadow
[ref]ruleIf the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following lines to a file with suffix .rules in the
directory /etc/audit/rules.d , in order to capture events that modify
account changes:
-w /etc/shadow -p wa -k audit_rules_usergroup_modification
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file, in order to capture events that modify
account changes:
-w /etc/shadow -p wa -k audit_rules_usergroup_modification Rationale:In addition to auditing new user and group accounts, these watches
will alert the system administrator(s) to any modifications. Any unexpected
users, groups, or modifications should be investigated for legitimacy. References:
FAU_GEN.1.1.c, SV-87823r3_rule, 5.2.5, 5.4.1.1, 3.1.7, CCI-000018, CCI-000172, CCI-001403, CCI-002130, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-2(4), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.5, SRG-OS-000004-GPOS-00004 Remediation Shell script: (show)
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix audit file system object watch rule for given path:
# * if rule exists, also verifies the -w bits match the requirements
# * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
# audit rules file, depending on the tool which was used to load audit rules
#
# Expects four arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules'
# * path value of -w audit rule's argument
# * required access bits value of -p audit rule's argument
# * key value of -k audit rule's argument
#
# Example call:
#
# fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
#
function fix_audit_watch_rule {
# Load function arguments into local variables
local tool="$1"
local path="$2"
local required_access_bits="$3"
local key="$4"
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
declare -a files_to_inspect
# Check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
exit 1
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
elif [ "$tool" == 'augenrules' ]
then
# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
# Get pair -- filepath : matching_row into @matches array
IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
# Reset IFS back to default
unset IFS
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
files_to_inspect="/etc/audit/rules.d/$key.rules"
# If the $key.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Escape slashes in path for use in sed pattern below
local esc_path=${path//$'/'/$'\/'}
# Define BRE whitespace class shortcut
local sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "$required_access_bits" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
fi
done
}
fix_audit_watch_rule "auditctl" "/etc/shadow" "wa" "audit_rules_usergroup_modification"
fix_audit_watch_rule "augenrules" "/etc/shadow" "wa" "audit_rules_usergroup_modification"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
#
# What architecture are we on?
#
- name: Set architecture for audit shadow tasks
set_fact:
audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"
#
# Inserts/replaces the rule in /etc/audit/rules.d
#
- name: Search /etc/audit/rules.d for other user/group modification audit rules
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "-k audit_rules_usergroup_modification$"
patterns: "*.rules"
register: find_shadow
- name: If existing user/group modification ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_shadow.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_shadow.files | map(attribute='path') | list | first }}"
when: find_shadow.matched > 0
- name: Inserts/replaces the shadow rule in rules.d when on x86
lineinfile:
path: "{{ all_files[0] }}"
line: "-w /etc/shadow -p wa -k audit_rules_usergroup_modification"
create: yes
tags:
- audit_rules_usergroup_modification_shadow
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80431-0
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030873
#
# Inserts/replaces the rule in /etc/audit/audit.rules
#
- name: Inserts/replaces the shadow rule in /etc/audit/audit.rules
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
with_items:
- "-w /etc/shadow -p wa -k audit_rules_usergroup_modification"
tags:
- audit_rules_usergroup_modification_shadow
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80431-0
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030873
|
Record Attempts to Alter Process and Session Initiation Information
[ref]ruleThe audit system already collects process information for all
users and root. If the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following lines to a file with suffix .rules in the
directory /etc/audit/rules.d in order to watch for attempted manual
edits of files involved in storing such process information:
-w /var/run/utmp -p wa -k session
-w /var/log/btmp -p wa -k session
-w /var/log/wtmp -p wa -k session
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file in order to watch for attempted manual
edits of files involved in storing such process information:
-w /var/run/utmp -p wa -k session
-w /var/log/btmp -p wa -k session
-w /var/log/wtmp -p wa -k session Rationale:Manual editing of these files may indicate nefarious activity, such
as an attacker attempting to remove evidence of an intrusion. References:
FAU_GEN.1.1.c, 5.2.9, 5.4.1.1, 3.1.7, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.3 Remediation Shell script: (show)
# Perform the remediation
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix audit file system object watch rule for given path:
# * if rule exists, also verifies the -w bits match the requirements
# * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
# audit rules file, depending on the tool which was used to load audit rules
#
# Expects four arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules'
# * path value of -w audit rule's argument
# * required access bits value of -p audit rule's argument
# * key value of -k audit rule's argument
#
# Example call:
#
# fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
#
function fix_audit_watch_rule {
# Load function arguments into local variables
local tool="$1"
local path="$2"
local required_access_bits="$3"
local key="$4"
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
declare -a files_to_inspect
# Check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
exit 1
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
elif [ "$tool" == 'augenrules' ]
then
# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
# Get pair -- filepath : matching_row into @matches array
IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
# Reset IFS back to default
unset IFS
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
files_to_inspect="/etc/audit/rules.d/$key.rules"
# If the $key.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Escape slashes in path for use in sed pattern below
local esc_path=${path//$'/'/$'\/'}
# Define BRE whitespace class shortcut
local sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "$required_access_bits" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
fi
done
}
fix_audit_watch_rule "auditctl" "/var/run/utmp" "wa" "session"
fix_audit_watch_rule "augenrules" "/var/run/utmp" "wa" "session"
# Function to fix audit file system object watch rule for given path:
# * if rule exists, also verifies the -w bits match the requirements
# * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
# audit rules file, depending on the tool which was used to load audit rules
#
# Expects four arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules'
# * path value of -w audit rule's argument
# * required access bits value of -p audit rule's argument
# * key value of -k audit rule's argument
#
# Example call:
#
# fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
#
function fix_audit_watch_rule {
# Load function arguments into local variables
local tool="$1"
local path="$2"
local required_access_bits="$3"
local key="$4"
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
declare -a files_to_inspect
# Check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
exit 1
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
elif [ "$tool" == 'augenrules' ]
then
# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
# Get pair -- filepath : matching_row into @matches array
IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
# Reset IFS back to default
unset IFS
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
files_to_inspect="/etc/audit/rules.d/$key.rules"
# If the $key.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Escape slashes in path for use in sed pattern below
local esc_path=${path//$'/'/$'\/'}
# Define BRE whitespace class shortcut
local sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "$required_access_bits" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
fi
done
}
fix_audit_watch_rule "auditctl" "/var/log/btmp" "wa" "session"
fix_audit_watch_rule "augenrules" "/var/log/btmp" "wa" "session"
# Function to fix audit file system object watch rule for given path:
# * if rule exists, also verifies the -w bits match the requirements
# * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
# audit rules file, depending on the tool which was used to load audit rules
#
# Expects four arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules'
# * path value of -w audit rule's argument
# * required access bits value of -p audit rule's argument
# * key value of -k audit rule's argument
#
# Example call:
#
# fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
#
function fix_audit_watch_rule {
# Load function arguments into local variables
local tool="$1"
local path="$2"
local required_access_bits="$3"
local key="$4"
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
declare -a files_to_inspect
# Check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
exit 1
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
elif [ "$tool" == 'augenrules' ]
then
# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
# Get pair -- filepath : matching_row into @matches array
IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
# Reset IFS back to default
unset IFS
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
files_to_inspect="/etc/audit/rules.d/$key.rules"
# If the $key.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Escape slashes in path for use in sed pattern below
local esc_path=${path//$'/'/$'\/'}
# Define BRE whitespace class shortcut
local sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "$required_access_bits" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
fi
done
}
fix_audit_watch_rule "auditctl" "/var/log/wtmp" "wa" "session"
fix_audit_watch_rule "augenrules" "/var/log/wtmp" "wa" "session"
|
Record Events that Modify User/Group Information - /etc/gshadow
[ref]ruleIf the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following lines to a file with suffix .rules in the
directory /etc/audit/rules.d , in order to capture events that modify
account changes:
-w /etc/gshadow -p wa -k audit_rules_usergroup_modification
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file, in order to capture events that modify
account changes:
-w /etc/gshadow -p wa -k audit_rules_usergroup_modification Rationale:In addition to auditing new user and group accounts, these watches
will alert the system administrator(s) to any modifications. Any unexpected
users, groups, or modifications should be investigated for legitimacy. References:
FAU_GEN.1.1.c, SV-87819r3_rule, 5.2.5, 5.4.1.1, 3.1.7, CCI-000018, CCI-000172, CCI-001403, CCI-002130, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-2(4), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.5, SRG-OS-000004-GPOS-00004 Remediation Shell script: (show)
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix audit file system object watch rule for given path:
# * if rule exists, also verifies the -w bits match the requirements
# * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
# audit rules file, depending on the tool which was used to load audit rules
#
# Expects four arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules'
# * path value of -w audit rule's argument
# * required access bits value of -p audit rule's argument
# * key value of -k audit rule's argument
#
# Example call:
#
# fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
#
function fix_audit_watch_rule {
# Load function arguments into local variables
local tool="$1"
local path="$2"
local required_access_bits="$3"
local key="$4"
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
declare -a files_to_inspect
# Check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
exit 1
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
elif [ "$tool" == 'augenrules' ]
then
# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
# Get pair -- filepath : matching_row into @matches array
IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
# Reset IFS back to default
unset IFS
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
files_to_inspect="/etc/audit/rules.d/$key.rules"
# If the $key.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Escape slashes in path for use in sed pattern below
local esc_path=${path//$'/'/$'\/'}
# Define BRE whitespace class shortcut
local sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "$required_access_bits" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
fi
done
}
fix_audit_watch_rule "auditctl" "/etc/gshadow" "wa" "audit_rules_usergroup_modification"
fix_audit_watch_rule "augenrules" "/etc/gshadow" "wa" "audit_rules_usergroup_modification"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
#
# What architecture are we on?
#
- name: Set architecture for audit gshadow tasks
set_fact:
audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"
#
# Inserts/replaces the rule in /etc/audit/rules.d
#
- name: Search /etc/audit/rules.d for other user/group modification audit rules
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "-k audit_rules_usergroup_modification$"
patterns: "*.rules"
register: find_gshadow
- name: If existing user/group modification ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_gshadow.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_gshadow.files | map(attribute='path') | list | first }}"
when: find_gshadow.matched > 0
- name: Inserts/replaces the gshadow rule in rules.d when on x86
lineinfile:
path: "{{ all_files[0] }}"
line: "-w /etc/gshadow -p wa -k audit_rules_usergroup_modification"
create: yes
tags:
- audit_rules_usergroup_modification_gshadow
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80432-8
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030872
#
# Inserts/replaces the rule in /etc/audit/audit.rules
#
- name: Inserts/replaces the gshadow rule in /etc/audit/audit.rules
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
with_items:
- "-w /etc/gshadow -p wa -k audit_rules_usergroup_modification"
tags:
- audit_rules_usergroup_modification_gshadow
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80432-8
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030872
|
Shutdown System When Auditing Failures Occur
[ref]ruleIf the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following line to a file with suffix .rules in the
directory /etc/audit/rules.d :
-f 2
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to the
top of the /etc/audit/audit.rules file:
-f 2 Rationale:It is critical for the appropriate personnel to be aware if a system
is at risk of failing to process audit logs as required. Without this
notification, the security personnel may be unaware of an impending failure of
the audit capability, and system operation may be adversely affected.
Audit processing failures include software/hardware errors, failures in the
audit capturing mechanisms, and audit storage capacity being reached or
exceeded. References:
SV-86705r3_rule, 3.3.1, 3.3.4, CCI-000139, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AU-5, AU-5(a), SRG-OS-000046-GPOS-00022, SRG-OS-000047-GPOS-00023 Remediation Shell script: (show)
# Traverse all of:
#
# /etc/audit/audit.rules, (for auditctl case)
# /etc/audit/rules.d/*.rules (for augenrules case)
#
# files to check if '-f .*' setting is present in that '*.rules' file already.
# If found, delete such occurrence since auditctl(8) manual page instructs the
# '-f 2' rule should be placed as the last rule in the configuration
find /etc/audit /etc/audit/rules.d -maxdepth 1 -type f -name *.rules -exec sed -i '/-e[[:space:]]\+.*/d' {} ';'
# Append '-f 2' requirement at the end of both:
# * /etc/audit/audit.rules file (for auditctl case)
# * /etc/audit/rules.d/immutable.rules (for augenrules case)
for AUDIT_FILE in "/etc/audit/audit.rules" "/etc/audit/rules.d/immutable.rules"
do
echo '' >> $AUDIT_FILE
echo '# Set the audit.rules configuration to halt system upon audit failure per security requirements' >> $AUDIT_FILE
echo '-f 2' >> $AUDIT_FILE
done
|
Ensure auditd Collects System Administrator Actions
[ref]ruleAt a minimum, the audit system should collect administrator actions
for all users and root. If the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the default),
add the following line to a file with suffix .rules in the directory
/etc/audit/rules.d :
-w /etc/sudoers -p wa -k actions
-w /etc/sudoers.d/ -p wa -k actions
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-w /etc/sudoers -p wa -k actions
-w /etc/sudoers.d/ -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. References:
FAU_GEN.1.1.c, SV-86787r4_rule, 5.4.1.1, 3.1.7, CCI-000126, CCI-000130, CCI-000135, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-2(7)(b), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), iAU-3(1), AU-12(a), AU-12(c), IR-5, Req-10.2.2, Req-10.2.5.b, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215 Remediation Shell script: (show)
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix audit file system object watch rule for given path:
# * if rule exists, also verifies the -w bits match the requirements
# * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
# audit rules file, depending on the tool which was used to load audit rules
#
# Expects four arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules'
# * path value of -w audit rule's argument
# * required access bits value of -p audit rule's argument
# * key value of -k audit rule's argument
#
# Example call:
#
# fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
#
function fix_audit_watch_rule {
# Load function arguments into local variables
local tool="$1"
local path="$2"
local required_access_bits="$3"
local key="$4"
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
declare -a files_to_inspect
# Check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
exit 1
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
elif [ "$tool" == 'augenrules' ]
then
# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
# Get pair -- filepath : matching_row into @matches array
IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
# Reset IFS back to default
unset IFS
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
files_to_inspect="/etc/audit/rules.d/$key.rules"
# If the $key.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Escape slashes in path for use in sed pattern below
local esc_path=${path//$'/'/$'\/'}
# Define BRE whitespace class shortcut
local sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "$required_access_bits" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
fi
done
}
fix_audit_watch_rule "auditctl" "/etc/sudoers" "wa" "actions"
fix_audit_watch_rule "augenrules" "/etc/sudoers" "wa" "actions"
|
Record Events that Modify the System's Network Environment
[ref]ruleIf the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following lines to a file with suffix .rules in the
directory /etc/audit/rules.d , setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S sethostname,setdomainname -F key=audit_rules_networkconfig_modification
-w /etc/issue -p wa -k audit_rules_networkconfig_modification
-w /etc/issue.net -p wa -k audit_rules_networkconfig_modification
-w /etc/hosts -p wa -k audit_rules_networkconfig_modification
-w /etc/sysconfig/network -p wa -k audit_rules_networkconfig_modification
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file, setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S sethostname,setdomainname -F key=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. References:
5.2.6, 5.4.1.1, 3.1.7, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5 Remediation Shell script: (show)
# First perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ $(getconf LONG_BIT) = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S .* -k *"
# Use escaped BRE regex to specify rule group
GROUP="set\(host\|domain\)name"
FULL_RULE="-a always,exit -F arch=$ARCH -S sethostname -S setdomainname -k audit_rules_networkconfig_modification"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
# Then perform the remediations for the watch rules
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix audit file system object watch rule for given path:
# * if rule exists, also verifies the -w bits match the requirements
# * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
# audit rules file, depending on the tool which was used to load audit rules
#
# Expects four arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules'
# * path value of -w audit rule's argument
# * required access bits value of -p audit rule's argument
# * key value of -k audit rule's argument
#
# Example call:
#
# fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
#
function fix_audit_watch_rule {
# Load function arguments into local variables
local tool="$1"
local path="$2"
local required_access_bits="$3"
local key="$4"
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
declare -a files_to_inspect
# Check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
exit 1
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
elif [ "$tool" == 'augenrules' ]
then
# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
# Get pair -- filepath : matching_row into @matches array
IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
# Reset IFS back to default
unset IFS
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
files_to_inspect="/etc/audit/rules.d/$key.rules"
# If the $key.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Escape slashes in path for use in sed pattern below
local esc_path=${path//$'/'/$'\/'}
# Define BRE whitespace class shortcut
local sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "$required_access_bits" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
fi
done
}
fix_audit_watch_rule "auditctl" "/etc/issue" "wa" "audit_rules_networkconfig_modification"
fix_audit_watch_rule "augenrules" "/etc/issue" "wa" "audit_rules_networkconfig_modification"
# Function to fix audit file system object watch rule for given path:
# * if rule exists, also verifies the -w bits match the requirements
# * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
# audit rules file, depending on the tool which was used to load audit rules
#
# Expects four arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules'
# * path value of -w audit rule's argument
# * required access bits value of -p audit rule's argument
# * key value of -k audit rule's argument
#
# Example call:
#
# fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
#
function fix_audit_watch_rule {
# Load function arguments into local variables
local tool="$1"
local path="$2"
local required_access_bits="$3"
local key="$4"
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
declare -a files_to_inspect
# Check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
exit 1
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
elif [ "$tool" == 'augenrules' ]
then
# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
# Get pair -- filepath : matching_row into @matches array
IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
# Reset IFS back to default
unset IFS
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
files_to_inspect="/etc/audit/rules.d/$key.rules"
# If the $key.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Escape slashes in path for use in sed pattern below
local esc_path=${path//$'/'/$'\/'}
# Define BRE whitespace class shortcut
local sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "$required_access_bits" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
fi
done
}
fix_audit_watch_rule "auditctl" "/etc/issue.net" "wa" "audit_rules_networkconfig_modification"
fix_audit_watch_rule "augenrules" "/etc/issue.net" "wa" "audit_rules_networkconfig_modification"
# Function to fix audit file system object watch rule for given path:
# * if rule exists, also verifies the -w bits match the requirements
# * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
# audit rules file, depending on the tool which was used to load audit rules
#
# Expects four arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules'
# * path value of -w audit rule's argument
# * required access bits value of -p audit rule's argument
# * key value of -k audit rule's argument
#
# Example call:
#
# fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
#
function fix_audit_watch_rule {
# Load function arguments into local variables
local tool="$1"
local path="$2"
local required_access_bits="$3"
local key="$4"
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
declare -a files_to_inspect
# Check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
exit 1
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
elif [ "$tool" == 'augenrules' ]
then
# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
# Get pair -- filepath : matching_row into @matches array
IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
# Reset IFS back to default
unset IFS
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
files_to_inspect="/etc/audit/rules.d/$key.rules"
# If the $key.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Escape slashes in path for use in sed pattern below
local esc_path=${path//$'/'/$'\/'}
# Define BRE whitespace class shortcut
local sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "$required_access_bits" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
fi
done
}
fix_audit_watch_rule "auditctl" "/etc/hosts" "wa" "audit_rules_networkconfig_modification"
fix_audit_watch_rule "augenrules" "/etc/hosts" "wa" "audit_rules_networkconfig_modification"
# Function to fix audit file system object watch rule for given path:
# * if rule exists, also verifies the -w bits match the requirements
# * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
# audit rules file, depending on the tool which was used to load audit rules
#
# Expects four arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules'
# * path value of -w audit rule's argument
# * required access bits value of -p audit rule's argument
# * key value of -k audit rule's argument
#
# Example call:
#
# fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
#
function fix_audit_watch_rule {
# Load function arguments into local variables
local tool="$1"
local path="$2"
local required_access_bits="$3"
local key="$4"
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
declare -a files_to_inspect
# Check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
exit 1
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
elif [ "$tool" == 'augenrules' ]
then
# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
# Get pair -- filepath : matching_row into @matches array
IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
# Reset IFS back to default
unset IFS
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
files_to_inspect="/etc/audit/rules.d/$key.rules"
# If the $key.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Escape slashes in path for use in sed pattern below
local esc_path=${path//$'/'/$'\/'}
# Define BRE whitespace class shortcut
local sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "$required_access_bits" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
fi
done
}
fix_audit_watch_rule "auditctl" "/etc/sysconfig/network" "wa" "audit_rules_networkconfig_modification"
fix_audit_watch_rule "augenrules" "/etc/sysconfig/network" "wa" "audit_rules_networkconfig_modification"
|
Ensure auditd Collects Information on Exporting to Media (successful)
[ref]ruleAt a minimum, the audit system should collect media exportation
events for all users and root. If the auditd daemon is configured to
use the augenrules program to read audit rules during daemon startup
(the default), add the following line to a file with suffix .rules in
the directory /etc/audit/rules.d , setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S mount -F auid>=1000 -F auid!=4294967295 -F key=export
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file, setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S mount -F auid>=1000 -F auid!=4294967295 -F key=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. References:
SV-86795r5_rule, 5.2.13, 5.4.1.1, 3.1.7, CCI-000135, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-3(1), AU-12(a), AU-12(c), IR-5, Req-10.2.7, SRG-OS-000042-GPOS-00020, SRG-OS-000392-GPOS-00172 Remediation Shell script: (show)
# Perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ $(getconf LONG_BIT) = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
PATTERN="-a always,exit -F arch=$ARCH -S .* -F auid>=1000 -F auid!=4294967295 -k *"
GROUP="mount"
FULL_RULE="-a always,exit -F arch=$ARCH -S mount -F auid>=1000 -F auid!=4294967295 -k export"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
# https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects five arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules
# * audit rules' pattern audit rule skeleton for same syscall
# * syscall group greatest common string this rule shares
# with other rules from the same group
# * architecture architecture this rule is intended for
# * full form of new rule to add expected full form of audit rule as to be
# added into audit.rules file
#
# Note: The 2-th up to 4-th arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (5-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
# See e.g. 'audit_rules_file_deletion_events.sh' remediation script
#
function fix_audit_syscall_rule {
# Load function arguments into local variables
local tool="$1"
local pattern="$2"
local group="$3"
local arch="$4"
local full_rule="$5"
# Check sanity of the input
if [ $# -ne "5" ]
then
echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
#
declare -a files_to_inspect
retval=0
# First check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
return 1
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
# If audit tool is 'augenrules', then check if the audit rule is defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
# If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
elif [ "$tool" == 'augenrules' ]
then
# Extract audit $key from audit rule so we can use it later
key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
# Check if particular audit rule is already defined
IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
for match in "${matches[@]}"
do
files_to_inspect=("${files_to_inspect[@]}" "${match}")
done
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
files_to_inspect="/etc/audit/rules.d/$key.rules"
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
#
# Indicator that we want to append $full_rule into $audit_file by default
local append_expected_rule=0
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d" "$audit_file"))
if [ $? -ne 0 ]
then
retval=1
fi
# Reset IFS back to default
unset IFS
# Process rules found case-by-case
for rule in "${existing_rules[@]}"
do
# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
if [ "${rule}" != "${full_rule}" ]
then
# If so, isolate just '(-S \w)+' substring of that rule
rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
# Check if list of '-S syscall' arguments of that rule is subset
# of '-S syscall' list of expected $full_rule
if grep -q -- "$rule_syscalls" <<< "$full_rule"
then
# Rule is covered (i.e. the list of -S syscalls for this rule is
# subset of -S syscalls of $full_rule => existing rule can be deleted
# Thus delete the rule from audit.rules & our array
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
existing_rules=("${existing_rules[@]//$rule/}")
else
# Rule isn't covered by $full_rule - it besides -S syscall arguments
# for this group contains also -S syscall arguments for other syscall
# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
# since 'lchown' & 'fchownat' share 'chown' substring
# Therefore:
# * 1) delete the original rule from audit.rules
# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
# * 2) delete the -S syscall arguments for this syscall group, but
# keep those not belonging to this syscall group
# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
# * 3) append the modified (filtered) rule again into audit.rules
# if the same rule not already present
#
# 1) Delete the original rule
sed -i -e "\;${rule};d" "$audit_file"
if [ $? -ne 0 ]
then
retval=1
fi
# 2) Delete syscalls for this group, but keep those from other groups
# Convert current rule syscall's string into array splitting by '-S' delimiter
IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
# Reset IFS back to default
unset IFS
# Declare new empty string to hold '-S syscall' arguments from other groups
new_syscalls_for_rule=''
# Walk through existing '-S syscall' arguments
for syscall_arg in "${rule_syscalls_as_array[@]}"
do
# Skip empty $syscall_arg values
if [ "$syscall_arg" == '' ]
then
continue
fi
# If the '-S syscall' doesn't belong to current group add it to the new list
# (together with adding '-S' delimiter back for each of such item found)
if grep -q -v -- "$group" <<< "$syscall_arg"
then
new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
fi
done
# Replace original '-S syscall' list with the new one for this rule
updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
# Squeeze repeated whitespace characters in rule definition (if any) into one
updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
# 3) Append the modified / filtered rule again into audit.rules
# (but only in case it's not present yet to prevent duplicate definitions)
if ! grep -q -- "$updated_rule" "$audit_file"
then
echo "$updated_rule" >> "$audit_file"
fi
fi
else
# $audit_file already contains the expected rule form for this
# architecture & key => don't insert it second time
append_expected_rule=1
fi
done
# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in $audit_file yet
if [[ ${append_expected_rule} -eq "0" ]]
then
echo "$full_rule" >> "$audit_file"
fi
done
return $retval
}
fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
done
|
Record Events that Modify User/Group Information - /etc/security/opasswd
[ref]ruleIf the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following lines to a file with suffix .rules in the
directory /etc/audit/rules.d , in order to capture events that modify
account changes:
-w /etc/security/opasswd -p wa -k audit_rules_usergroup_modification
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file, in order to capture events that modify
account changes:
-w /etc/security/opasswd -p wa -k audit_rules_usergroup_modification Rationale:In addition to auditing new user and group accounts, these watches
will alert the system administrator(s) to any modifications. Any unexpected
users, groups, or modifications should be investigated for legitimacy. References:
FAU_GEN.1.1.c, SV-87825r4_rule, 5.2.5, 5.4.1.1, 3.1.7, CCI-000018, CCI-000172, CCI-001403, CCI-002130, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-2(4), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.5, SRG-OS-000004-GPOS-00004 Remediation Shell script: (show)
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix audit file system object watch rule for given path:
# * if rule exists, also verifies the -w bits match the requirements
# * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
# audit rules file, depending on the tool which was used to load audit rules
#
# Expects four arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules'
# * path value of -w audit rule's argument
# * required access bits value of -p audit rule's argument
# * key value of -k audit rule's argument
#
# Example call:
#
# fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
#
function fix_audit_watch_rule {
# Load function arguments into local variables
local tool="$1"
local path="$2"
local required_access_bits="$3"
local key="$4"
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
declare -a files_to_inspect
# Check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
exit 1
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
elif [ "$tool" == 'augenrules' ]
then
# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
# Get pair -- filepath : matching_row into @matches array
IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
# Reset IFS back to default
unset IFS
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
files_to_inspect="/etc/audit/rules.d/$key.rules"
# If the $key.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Escape slashes in path for use in sed pattern below
local esc_path=${path//$'/'/$'\/'}
# Define BRE whitespace class shortcut
local sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "$required_access_bits" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
fi
done
}
fix_audit_watch_rule "auditctl" "/etc/security/opasswd" "wa" "audit_rules_usergroup_modification"
fix_audit_watch_rule "augenrules" "/etc/security/opasswd" "wa" "audit_rules_usergroup_modification"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
#
# What architecture are we on?
#
- name: Set architecture for audit opasswd tasks
set_fact:
audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"
#
# Inserts/replaces the rule in /etc/audit/rules.d
#
- name: Search /etc/audit/rules.d for other user/group modification audit rules
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "-k audit_rules_usergroup_modification$"
patterns: "*.rules"
register: find_opasswd
- name: If existing user/group modification ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_opasswd.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_opasswd.files | map(attribute='path') | list | first }}"
when: find_opasswd.matched > 0
- name: Inserts/replaces the opasswd rule in rules.d when on x86
lineinfile:
path: "{{ all_files[0] }}"
line: "-w /etc/security/opasswd -p wa -k audit_rules_usergroup_modification"
create: yes
tags:
- audit_rules_usergroup_modification_opasswd
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80430-2
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030874
#
# Inserts/replaces the rule in /etc/audit/audit.rules
#
- name: Inserts/replaces the opasswd rule in /etc/audit/audit.rules
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
with_items:
- "-w /etc/security/opasswd -p wa -k audit_rules_usergroup_modification"
tags:
- audit_rules_usergroup_modification_opasswd
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80430-2
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030874
|
System Audit Logs Must Have Mode 0640 or Less Permissive
[ref]ruleIf 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. Remediation Shell script: (show)
if `grep -q ^log_group /etc/audit/auditd.conf` ; then
GROUP=$(awk -F "=" '/log_group/ {print $2}' /etc/audit/auditd.conf | tr -d ' ')
if ! [ "${GROUP}" == 'root' ] ; then
chmod 0640 /var/log/audit/audit.log
chmod 0440 /var/log/audit/audit.log.*
else
chmod 0600 /var/log/audit/audit.log
chmod 0400 /var/log/audit/audit.log.*
fi
chmod 0640 /etc/audit/audit*
chmod 0640 /etc/audit/rules.d/*
else
chmod 0600 /var/log/audit/audit.log
chmod 0400 /var/log/audit/audit.log.*
chmod 0640 /etc/audit/audit*
chmod 0640 /etc/audit/rules.d/*
fi
|
System Audit Logs Must Be Owned By Root
[ref]ruleAll audit logs must be owned by root user and group. By default, the path for audit log is /var/log/audit/ .
To properly set the owner of /var/log/audit , run the command:
$ sudo chown root /var/log/audit
To properly set the owner of /var/log/audit/* , run the command:
$ sudo chown root /var/log/audit/* Rationale:Unauthorized disclosure of audit records can reveal system and configuration data to
attackers, thus compromising its confidentiality. Remediation Shell script: (show)
if `grep -q ^log_group /etc/audit/auditd.conf` ; then
GROUP=$(awk -F "=" '/log_group/ {print $2}' /etc/audit/auditd.conf | tr -d ' ')
if ! [ "${GROUP}" == 'root' ] ; then
chown root.${GROUP} /var/log/audit
chown root.${GROUP} /var/log/audit/audit.log*
else
chown root.root /var/log/audit
chown root.root /var/log/audit/audit.log*
fi
else
chown root.root /var/log/audit
chown root.root /var/log/audit/audit.log*
fi
|
Make the auditd Configuration Immutable
[ref]ruleIf the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following line to a file with suffix .rules in the
directory /etc/audit/rules.d in order to make the auditd configuration
immutable:
-e 2
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file in order to make the auditd configuration
immutable:
-e 2
With this setting, a reboot will be required to change any audit rules.Rationale:Making the audit configuration immutable prevents accidental as
well as malicious modification of the audit rules, although it may be
problematic if legitimate changes are needed during system
operation References:
4.1.18, 5.4.1.1, 3.3.1, 3.4.3, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.310(a)(2)(iv), 164.312(d), 164.310(d)(2)(iii), 164.312(b), 164.312(e), AC-6, AU-1(b), AU-2(a), AU-2(c), AU-2(d), IR-5, Req-10.5.2 Remediation Shell script: (show)
# Traverse all of:
#
# /etc/audit/audit.rules, (for auditctl case)
# /etc/audit/rules.d/*.rules (for augenrules case)
#
# files to check if '-e .*' setting is present in that '*.rules' file already.
# If found, delete such occurrence since auditctl(8) manual page instructs the
# '-e 2' rule should be placed as the last rule in the configuration
find /etc/audit /etc/audit/rules.d -maxdepth 1 -type f -name *.rules -exec sed -i '/-e[[:space:]]\+.*/d' {} ';'
# Append '-e 2' requirement at the end of both:
# * /etc/audit/audit.rules file (for auditctl case)
# * /etc/audit/rules.d/immutable.rules (for augenrules case)
for AUDIT_FILE in "/etc/audit/audit.rules" "/etc/audit/rules.d/immutable.rules"
do
echo '' >> $AUDIT_FILE
echo '# Set the audit.rules configuration immutable per security requirements' >> $AUDIT_FILE
echo '# Reboot is required to change audit rules once this setting is applied' >> $AUDIT_FILE
echo '-e 2' >> $AUDIT_FILE
done
|
Record Events that Modify User/Group Information - /etc/passwd
[ref]ruleIf the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following lines to a file with suffix .rules in the
directory /etc/audit/rules.d , in order to capture events that modify
account changes:
-w /etc/passwd -p wa -k audit_rules_usergroup_modification
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file, in order to capture events that modify
account changes:
-w /etc/passwd -p wa -k audit_rules_usergroup_modification Rationale:In addition to auditing new user and group accounts, these watches
will alert the system administrator(s) to any modifications. Any unexpected
users, groups, or modifications should be investigated for legitimacy. References:
FAU_GEN.1.1.c, SV-86821r4_rule, 5.2.5, 5.4.1.1, 3.1.7, CCI-000018, CCI-000172, CCI-001403, CCI-002130, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-2(4), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.5, SRG-OS-000004-GPOS-00004, SRG-OS-000239-GPOS-00089, SRG-OS-000240-GPOS-00090, SRG-OS-000241-GPOS-00091, SRG-OS-000303-GPOS-00120, SRG-OS-000476-GPOS-00221 Remediation Shell script: (show)
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix audit file system object watch rule for given path:
# * if rule exists, also verifies the -w bits match the requirements
# * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
# audit rules file, depending on the tool which was used to load audit rules
#
# Expects four arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules'
# * path value of -w audit rule's argument
# * required access bits value of -p audit rule's argument
# * key value of -k audit rule's argument
#
# Example call:
#
# fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
#
function fix_audit_watch_rule {
# Load function arguments into local variables
local tool="$1"
local path="$2"
local required_access_bits="$3"
local key="$4"
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
declare -a files_to_inspect
# Check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
exit 1
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
elif [ "$tool" == 'augenrules' ]
then
# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
# Get pair -- filepath : matching_row into @matches array
IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
# Reset IFS back to default
unset IFS
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
files_to_inspect="/etc/audit/rules.d/$key.rules"
# If the $key.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Escape slashes in path for use in sed pattern below
local esc_path=${path//$'/'/$'\/'}
# Define BRE whitespace class shortcut
local sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "$required_access_bits" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
fi
done
}
fix_audit_watch_rule "auditctl" "/etc/passwd" "wa" "audit_rules_usergroup_modification"
fix_audit_watch_rule "augenrules" "/etc/passwd" "wa" "audit_rules_usergroup_modification"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
#
# What architecture are we on?
#
- name: Set architecture for audit passwd tasks
set_fact:
audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"
#
# Inserts/replaces the rule in /etc/audit/rules.d
#
- name: Search /etc/audit/rules.d for other user/group modification audit rules
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "-k audit_rules_usergroup_modification$"
patterns: "*.rules"
register: find_passwd
- name: If existing user/group modification ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_passwd.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_passwd.files | map(attribute='path') | list | first }}"
when: find_passwd.matched > 0
- name: Inserts/replaces the passwd rule in rules.d when on x86
lineinfile:
path: "{{ all_files[0] }}"
line: "-w /etc/passwd -p wa -k audit_rules_usergroup_modification"
create: yes
tags:
- audit_rules_usergroup_modification_passwd
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80435-1
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030870
#
# Inserts/replaces the rule in /etc/audit/audit.rules
#
- name: Inserts/replaces the passwd rule in /etc/audit/audit.rules
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
with_items:
- "-w /etc/passwd -p wa -k audit_rules_usergroup_modification"
tags:
- audit_rules_usergroup_modification_passwd
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80435-1
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030870
|
Record Events that Modify the System's Mandatory Access Controls
[ref]ruleIf the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following line to a file with suffix .rules in the
directory /etc/audit/rules.d :
-w /etc/selinux/ -p wa -k MAC-policy
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-w /etc/selinux/ -p wa -k MAC-policy Rationale:The system's mandatory access policy (SELinux) should not be
arbitrarily changed by anything other than administrator action. All changes to
MAC policy should be audited. References:
FAU_GEN.1.1.c, 5.2.7, 5.4.1.1, 3.1.8, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5 Remediation Shell script: (show)
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix audit file system object watch rule for given path:
# * if rule exists, also verifies the -w bits match the requirements
# * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
# audit rules file, depending on the tool which was used to load audit rules
#
# Expects four arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules'
# * path value of -w audit rule's argument
# * required access bits value of -p audit rule's argument
# * key value of -k audit rule's argument
#
# Example call:
#
# fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
#
function fix_audit_watch_rule {
# Load function arguments into local variables
local tool="$1"
local path="$2"
local required_access_bits="$3"
local key="$4"
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
declare -a files_to_inspect
# Check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
exit 1
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
elif [ "$tool" == 'augenrules' ]
then
# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
# Get pair -- filepath : matching_row into @matches array
IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
# Reset IFS back to default
unset IFS
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
files_to_inspect="/etc/audit/rules.d/$key.rules"
# If the $key.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Escape slashes in path for use in sed pattern below
local esc_path=${path//$'/'/$'\/'}
# Define BRE whitespace class shortcut
local sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "$required_access_bits" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
fi
done
}
fix_audit_watch_rule "auditctl" "/etc/selinux/" "wa" "MAC-policy"
fix_audit_watch_rule "augenrules" "/etc/selinux/" "wa" "MAC-policy"
|
Record Events that Modify User/Group Information - /etc/group
[ref]ruleIf the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following lines to a file with suffix .rules in the
directory /etc/audit/rules.d , in order to capture events that modify
account changes:
-w /etc/group -p wa -k audit_rules_usergroup_modification
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file, in order to capture events that modify
account changes:
-w /etc/group -p wa -k audit_rules_usergroup_modification Rationale:In addition to auditing new user and group accounts, these watches
will alert the system administrator(s) to any modifications. Any unexpected
users, groups, or modifications should be investigated for legitimacy. References:
FAU_GEN.1.1.c, SV-87817r2_rule, 5.2.5, 5.4.1.1, 3.1.7, CCI-000018, CCI-000172, CCI-001403, CCI-002130, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-2(4), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.5, SRG-OS-000004-GPOS-00004 Remediation Shell script: (show)
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function to fix audit file system object watch rule for given path:
# * if rule exists, also verifies the -w bits match the requirements
# * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
# audit rules file, depending on the tool which was used to load audit rules
#
# Expects four arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules'
# * path value of -w audit rule's argument
# * required access bits value of -p audit rule's argument
# * key value of -k audit rule's argument
#
# Example call:
#
# fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
#
function fix_audit_watch_rule {
# Load function arguments into local variables
local tool="$1"
local path="$2"
local required_access_bits="$3"
local key="$4"
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
declare -a files_to_inspect
# Check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
exit 1
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
elif [ "$tool" == 'augenrules' ]
then
# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
# Get pair -- filepath : matching_row into @matches array
IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
# Reset IFS back to default
unset IFS
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
files_to_inspect="/etc/audit/rules.d/$key.rules"
# If the $key.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Escape slashes in path for use in sed pattern below
local esc_path=${path//$'/'/$'\/'}
# Define BRE whitespace class shortcut
local sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "$required_access_bits" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
fi
done
}
fix_audit_watch_rule "auditctl" "/etc/group" "wa" "audit_rules_usergroup_modification"
fix_audit_watch_rule "augenrules" "/etc/group" "wa" "audit_rules_usergroup_modification"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
#
# What architecture are we on?
#
- name: Set architecture for audit group tasks
set_fact:
audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"
#
# Inserts/replaces the rule in /etc/audit/rules.d
#
- name: Search /etc/audit/rules.d for other user/group modification audit rules
find:
paths: "/etc/audit/rules.d"
recurse: no
contains: "-k audit_rules_usergroup_modification$"
patterns: "*.rules"
register: find_group
- name: If existing user/group modification ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/privileged.rules
when: find_group.matched == 0
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- "{{ find_group.files | map(attribute='path') | list | first }}"
when: find_group.matched > 0
- name: Inserts/replaces the group rule in rules.d when on x86
lineinfile:
path: "{{ all_files[0] }}"
line: "-w /etc/group -p wa -k audit_rules_usergroup_modification"
create: yes
tags:
- audit_rules_usergroup_modification_group
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80433-6
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030871
#
# Inserts/replaces the rule in /etc/audit/audit.rules
#
- name: Inserts/replaces the group rule in /etc/audit/audit.rules
lineinfile:
line: "{{ item }}"
state: present
dest: /etc/audit/audit.rules
with_items:
- "-w /etc/group -p wa -k audit_rules_usergroup_modification"
tags:
- audit_rules_usergroup_modification_group
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-80433-6
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-17(7)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-IR-5
- NIST-800-171-3.1.7
- PCI-DSS-Req-10.2.5
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030871
|
Enable Auditing for Processes Which Start Prior to the Audit Daemon
[ref]ruleTo ensure all processes can be audited, even those which start
prior to the audit daemon, add the argument audit=1 to the default
GRUB 2 command line for the Linux operating system in
/etc/default/grub , in the manner below:
GRUB_CMDLINE_LINUX="crashkernel=auto rd.lvm.lv=VolGroup/LogVol06 rd.lvm.lv=VolGroup/lv_swap rhgb quiet rd.shell=0 audit=1" Rationale:Each process on the system carries an "auditable" flag which indicates whether
its activities can be audited. Although auditd takes care of enabling
this for all processes which launch after it does, adding the kernel argument
ensures it is set for every process during boot. References:
4.1.3, 5.4.1.1, 3.3.1, CCI-001464, CCI-000130, 164.308(a)(1)(ii)(D), 164.308(a)(5)(ii)(C), 164.310(a)(2)(iv), 164.310(d)(2)(iii), 164.312(b), AC-17(1), AU-14(1), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-10, IR-5, Req-10.3 Remediation Shell script: (show)
# Correct the form of default kernel command line in /etc/default/grub
grep -q ^GRUB_CMDLINE_LINUX=\".*audit=0.*\" /etc/default/grub && \
sed -i "s/audit=[^[:space:]\+]/audit=1/g" /etc/default/grub
if ! [ $? -eq 0 ]; then
sed -i "s/\(GRUB_CMDLINE_LINUX=\)\"\(.*\)\"/\1\"\2 audit=1\"/" /etc/default/grub
fi
# Correct the form of kernel command line for each installed kernel
# in the bootloader
/sbin/grubby --update-kernel=ALL --args="audit=1"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: "Enable Auditing for Processes Which Start Prior to the Audit Daemon"
shell: /sbin/grubby --update-kernel=ALL --args="audit=1"
tags:
- bootloader_audit_argument
- medium_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27212-0
- NIST-800-53-AC-17(1)
- NIST-800-53-AU-14(1)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-2(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-10
- NIST-800-53-IR-5
- NIST-800-171-3.3.1
- PCI-DSS-Req-10.3
- CJIS-5.4.1.1
|
Enable auditd Service
[ref]ruleThe auditd service is an essential userspace component of
the Linux Auditing System, as it is responsible for writing audit records to
disk.
The auditd service can be enabled with the following command:
$ sudo systemctl enable auditd.service Rationale:Without establishing what type of events occurred, it would be difficult
to establish, correlate, and investigate the events leading up to an outage or attack.
Ensuring the auditd service is active ensures audit records
generated by the kernel are appropriately recorded.
Additionally, a properly configured audit subsystem ensures that actions of
individual system users can be uniquely traced to those users so they
can be held accountable for their actions. References:
SV-86703r2_rule, 4.1.2, 5.4.1.1, 3.3.1, 3.3.2, 3.3.6, CCI-000126, CCI-000131, 164.308(a)(1)(ii)(D), 164.308(a)(5)(ii)(C), 164.310(a)(2)(iv), 164.310(d)(2)(iii), 164.312(b), AU-3, AC-17(1), AU-1(b), AU-10, AU-12(a), AU-12(c), AU-14(1), IR-5, Req-10, SRG-OS-000038-GPOS-00016, SRG-OS-000039-GPOS-00017, SRG-OS-000042-GPOS-00021, SRG-OS-000254-GPOS-00095, SRG-OS-000255-GPOS-00096 Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
# Function to enable/disable and start/stop services on RHEL and Fedora systems.
#
# Example Call(s):
#
# service_command enable bluetooth
# service_command disable bluetooth.service
#
# Using xinetd:
# service_command disable rsh.socket xinetd=rsh
#
function service_command {
# Load function arguments into local variables
local service_state=$1
local service=$2
local xinetd=$(echo $3 | cut -d'=' -f2)
# Check sanity of the input
if [ $# -lt "2" ]
then
echo "Usage: service_command 'enable/disable' 'service_name.service'"
echo
echo "To enable or disable xinetd services add \'xinetd=service_name\'"
echo "as the last argument"
echo "Aborting."
exit 1
fi
# If systemctl is installed, use systemctl command; otherwise, use the service/chkconfig commands
if [ -f "/usr/bin/systemctl" ] ; then
service_util="/usr/bin/systemctl"
else
service_util="/sbin/service"
chkconfig_util="/sbin/chkconfig"
fi
# If disable is not specified in arg1, set variables to enable services.
# Otherwise, variables are to be set to disable services.
if [ "$service_state" != 'disable' ] ; then
service_state="enable"
service_operation="start"
chkconfig_state="on"
else
service_state="disable"
service_operation="stop"
chkconfig_state="off"
fi
# If chkconfig_util is not empty, use chkconfig/service commands.
if [ "x$chkconfig_util" != x ] ; then
$service_util $service $service_operation
$chkconfig_util --level 0123456 $service $chkconfig_state
else
$service_util $service_operation $service
$service_util $service_state $service
# The service may not be running because it has been started and failed,
# so let's reset the state so OVAL checks pass.
# Service should be 'inactive', not 'failed' after reboot though.
$service_util reset-failed $service
fi
# Test if local variable xinetd is empty using non-bashism.
# If empty, then xinetd is not being used.
if [ "x$xinetd" != x ] ; then
grep -qi disable /etc/xinetd.d/$xinetd && \
if [ "$service_operation" = 'disable' ] ; then
sed -i "s/disable.*/disable = no/gI" /etc/xinetd.d/$xinetd
else
sed -i "s/disable.*/disable = yes/gI" /etc/xinetd.d/$xinetd
fi
fi
}
service_command enable auditd
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: Enable service auditd
service:
name="{{item}}"
enabled="yes"
state="started"
with_items:
- auditd
tags:
- service_auditd_enabled
- high_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-27407-6
- NIST-800-53-AU-3
- NIST-800-53-AC-17(1)
- NIST-800-53-AU-1(b)
- NIST-800-53-AU-10
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-14(1)
- NIST-800-53-IR-5
- NIST-800-171-3.3.1
- NIST-800-171-3.3.2
- NIST-800-171-3.3.6
- PCI-DSS-Req-10
- CJIS-5.4.1.1
- DISA-STIG-RHEL-07-030000
|
File Permissions and Masks
[ref]groupTraditional Unix security relies heavily on file and
directory permissions to prevent unauthorized users from reading or
modifying files to which they should not have access.
Several of the commands in this section search filesystems
for files or directories with certain characteristics, and are
intended to be run on every local partition on a given system.
When the variable PART appears in one of the commands below,
it means that the command is intended to be run repeatedly, with the
name of each local partition substituted for PART in turn.
The following command prints a list of all xfs partitions on the local
system, which is the default filesystem for Red Hat Enterprise Linux
7 installations:
$ mount -t xfs | awk '{print $3}'
For any systems that use a different
local filesystem type, modify this command as appropriate. |
contains 19 rules |
Verify Permissions on Important Files and
Directories
[ref]groupPermissions for many files on a system must be set
restrictively to ensure sensitive information is properly protected.
This section discusses important
permission restrictions which can be verified
to ensure that no harmful discrepancies have
arisen. |
contains 3 rules |
Ensure All Files Are Owned by a Group
[ref]ruleIf any files are not owned by a group, then the
cause of their lack of group-ownership should be investigated.
Following this, the files should be deleted or assigned to an
appropriate group. Rationale:Unowned files do not directly imply a security problem, but they are generally
a sign that something is amiss. They may
be caused by an intruder, by incorrect software installation or
draft software removal, or by failure to remove all files belonging
to a deleted account. The files should be repaired so they
will not cause problems when accounts are created in the future,
and the cause should be discovered and addressed. |
Ensure All World-Writable Directories Are Owned by a System Account
[ref]ruleAll directories in local partitions which are
world-writable should be owned by root or another
system account. If any world-writable directories are not
owned by a system account, this should be investigated.
Following this, the files should be deleted or assigned to an
appropriate group. Rationale:Allowing a user account to own a world-writable directory is
undesirable because it allows the owner of that directory to remove
or replace any files that may be placed in the directory by other
users. |
Ensure All Files Are Owned by a User
[ref]ruleIf any files are not owned by a user, then the
cause of their lack of ownership should be investigated.
Following this, the files should be deleted or assigned to an
appropriate user. Rationale:Unowned files do not directly imply a security problem, but they are generally
a sign that something is amiss. They may
be caused by an intruder, by incorrect software installation or
draft software removal, or by failure to remove all files belonging
to a deleted account. The files should be repaired so they
will not cause problems when accounts are created in the future,
and the cause should be discovered and addressed. |
Restrict Dynamic Mounting and Unmounting of
Filesystems
[ref]groupLinux includes a number of facilities for the automated addition
and removal of filesystems on a running system. These facilities may be
necessary in many environments, but this capability also carries some risk -- whether direct
risk from allowing users to introduce arbitrary filesystems,
or risk that software flaws in the automated mount facility itself could
allow an attacker to compromise the system.
This command can be used to list the types of filesystems that are
available to the currently executing kernel:
$ find /lib/modules/`uname -r`/kernel/fs -type f -name '*.ko'
If these filesystems are not required then they can be explicitly disabled
in a configuratio file in /etc/modprobe.d . |
contains 8 rules |
Disable Modprobe Loading of USB Storage Driver
[ref]ruleTo prevent USB storage devices from being used, configure the kernel module loading system
to prevent automatic loading of the USB storage driver.
To configure the system to prevent the usb-storage
kernel module from being loaded, add the following line to a file in the directory /etc/modprobe.d :
install usb-storage /bin/true
This will prevent the modprobe program from loading the usb-storage
module, but will not prevent an administrator (or another program) from using the
insmod program to load the module manually.Rationale:USB storage devices such as thumb drives can be used to introduce
malicious software. References:
SV-86607r2_rule, 3.1.21, CCI-000366, CCI-000778, CCI-001958, 164.308(a)(3)(i), 164.308(a)(3)(ii)(A), 164.310(d)(1), 164.310(d)(2), 164.312(a)(1), 164.312(a)(2)(iv), 164.312(b), AC-19(a), AC-19(d), AC-19(e), IA-3, SRG-OS-000114-GPOS-00059, SRG-OS-000378-GPOS-0016, SRG-OS-000480-GPOS-00227 Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
if grep --silent "^install usb-storage" /etc/modprobe.d/usb-storage.conf ; then
sed -i 's/^install usb-storage.*/install usb-storage /bin/true/g' /etc/modprobe.d/usb-storage.conf
else
echo -e "\n# Disable per security requirements" >> /etc/modprobe.d/usb-storage.conf
echo "install usb-storage /bin/true" >> /etc/modprobe.d/usb-storage.conf
fi
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: Ensure kernel module 'usb-storage' is disabled
lineinfile:
create=yes
dest="/etc/modprobe.d/{{item}}.conf"
regexp="{{item}}"
line="install {{item}} /bin/true"
with_items:
- usb-storage
tags:
- kernel_module_usb-storage_disabled
- medium_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-27277-3
- NIST-800-53-AC-19(a)
- NIST-800-53-AC-19(d)
- NIST-800-53-AC-19(e)
- NIST-800-53-IA-3
- NIST-800-171-3.1.21
- DISA-STIG-RHEL-07-020100
|
Disable Mounting of freevxfs
[ref]rule
To configure the system to prevent the freevxfs
kernel module from being loaded, add the following line to a file in the directory /etc/modprobe.d :
install freevxfs /bin/true
This effectively prevents usage of this uncommon filesystem.Rationale:Linux kernel modules which implement filesystems that are not needed by the
local system should be disabled. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
if grep --silent "^install freevxfs" /etc/modprobe.d/freevxfs.conf ; then
sed -i 's/^install freevxfs.*/install freevxfs /bin/true/g' /etc/modprobe.d/freevxfs.conf
else
echo -e "\n# Disable per security requirements" >> /etc/modprobe.d/freevxfs.conf
echo "install freevxfs /bin/true" >> /etc/modprobe.d/freevxfs.conf
fi
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: Ensure kernel module 'freevxfs' is disabled
lineinfile:
create=yes
dest="/etc/modprobe.d/{{item}}.conf"
regexp="{{item}}"
line="install {{item}} /bin/true"
with_items:
- freevxfs
tags:
- kernel_module_freevxfs_disabled
- low_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-80138-1
- NIST-800-53-CM-7
- NIST-800-171-3.4.6
|
Disable Mounting of cramfs
[ref]rule
To configure the system to prevent the cramfs
kernel module from being loaded, add the following line to a file in the directory /etc/modprobe.d :
install cramfs /bin/true
This effectively prevents usage of this uncommon filesystem.Rationale:Linux kernel modules which implement filesystems that are not needed by the
local system should be disabled. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
if grep --silent "^install cramfs" /etc/modprobe.d/cramfs.conf ; then
sed -i 's/^install cramfs.*/install cramfs /bin/true/g' /etc/modprobe.d/cramfs.conf
else
echo -e "\n# Disable per security requirements" >> /etc/modprobe.d/cramfs.conf
echo "install cramfs /bin/true" >> /etc/modprobe.d/cramfs.conf
fi
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: Ensure kernel module 'cramfs' is disabled
lineinfile:
create=yes
dest="/etc/modprobe.d/{{item}}.conf"
regexp="{{item}}"
line="install {{item}} /bin/true"
with_items:
- cramfs
tags:
- kernel_module_cramfs_disabled
- low_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-80137-3
- NIST-800-53-CM-7
- NIST-800-171-3.4.6
|
Disable Mounting of squashfs
[ref]rule
To configure the system to prevent the squashfs
kernel module from being loaded, add the following line to a file in the directory /etc/modprobe.d :
install squashfs /bin/true
This effectively prevents usage of this uncommon filesystem.Rationale:Linux kernel modules which implement filesystems that are not needed by the
local system should be disabled. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
if grep --silent "^install squashfs" /etc/modprobe.d/squashfs.conf ; then
sed -i 's/^install squashfs.*/install squashfs /bin/true/g' /etc/modprobe.d/squashfs.conf
else
echo -e "\n# Disable per security requirements" >> /etc/modprobe.d/squashfs.conf
echo "install squashfs /bin/true" >> /etc/modprobe.d/squashfs.conf
fi
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: Ensure kernel module 'squashfs' is disabled
lineinfile:
create=yes
dest="/etc/modprobe.d/{{item}}.conf"
regexp="{{item}}"
line="install {{item}} /bin/true"
with_items:
- squashfs
tags:
- kernel_module_squashfs_disabled
- low_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-80142-3
- NIST-800-53-CM-7
- NIST-800-171-3.4.6
|
Disable the Automounter
[ref]ruleThe autofs daemon mounts and unmounts filesystems, such as user
home directories shared via NFS, on demand. In addition, autofs can be used to handle
removable media, and the default configuration provides the cdrom device as /misc/cd .
However, this method of providing access to removable media is not common, so autofs
can almost always be disabled if NFS is not in use. Even if NFS is required, it may be
possible to configure filesystem mounts statically by editing /etc/fstab
rather than relying on the automounter.
The autofs service can be disabled with the following command:
$ sudo systemctl disable autofs.service Rationale:Disabling the automounter permits the administrator to
statically control filesystem mounting through /etc/fstab .
Additionally, automatically mounting filesystems permits easy introduction of
unknown devices, thereby facilitating malicious activity. References:
SV-86609r1_rule, 1.1.22, 3.4.6, CCI-000366, CCI-000778, CCI-001958, 164.308(a)(3)(i), 164.308(a)(3)(ii)(A), 164.310(d)(1), 164.310(d)(2), 164.312(a)(1), 164.312(a)(2)(iv), 164.312(b), AC-19(a), AC-19(d), AC-19(e), IA-3, SRG-OS-000114-GPOS-00059, SRG-OS-000378-GPOS-00163, SRG-OS-000480-GPOS-00227 Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
# Function to enable/disable and start/stop services on RHEL and Fedora systems.
#
# Example Call(s):
#
# service_command enable bluetooth
# service_command disable bluetooth.service
#
# Using xinetd:
# service_command disable rsh.socket xinetd=rsh
#
function service_command {
# Load function arguments into local variables
local service_state=$1
local service=$2
local xinetd=$(echo $3 | cut -d'=' -f2)
# Check sanity of the input
if [ $# -lt "2" ]
then
echo "Usage: service_command 'enable/disable' 'service_name.service'"
echo
echo "To enable or disable xinetd services add \'xinetd=service_name\'"
echo "as the last argument"
echo "Aborting."
exit 1
fi
# If systemctl is installed, use systemctl command; otherwise, use the service/chkconfig commands
if [ -f "/usr/bin/systemctl" ] ; then
service_util="/usr/bin/systemctl"
else
service_util="/sbin/service"
chkconfig_util="/sbin/chkconfig"
fi
# If disable is not specified in arg1, set variables to enable services.
# Otherwise, variables are to be set to disable services.
if [ "$service_state" != 'disable' ] ; then
service_state="enable"
service_operation="start"
chkconfig_state="on"
else
service_state="disable"
service_operation="stop"
chkconfig_state="off"
fi
# If chkconfig_util is not empty, use chkconfig/service commands.
if [ "x$chkconfig_util" != x ] ; then
$service_util $service $service_operation
$chkconfig_util --level 0123456 $service $chkconfig_state
else
$service_util $service_operation $service
$service_util $service_state $service
# The service may not be running because it has been started and failed,
# so let's reset the state so OVAL checks pass.
# Service should be 'inactive', not 'failed' after reboot though.
$service_util reset-failed $service
fi
# Test if local variable xinetd is empty using non-bashism.
# If empty, then xinetd is not being used.
if [ "x$xinetd" != x ] ; then
grep -qi disable /etc/xinetd.d/$xinetd && \
if [ "$service_operation" = 'disable' ] ; then
sed -i "s/disable.*/disable = no/gI" /etc/xinetd.d/$xinetd
else
sed -i "s/disable.*/disable = yes/gI" /etc/xinetd.d/$xinetd
fi
fi
}
service_command disable autofs
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
- name: Disable service autofs
service:
name="{{item}}"
enabled="no"
state="stopped"
register: service_result
failed_when: "service_result|failed and ('Could not find the requested service' not in service_result.msg)"
with_items:
- autofs
tags:
- service_autofs_disabled
- medium_severity
- disable_strategy
- low_complexity
- low_disruption
- CCE-27498-5
- NIST-800-53-AC-19(a)
- NIST-800-53-AC-19(d)
- NIST-800-53-AC-19(e)
- NIST-800-53-IA-3
- NIST-800-171-3.4.6
- DISA-STIG-RHEL-07-020110
|
Disable Mounting of jffs2
[ref]rule
To configure the system to prevent the jffs2
kernel module from being loaded, add the following line to a file in the directory /etc/modprobe.d :
install jffs2 /bin/true
This effectively prevents usage of this uncommon filesystem.Rationale:Linux kernel modules which implement filesystems that are not needed by the
local system should be disabled. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
if grep --silent "^install jffs2" /etc/modprobe.d/jffs2.conf ; then
sed -i 's/^install jffs2.*/install jffs2 /bin/true/g' /etc/modprobe.d/jffs2.conf
else
echo -e "\n# Disable per security requirements" >> /etc/modprobe.d/jffs2.conf
echo "install jffs2 /bin/true" >> /etc/modprobe.d/jffs2.conf
fi
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: Ensure kernel module 'jffs2' is disabled
lineinfile:
create=yes
dest="/etc/modprobe.d/{{item}}.conf"
regexp="{{item}}"
line="install {{item}} /bin/true"
with_items:
- jffs2
tags:
- kernel_module_jffs2_disabled
- low_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-80139-9
- NIST-800-53-CM-7
- NIST-800-171-3.4.6
|
Disable Mounting of hfs
[ref]rule
To configure the system to prevent the hfs
kernel module from being loaded, add the following line to a file in the directory /etc/modprobe.d :
install hfs /bin/true
This effectively prevents usage of this uncommon filesystem.Rationale:Linux kernel modules which implement filesystems that are not needed by the
local system should be disabled. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
if grep --silent "^install hfs" /etc/modprobe.d/hfs.conf ; then
sed -i 's/^install hfs.*/install hfs /bin/true/g' /etc/modprobe.d/hfs.conf
else
echo -e "\n# Disable per security requirements" >> /etc/modprobe.d/hfs.conf
echo "install hfs /bin/true" >> /etc/modprobe.d/hfs.conf
fi
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: Ensure kernel module 'hfs' is disabled
lineinfile:
create=yes
dest="/etc/modprobe.d/{{item}}.conf"
regexp="{{item}}"
line="install {{item}} /bin/true"
with_items:
- hfs
tags:
- kernel_module_hfs_disabled
- low_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-80140-7
- NIST-800-53-CM-7
- NIST-800-171-3.4.6
|
Disable Mounting of hfsplus
[ref]rule
To configure the system to prevent the hfsplus
kernel module from being loaded, add the following line to a file in the directory /etc/modprobe.d :
install hfsplus /bin/true
This effectively prevents usage of this uncommon filesystem.Rationale:Linux kernel modules which implement filesystems that are not needed by the
local system should be disabled. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
if grep --silent "^install hfsplus" /etc/modprobe.d/hfsplus.conf ; then
sed -i 's/^install hfsplus.*/install hfsplus /bin/true/g' /etc/modprobe.d/hfsplus.conf
else
echo -e "\n# Disable per security requirements" >> /etc/modprobe.d/hfsplus.conf
echo "install hfsplus /bin/true" >> /etc/modprobe.d/hfsplus.conf
fi
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: Ensure kernel module 'hfsplus' is disabled
lineinfile:
create=yes
dest="/etc/modprobe.d/{{item}}.conf"
regexp="{{item}}"
line="install {{item}} /bin/true"
with_items:
- hfsplus
tags:
- kernel_module_hfsplus_disabled
- low_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-80141-5
- NIST-800-53-CM-7
- NIST-800-171-3.4.6
|
Restrict Partition Mount Options
[ref]groupSystem partitions can be mounted with certain options
that limit what files on those partitions can do. These options
are set in the /etc/fstab configuration file, and can be
used to make certain types of malicious behavior more difficult. |
contains 3 rules |
Add nosuid Option to Removable Media Partitions
[ref]ruleThe nosuid mount option prevents set-user-identifier (SUID)
and set-group-identifier (SGID) permissions from taking effect. These permissions
allow users to execute binaries with the same permissions as the owner and group
of the file respectively. Users should not be allowed to introduce SUID and SGID
files into the system via partitions mounted from removeable media.
Add the nosuid option to the fourth column of
/etc/fstab for the line which controls mounting of
any removable media partitions.
Rationale:The presence of SUID and SGID executables should be tightly controlled. Allowing
users to introduce SUID or SGID binaries from partitions mounted off of
removable media would allow them to introduce their own highly-privileged programs. Remediation Shell script: (show)
var_removable_partition="(N/A)"
function include_mount_options_functions {
:
}
# $1: mount point
# $2: new mount point option
function ensure_mount_option_in_fstab {
local _mount_point="$1" _new_opt="$2" _mount_point_match_regexp="" _previous_mount_opts=""
_mount_point_match_regexp="$(get_mount_point_regexp "$_mount_point")"
if [ $(grep "$_mount_point_match_regexp" /etc/fstab | grep -c "$_new_opt" ) -eq 0 ]; then
_previous_mount_opts=$(grep "$_mount_point_match_regexp" /etc/fstab | awk '{print $4}')
sed -i "s|\(${_mount_point_match_regexp}.*${_previous_mount_opts}\)|\1,${_new_opt}|" /etc/fstab
fi
}
# $1: mount point
function get_mount_point_regexp {
printf "[[:space:]]%s[[:space:]]" "$1"
}
# $1: mount point
function assert_mount_point_in_fstab {
local _mount_point_match_regexp
_mount_point_match_regexp="$(get_mount_point_regexp "$1")"
grep "$_mount_point_match_regexp" -q /etc/fstab \
|| { echo "The mount point '$1' is not even in /etc/fstab, so we can't set up mount options" >&2; return 1; }
}
# $1: mount point
function remove_defaults_from_fstab_if_overriden {
local _mount_point_match_regexp
_mount_point_match_regexp="$(get_mount_point_regexp "$1")"
if [ $(grep "$_mount_point_match_regexp" /etc/fstab | grep -q "defaults,") -gt 0 ]
then
sed -i "s|\(${_mount_point_match_regexp}.*\)defaults,|\1|" /etc/fstab
fi
}
# $1: mount point
function ensure_partition_is_mounted {
local _mount_point="$1"
mkdir -p "$_mount_point" || return 1
if mountpoint -q "$_mount_point"; then
mount -o remount --target "$_mount_point"
else
mount --target "$_mount_point"
fi
}
include_mount_options_functions
# test "$mount_has_to_exist" = 'yes'
test "no" = 'yes' && assert_mount_point_in_fstab "$var_removable_partition" \
|| { echo "Not remediating, because there is no record of $var_removable_partition in /etc/fstab" >&2; exit 1; }
ensure_mount_option_in_fstab "$var_removable_partition" "nosuid"
ensure_partition_is_mounted "$var_removable_partition"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | high |
---|
Strategy: | configure |
---|
- name: XCCDF Value var_removable_partition # promote to variable
set_fact:
var_removable_partition: (N/A)
tags:
- always
- name: get back device associated to mountpoint
shell: mount | grep ' {{ var_removable_partition }} ' |cut -d ' ' -f 1
register: device_name
check_mode: no
tags:
- mount_option_nosuid_removable_partitions
- unknown_severity
- configure_strategy
- low_complexity
- high_disruption
- CCE-80148-0
- NIST-800-53-AC-6
- NIST-800-53-AC-19(a)
- NIST-800-53-AC-19(d)
- NIST-800-53-AC-19(e)
- NIST-800-53-CM-7
- NIST-800-53-MP-2
- DISA-STIG-RHEL-07-021010
- name: get back device previous mount option
shell: mount | grep ' {{ var_removable_partition }} ' | sed -re 's:.*\((.*)\):\1:'
register: device_cur_mountoption
check_mode: no
tags:
- mount_option_nosuid_removable_partitions
- unknown_severity
- configure_strategy
- low_complexity
- high_disruption
- CCE-80148-0
- NIST-800-53-AC-6
- NIST-800-53-AC-19(a)
- NIST-800-53-AC-19(d)
- NIST-800-53-AC-19(e)
- NIST-800-53-CM-7
- NIST-800-53-MP-2
- DISA-STIG-RHEL-07-021010
- name: get back device fstype
shell: mount | grep ' {{ var_removable_partition }} ' | cut -d ' ' -f 5
register: device_fstype
check_mode: no
tags:
- mount_option_nosuid_removable_partitions
- unknown_severity
- configure_strategy
- low_complexity
- high_disruption
- CCE-80148-0
- NIST-800-53-AC-6
- NIST-800-53-AC-19(a)
- NIST-800-53-AC-19(d)
- NIST-800-53-AC-19(e)
- NIST-800-53-CM-7
- NIST-800-53-MP-2
- DISA-STIG-RHEL-07-021010
- name: Ensure permission nosuid are set on var_removable_partition
mount:
path: "{{ var_removable_partition }}"
src: "{{device_name.stdout}}"
opts: "{{ device_cur_mountoption.stdout }},nosuid"
state: "mounted"
fstype: "{{device_fstype.stdout}}"
tags:
- mount_option_nosuid_removable_partitions
- unknown_severity
- configure_strategy
- low_complexity
- high_disruption
- CCE-80148-0
- NIST-800-53-AC-6
- NIST-800-53-AC-19(a)
- NIST-800-53-AC-19(d)
- NIST-800-53-AC-19(e)
- NIST-800-53-CM-7
- NIST-800-53-MP-2
- DISA-STIG-RHEL-07-021010
|
Add nodev Option to Removable Media Partitions
[ref]ruleThe nodev mount option prevents files from being
interpreted as character or block devices.
Legitimate character and block devices should exist only in
the /dev directory on the root partition or within chroot
jails built for system services.
Add the nodev option to the fourth column of
/etc/fstab for the line which controls mounting of
any removable media partitions.
Rationale:The only legitimate location for device files is the /dev directory
located on the root partition. An exception to this is chroot jails, and it is
not advised to set nodev on partitions which contain their root
filesystems. Remediation Shell script: (show)
var_removable_partition="(N/A)"
function include_mount_options_functions {
:
}
# $1: mount point
# $2: new mount point option
function ensure_mount_option_in_fstab {
local _mount_point="$1" _new_opt="$2" _mount_point_match_regexp="" _previous_mount_opts=""
_mount_point_match_regexp="$(get_mount_point_regexp "$_mount_point")"
if [ $(grep "$_mount_point_match_regexp" /etc/fstab | grep -c "$_new_opt" ) -eq 0 ]; then
_previous_mount_opts=$(grep "$_mount_point_match_regexp" /etc/fstab | awk '{print $4}')
sed -i "s|\(${_mount_point_match_regexp}.*${_previous_mount_opts}\)|\1,${_new_opt}|" /etc/fstab
fi
}
# $1: mount point
function get_mount_point_regexp {
printf "[[:space:]]%s[[:space:]]" "$1"
}
# $1: mount point
function assert_mount_point_in_fstab {
local _mount_point_match_regexp
_mount_point_match_regexp="$(get_mount_point_regexp "$1")"
grep "$_mount_point_match_regexp" -q /etc/fstab \
|| { echo "The mount point '$1' is not even in /etc/fstab, so we can't set up mount options" >&2; return 1; }
}
# $1: mount point
function remove_defaults_from_fstab_if_overriden {
local _mount_point_match_regexp
_mount_point_match_regexp="$(get_mount_point_regexp "$1")"
if [ $(grep "$_mount_point_match_regexp" /etc/fstab | grep -q "defaults,") -gt 0 ]
then
sed -i "s|\(${_mount_point_match_regexp}.*\)defaults,|\1|" /etc/fstab
fi
}
# $1: mount point
function ensure_partition_is_mounted {
local _mount_point="$1"
mkdir -p "$_mount_point" || return 1
if mountpoint -q "$_mount_point"; then
mount -o remount --target "$_mount_point"
else
mount --target "$_mount_point"
fi
}
include_mount_options_functions
# test "$mount_has_to_exist" = 'yes'
test "no" = 'yes' && assert_mount_point_in_fstab "$var_removable_partition" \
|| { echo "Not remediating, because there is no record of $var_removable_partition in /etc/fstab" >&2; exit 1; }
ensure_mount_option_in_fstab "$var_removable_partition" "nodev"
ensure_partition_is_mounted "$var_removable_partition"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | high |
---|
Strategy: | configure |
---|
- name: XCCDF Value var_removable_partition # promote to variable
set_fact:
var_removable_partition: (N/A)
tags:
- always
- name: get back device associated to mountpoint
shell: mount | grep ' {{ var_removable_partition }} ' |cut -d ' ' -f 1
register: device_name
check_mode: no
tags:
- mount_option_nodev_removable_partitions
- unknown_severity
- configure_strategy
- low_complexity
- high_disruption
- CCE-80146-4
- NIST-800-53-AC-19(a)
- NIST-800-53-AC-19(d)
- NIST-800-53-AC-19(e)
- NIST-800-53-CM-7
- NIST-800-53-MP-2
- name: get back device previous mount option
shell: mount | grep ' {{ var_removable_partition }} ' | sed -re 's:.*\((.*)\):\1:'
register: device_cur_mountoption
check_mode: no
tags:
- mount_option_nodev_removable_partitions
- unknown_severity
- configure_strategy
- low_complexity
- high_disruption
- CCE-80146-4
- NIST-800-53-AC-19(a)
- NIST-800-53-AC-19(d)
- NIST-800-53-AC-19(e)
- NIST-800-53-CM-7
- NIST-800-53-MP-2
- name: get back device fstype
shell: mount | grep ' {{ var_removable_partition }} ' | cut -d ' ' -f 5
register: device_fstype
check_mode: no
tags:
- mount_option_nodev_removable_partitions
- unknown_severity
- configure_strategy
- low_complexity
- high_disruption
- CCE-80146-4
- NIST-800-53-AC-19(a)
- NIST-800-53-AC-19(d)
- NIST-800-53-AC-19(e)
- NIST-800-53-CM-7
- NIST-800-53-MP-2
- name: Ensure permission nodev are set on var_removable_partition
mount:
path: "{{ var_removable_partition }}"
src: "{{device_name.stdout}}"
opts: "{{ device_cur_mountoption.stdout }},nodev"
state: "mounted"
fstype: "{{device_fstype.stdout}}"
tags:
- mount_option_nodev_removable_partitions
- unknown_severity
- configure_strategy
- low_complexity
- high_disruption
- CCE-80146-4
- NIST-800-53-AC-19(a)
- NIST-800-53-AC-19(d)
- NIST-800-53-AC-19(e)
- NIST-800-53-CM-7
- NIST-800-53-MP-2
|
Add noexec Option to Removable Media Partitions
[ref]ruleThe noexec mount option prevents the direct
execution of binaries on the mounted filesystem.
Preventing the direct execution of binaries from removable media (such as a USB
key) provides a defense against malicious software that may be present on such
untrusted media.
Add the noexec option to the fourth column of
/etc/fstab for the line which controls mounting of
any removable media partitions.
Rationale:Allowing users to execute binaries from removable media such as USB keys exposes
the system to potential compromise. Remediation Shell script: (show)
var_removable_partition="(N/A)"
function include_mount_options_functions {
:
}
# $1: mount point
# $2: new mount point option
function ensure_mount_option_in_fstab {
local _mount_point="$1" _new_opt="$2" _mount_point_match_regexp="" _previous_mount_opts=""
_mount_point_match_regexp="$(get_mount_point_regexp "$_mount_point")"
if [ $(grep "$_mount_point_match_regexp" /etc/fstab | grep -c "$_new_opt" ) -eq 0 ]; then
_previous_mount_opts=$(grep "$_mount_point_match_regexp" /etc/fstab | awk '{print $4}')
sed -i "s|\(${_mount_point_match_regexp}.*${_previous_mount_opts}\)|\1,${_new_opt}|" /etc/fstab
fi
}
# $1: mount point
function get_mount_point_regexp {
printf "[[:space:]]%s[[:space:]]" "$1"
}
# $1: mount point
function assert_mount_point_in_fstab {
local _mount_point_match_regexp
_mount_point_match_regexp="$(get_mount_point_regexp "$1")"
grep "$_mount_point_match_regexp" -q /etc/fstab \
|| { echo "The mount point '$1' is not even in /etc/fstab, so we can't set up mount options" >&2; return 1; }
}
# $1: mount point
function remove_defaults_from_fstab_if_overriden {
local _mount_point_match_regexp
_mount_point_match_regexp="$(get_mount_point_regexp "$1")"
if [ $(grep "$_mount_point_match_regexp" /etc/fstab | grep -q "defaults,") -gt 0 ]
then
sed -i "s|\(${_mount_point_match_regexp}.*\)defaults,|\1|" /etc/fstab
fi
}
# $1: mount point
function ensure_partition_is_mounted {
local _mount_point="$1"
mkdir -p "$_mount_point" || return 1
if mountpoint -q "$_mount_point"; then
mount -o remount --target "$_mount_point"
else
mount --target "$_mount_point"
fi
}
include_mount_options_functions
# test "$mount_has_to_exist" = 'yes'
test "no" = 'yes' && assert_mount_point_in_fstab "$var_removable_partition" \
|| { echo "Not remediating, because there is no record of $var_removable_partition in /etc/fstab" >&2; exit 1; }
ensure_mount_option_in_fstab "$var_removable_partition" "noexec"
ensure_partition_is_mounted "$var_removable_partition"
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | high |
---|
Strategy: | configure |
---|
- name: XCCDF Value var_removable_partition # promote to variable
set_fact:
var_removable_partition: (N/A)
tags:
- always
- name: get back device associated to mountpoint
shell: mount | grep ' {{ var_removable_partition }} ' |cut -d ' ' -f 1
register: device_name
check_mode: no
tags:
- mount_option_noexec_removable_partitions
- unknown_severity
- configure_strategy
- low_complexity
- high_disruption
- CCE-80147-2
- NIST-800-53-AC-19(a)
- NIST-800-53-AC-19(d)
- NIST-800-53-AC-19(e)
- NIST-800-53-CM-7
- NIST-800-53-MP-2
- name: get back device previous mount option
shell: mount | grep ' {{ var_removable_partition }} ' | sed -re 's:.*\((.*)\):\1:'
register: device_cur_mountoption
check_mode: no
tags:
- mount_option_noexec_removable_partitions
- unknown_severity
- configure_strategy
- low_complexity
- high_disruption
- CCE-80147-2
- NIST-800-53-AC-19(a)
- NIST-800-53-AC-19(d)
- NIST-800-53-AC-19(e)
- NIST-800-53-CM-7
- NIST-800-53-MP-2
- name: get back device fstype
shell: mount | grep ' {{ var_removable_partition }} ' | cut -d ' ' -f 5
register: device_fstype
check_mode: no
tags:
- mount_option_noexec_removable_partitions
- unknown_severity
- configure_strategy
- low_complexity
- high_disruption
- CCE-80147-2
- NIST-800-53-AC-19(a)
- NIST-800-53-AC-19(d)
- NIST-800-53-AC-19(e)
- NIST-800-53-CM-7
- NIST-800-53-MP-2
- name: Ensure permission noexec are set on var_removable_partition
mount:
path: "{{ var_removable_partition }}"
src: "{{device_name.stdout}}"
opts: "{{ device_cur_mountoption.stdout }},noexec"
state: "mounted"
fstype: "{{device_fstype.stdout}}"
tags:
- mount_option_noexec_removable_partitions
- unknown_severity
- configure_strategy
- low_complexity
- high_disruption
- CCE-80147-2
- NIST-800-53-AC-19(a)
- NIST-800-53-AC-19(d)
- NIST-800-53-AC-19(e)
- NIST-800-53-CM-7
- NIST-800-53-MP-2
|
Restrict Programs from Dangerous Execution Patterns
[ref]groupThe recommendations in this section are designed to
ensure that the system's features to protect against potentially
dangerous program execution are activated.
These protections are applied at the system initialization or
kernel level, and defend against certain types of badly-configured
or compromised programs. |
contains 5 rules |
Disable Core Dumps
[ref]groupA core dump file is the memory image of an executable
program when it was terminated by the operating system due to
errant behavior. In most cases, only software developers
legitimately need to access these files. The core dump files may
also contain sensitive information, or unnecessarily occupy large
amounts of disk space.
Once a hard limit is set in /etc/security/limits.conf , a
user cannot increase that limit within his or her own session. If access
to core dumps is required, consider restricting them to only
certain users or groups. See the limits.conf man page for more
information.
The core dumps of setuid programs are further protected. The
sysctl variable fs.suid_dumpable controls whether
the kernel allows core dumps from these programs at all. The default
value of 0 is recommended. |
contains 1 rule |
Disable Core Dumps for SUID programs
[ref]rule
To set the runtime status of the fs.suid_dumpable kernel parameter,
run the following command:
$ sudo sysctl -w fs.suid_dumpable=0
If this is not the system's default value, add the following line to /etc/sysctl.conf :
fs.suid_dumpable = 0
Rationale:The core dump of a setuid program is more likely to contain
sensitive data, as the program itself runs with greater privileges than the
user who initiated execution of the program. Disabling the ability for any
setuid program to write a core file decreases the risk of unauthorized access
of such data. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
#
# Set runtime for fs.suid_dumpable
#
/sbin/sysctl -q -n -w fs.suid_dumpable=0
#
# If fs.suid_dumpable present in /etc/sysctl.conf, change value to "0"
# else, add "fs.suid_dumpable = 0" to /etc/sysctl.conf
#
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysctl.conf' '^fs.suid_dumpable' "0" 'CCE-26900-1'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: Ensure sysctl fs.suid_dumpable is set to 0
sysctl:
name: fs.suid_dumpable
value: 0
state: present
reload: yes
tags:
- sysctl_fs_suid_dumpable
- unknown_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-26900-1
- NIST-800-53-SI-11
|
Enable Execute Disable (XD) or No Execute (NX) Support on
x86 Systems
[ref]groupRecent processors in the x86 family support the
ability to prevent code execution on a per memory page basis.
Generically and on AMD processors, this ability is called No
Execute (NX), while on Intel processors it is called Execute
Disable (XD). This ability can help prevent exploitation of buffer
overflow vulnerabilities and should be activated whenever possible.
Extra steps must be taken to ensure that this protection is
enabled, particularly on 32-bit x86 systems. Other processors, such
as Itanium and POWER, have included such support since inception
and the standard kernel for those platforms supports the
feature. This is enabled by default on the latest Red Hat and
Fedora systems if supported by the hardware. |
contains 1 rule |
Install PAE Kernel on Supported 32-bit x86 Systems
[ref]ruleSystems that are using the 64-bit x86 kernel package
do not need to install the kernel-PAE package because the 64-bit
x86 kernel already includes this support. However, if the system is
32-bit and also supports the PAE and NX features as
determined in the previous section, the kernel-PAE package should
be installed to enable XD or NX support:
$ sudo yum install kernel-PAE
The installation process should also have configured the
bootloader to load the new kernel at boot. Verify this at reboot
and modify /etc/default/grub if necessary.Rationale:On 32-bit systems that support the XD or NX bit, the vendor-supplied
PAE kernel is required to enable either Execute Disable (XD) or No Execute (NX) support. |
Enable ExecShield
[ref]groupExecShield describes kernel features that provide
protection against exploitation of memory corruption errors such as buffer
overflows. These features include random placement of the stack and other
memory regions, prevention of execution in memory that should only hold data,
and special handling of text buffers. These protections are enabled by default
on 32-bit systems and controlled through sysctl variables
kernel.exec-shield and kernel.randomize_va_space . On the latest
64-bit systems, kernel.exec-shield cannot be enabled or disabled with
sysctl . |
contains 2 rules |
Enable ExecShield
[ref]ruleBy default on Red Hat Enterprise Linux 7 64-bit systems, ExecShield
is enabled and can only be disabled if the hardware does not support ExecShield
or is disabled in /etc/default/grub . For Red Hat Enterprise Linux 7
32-bit systems, sysctl can be used to enable ExecShield. Rationale:ExecShield uses the segmentation feature on all x86 systems
to prevent execution in memory higher than a certain address. It
writes an address as a limit in the code segment descriptor, to
control where code can be executed, on a per-process basis. When
the kernel places a process's memory regions such as the stack and
heap higher than this address, the hardware prevents execution in that
address range. This is enabled by default on the latest Red Hat and Fedora
systems if supported by the hardware. References:
1.5.2, 3.1.7, CCI-002530, 164.308(a)(1)(ii)(D), 164.308(a)(3), 164.308(a)(4), 164.310(b), 164.310(c), 164.312(a), 164.312(e), SC-39 Remediation Shell script: (show)
if [ $(getconf LONG_BIT) = "32" ] ; then
#
# Set runtime for kernel.exec-shield
#
sysctl -q -n -w kernel.exec-shield=1
#
# If kernel.exec-shield present in /etc/sysctl.conf, change value to "1"
# else, add "kernel.exec-shield = 1" to /etc/sysctl.conf
#
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysctl.conf' '^kernel.exec-shield' '1' 'CCE-27211-2'
fi
if [ $(getconf LONG_BIT) = "64" ] ; then
if grep --silent noexec /boot/grub2/grub*.cfg ; then
sed -i "s/noexec.*//g" /etc/default/grub
sed -i "s/noexec.*//g" /etc/grub.d/*
GRUBCFG=`ls | grep '.cfg$'`
grub2-mkconfig -o /boot/grub2/$GRUBCFG
fi
fi
|
Enable Randomized Layout of Virtual Address Space
[ref]rule
To set the runtime status of the kernel.randomize_va_space kernel parameter,
run the following command:
$ sudo sysctl -w kernel.randomize_va_space=2
If this is not the system's default value, add the following line to /etc/sysctl.conf :
kernel.randomize_va_space = 2
Rationale:Address space layout randomization (ASLR) makes it more difficult
for an attacker to predict the location of attack code they have introduced
into a process's address space during an attempt at exploitation. Additionally, ASLR
makes it more difficult for an attacker to know the location of existing code
in order to re-purpose it using return oriented programming (ROP) techniques. References:
1.5.1, 3.1.7, CCI-000366, 164.308(a)(1)(ii)(D), 164.308(a)(3), 164.308(a)(4), 164.310(b), 164.310(c), 164.312(a), 164.312(e), SC-30(2), SRG-OS-000480-GPOS-00227, SV-92521r1_rule Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' 'CCE-27127-0'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: Ensure sysctl kernel.randomize_va_space is set to 2
sysctl:
name: kernel.randomize_va_space
value: 2
state: present
reload: yes
tags:
- sysctl_kernel_randomize_va_space
- medium_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-27127-0
- NIST-800-53-SC-30(2)
- NIST-800-171-3.1.7
- DISA-STIG-RHEL-07-040201
|
Restrict Access to Kernel Message Buffer
[ref]rule
To set the runtime status of the kernel.dmesg_restrict kernel parameter,
run the following command:
$ sudo sysctl -w kernel.dmesg_restrict=1
If this is not the system's default value, add the following line to /etc/sysctl.conf :
kernel.dmesg_restrict = 1
Rationale:Unprivileged access to the kernel syslog can expose sensitive kernel
address information. Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
#
# Set runtime for kernel.dmesg_restrict
#
/sbin/sysctl -q -n -w kernel.dmesg_restrict=1
#
# If kernel.dmesg_restrict present in /etc/sysctl.conf, change value to "1"
# else, add "kernel.dmesg_restrict = 1" to /etc/sysctl.conf
#
# Function to replace configuration setting in config file or add the configuration setting if
# it does not exist.
#
# Expects arguments:
#
# config_file: Configuration file that will be modified
# key: Configuration option to change
# value: Value of the configuration option to change
# cce: The CCE identifier or '@CCENUM@' if no CCE identifier exists
# format: The printf-like format string that will be given stripped key and value as arguments,
# so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
#
# Optional arugments:
#
# format: Optional argument to specify the format of how key/value should be
# modified/appended in the configuration file. The default is key = value.
#
# Example Call(s):
#
# With default format of 'key = value':
# replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
#
# With custom key/value format:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
#
# With a variable:
# replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
#
function replace_or_append {
local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
local config_file=$1
local key=$2
local value=$3
local cce=$4
local format=$5
if [ "$case_insensitive_mode" = yes ]; then
sed_case_insensitive_option="i"
grep_case_insensitive_option="-i"
fi
[ -n "$format" ] || format="$default_format"
# Check sanity of the input
[ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }
# Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
# Otherwise, regular sed command will do.
sed_command=('sed' '-i')
if test -L "$config_file"; then
sed_command+=('--follow-symlinks')
fi
# Test that the cce arg is not empty or does not equal @CCENUM@.
# If @CCENUM@ exists, it means that there is no CCE assigned.
if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
cce="CCE-${cce}"
else
cce="CCE"
fi
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")
# shellcheck disable=SC2059
printf -v formatted_output "$format" "$stripped_key" "$value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
"${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
else
# \n is precaution for case where file ends without trailing newline
printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
printf '%s\n' "$formatted_output" >> "$config_file"
fi
}
replace_or_append '/etc/sysctl.conf' '^kernel.dmesg_restrict' "1" 'CCE-27050-4'
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: Ensure sysctl kernel.dmesg_restrict is set to 1
sysctl:
name: kernel.dmesg_restrict
value: 1
state: present
reload: yes
tags:
- sysctl_kernel_dmesg_restrict
- unknown_severity
- disable_strategy
- low_complexity
- medium_disruption
- CCE-27050-4
- NIST-800-53-SI-11
- NIST-800-171-3.1.5
|