contains 38 rules |
System Settings
[ref]groupContains rules that check correct system settings. |
contains 32 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 2 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 2 rules |
Ensure /var/log Located On Separate Partition
[ref]rule
System logs are stored in the /var/log directory.
Ensure that it has its own partition or logical
volume at installation time, or migrate it using LVM.
Rationale:
Placing /var/log in its own partition
enables better separation between log files
and other files in /var/ .
|
Ensure /var/log/audit Located On Separate Partition
[ref]rule
Audit logs are stored in the /var/log/audit directory. Ensure that it
has its own partition or logical volume at installation time, or migrate it
later using LVM. Make absolutely certain that it is large enough to store all
audit logs that will be created by the auditing daemon.
Rationale:
Placing /var/log/audit in its own partition
enables better separation between audit files
and other files, and helps ensure that
auditing cannot be halted due to the partition running out
of space.
|
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 1 rule |
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 1 rule |
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.
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
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
- DISA-STIG-RHEL-07-020110
|
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 2 rules |
Ensure rsyslog is Installed
[ref]rule
Rsyslog is installed by default.
The rsyslog package can be installed with the following command:
$ sudo yum install rsyslog
Rationale:
The rsyslog package provides the rsyslog daemon, which provides
system logging services.
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
# Function to install or uninstall packages on RHEL and Fedora systems.
#
# Example Call(s):
#
# package_command install aide
# package_command remove telnet-server
#
function package_command {
# Load function arguments into local variables
local package_operation=$1
local package=$2
# Check sanity of the input
if [ $# -ne "2" ]
then
echo "Usage: package_command 'install/uninstall' 'rpm_package_name"
echo "Aborting."
exit 1
fi
# If dnf is installed, use dnf; otherwise, use yum
if [ -f "/usr/bin/dnf" ] ; then
install_util="/usr/bin/dnf"
else
install_util="/usr/bin/yum"
fi
if [ "$package_operation" != 'remove' ] ; then
# If the rpm is not installed, install the rpm
if ! /bin/rpm -q --quiet $package; then
$install_util -y $package_operation $package
fi
else
# If the rpm is installed, uninstall the rpm
if /bin/rpm -q --quiet $package; then
$install_util -y $package_operation $package
fi
fi
}
package_command install rsyslog
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: Ensure rsyslog is installed
package:
name="{{item}}"
state=present
with_items:
- rsyslog
tags:
- package_rsyslog_installed
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-80187-8
- NIST-800-53-AU-9(2)
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
include install_rsyslog
class install_rsyslog {
package { 'rsyslog':
ensure => 'installed',
}
}
Remediation Anaconda snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
package --add=rsyslog
|
Enable rsyslog Service
[ref]ruleThe rsyslog service provides syslog-style logging by default on Red Hat Enterprise Linux 7.
The rsyslog service can be enabled with the following command:
$ sudo systemctl enable rsyslog.service
Rationale:The rsyslog service must be running in order to provide
logging services, which are essential to system administration.
Identifiers:
CCE-80188-6 References:
AU-4(1), AU-12, CCI-001311, CCI-001312, CCI-001557, CCI-001851, 4.2.1.1, NT28(R5), NT28(R46), 164.312(a)(2)(ii), A.12.3.1 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
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 rsyslog
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | enable |
---|
- name: Enable service rsyslog
service:
name="{{item}}"
enabled="yes"
state="started"
with_items:
- rsyslog
tags:
- service_rsyslog_enabled
- medium_severity
- enable_strategy
- low_complexity
- low_disruption
- CCE-80188-6
- NIST-800-53-AU-4(1)
- NIST-800-53-AU-12
|
System Accounting with auditd
[ref]groupThe audit service provides substantial capabilities
for recording system activities. By default, the service audits about
SELinux AVC denials and certain types of security-relevant events
such as system logins, account modifications, and authentication
events performed by programs such as sudo.
Under its default configuration, auditd has modest disk space
requirements, and should not noticeably impact system performance.
NOTE: The Linux Audit daemon auditd can be configured to use
the augenrules program to read audit rules files (*.rules )
located in /etc/audit/rules.d location and compile them to create
the resulting form of the /etc/audit/audit.rules configuration file
during the daemon startup (default configuration). Alternatively, the auditd
daemon can use the auditctl utility to read audit rules from the
/etc/audit/audit.rules configuration file during daemon startup,
and load them into the kernel. The expected behavior is configured via the
appropriate ExecStartPost directive setting in the
/usr/lib/systemd/system/auditd.service configuration file.
To instruct the auditd daemon to use the augenrules program
to read audit rules (default configuration), use the following setting:
ExecStartPost=-/sbin/augenrules --load
in the /usr/lib/systemd/system/auditd.service configuration file.
In order to instruct the auditd daemon to use the auditctl
utility to read audit rules, use the following setting:
ExecStartPost=-/sbin/auditctl -R /etc/audit/audit.rules
in the /usr/lib/systemd/system/auditd.service configuration file.
Refer to [Service] section of the /usr/lib/systemd/system/auditd.service
configuration file for further details.
Government networks often have substantial auditing
requirements and auditd can be configured to meet these
requirements.
Examining some example audit records demonstrates how the Linux audit system
satisfies common requirements.
The following example from 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 27 rules |
Configure auditd Rules for Comprehensive Auditing
[ref]groupThe auditd program can perform comprehensive
monitoring of system activity. This section describes recommended
configuration settings for comprehensive auditing, but a full
description of the auditing system's capabilities is beyond the
scope of this guide. The mailing list linux-audit@redhat.com exists
to facilitate community discussion of the auditing system.
The audit subsystem supports extensive collection of events, including:
- Tracing of arbitrary system calls (identified by name or number)
on entry or exit.
- Filtering by PID, UID, call success, system call argument (with
some limitations), etc.
- Monitoring of specific files for modifications to the file's
contents or metadata.
Auditing rules at startup are controlled by the file /etc/audit/audit.rules .
Add rules to it to meet the auditing requirements for your organization.
Each line in /etc/audit/audit.rules represents a series of arguments
that can be passed to auditctl and can be individually tested
during runtime. See documentation in /usr/share/doc/audit-VERSION and
in the related man pages for more details.
If copying any example audit rulesets from /usr/share/doc/audit-VERSION ,
be sure to comment out the
lines containing arch= which are not appropriate for your system's
architecture. Then review and understand the following rules,
ensuring rules are activated as needed for the appropriate
architecture.
After reviewing all the rules, reading the following sections, and
editing as needed, the new rules can be activated as follows:
$ sudo service auditd restart
|
contains 27 rules |
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 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. Identifiers:
CCE-27290-6 References:
AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 5.2.4, Req-10.4.2.b, CCI-001487, CCI-000169, 5.4.1.1, 3.1.7 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 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. Identifiers:
CCE-27216-1 References:
AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 5.2.4, Req-10.4.2.b, CCI-001487, CCI-000169, 5.4.1.1, 3.1.7 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 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. Identifiers:
CCE-27299-7 References:
AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.4.2.b, CCI-001487, CCI-000169, 5.4.1.1, 3.1.7 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. Identifiers:
CCE-27219-5 References:
AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, 5.2.4, Req-10.4.2.b, CCI-001487, CCI-000169, 5.4.1.1, 3.1.7 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 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. Identifiers:
CCE-27310-2 References:
AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(b), IR-5, 5.2.4, Req-10.4.2.b, CCI-001487, CCI-000169, 5.4.1.1, 3.1.7 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 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 - 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
Warning:
Note that these rules can be configured in a
number of ways while still achieving the desired effect. Here the system calls
have been placed independent of other system calls. Grouping these system
calls with others as identifying earlier in this guide is more efficient.
Rationale:The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27339-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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27339-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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27339-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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27339-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
Warning:
Note that these rules can be configured in a
number of ways while still achieving the desired effect. Here the system calls
have been placed independent of other system calls. Grouping these system
calls with others as identifying earlier in this guide is more efficient.
Rationale:The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27364-9
- 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27364-9
- 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27364-9
- 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27364-9
- DISA-STIG-RHEL-07-030370
|
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
Warning:
Note that these rules can be configured in a
number of ways while still achieving the desired effect. Here the system calls
have been placed independent of other system calls. Grouping these system
calls with others as identifying earlier in this guide is more efficient.
Rationale:The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27393-8
- 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27393-8
- 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27393-8
- 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27393-8
- DISA-STIG-RHEL-07-030420
|
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
Warning:
Note that these rules can be configured in a
number of ways while still achieving the desired effect. Here the system calls
have been placed independent of other system calls. Grouping these system
calls with others as identifying earlier in this guide is more efficient.
Rationale:The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27388-8
- 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27388-8
- 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27388-8
- 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27388-8
- DISA-STIG-RHEL-07-030430
|
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
Warning:
Note that these rules can be configured in a
number of ways while still achieving the desired effect. Here the system calls
have been placed independent of other system calls. Grouping these system
calls with others as identifying earlier in this guide is more efficient.
Rationale:The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27356-5
- 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27356-5
- 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27356-5
- 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27356-5
- DISA-STIG-RHEL-07-030380
|
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
Warning:
Note that these rules can be configured in a
number of ways while still achieving the desired effect. Here the system calls
have been placed independent of other system calls. Grouping these system
calls with others as identifying earlier in this guide is more efficient.
Rationale:The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27387-0
- 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27387-0
- 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27387-0
- 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27387-0
- DISA-STIG-RHEL-07-030400
|
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
Warning:
Note that these rules can be configured in a
number of ways while still achieving the desired effect. Here the system calls
have been placed independent of other system calls. Grouping these system
calls with others as identifying earlier in this guide is more efficient.
Rationale:The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. 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
- 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
- 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
- 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
- DISA-STIG-RHEL-07-030480
|
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
Warning:
Note that these rules can be configured in a
number of ways while still achieving the desired effect. Here the system calls
have been placed independent of other system calls. Grouping these system
calls with others as identifying earlier in this guide is more efficient.
Rationale:The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27389-6
- 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27389-6
- 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27389-6
- 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27389-6
- DISA-STIG-RHEL-07-030450
|
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
Warning:
Note that these rules can be configured in a
number of ways while still achieving the desired effect. Here the system calls
have been placed independent of other system calls. Grouping these system
calls with others as identifying earlier in this guide is more efficient.
Rationale:The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27083-5
- 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27083-5
- 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27083-5
- 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27083-5
- DISA-STIG-RHEL-07-030390
|
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
Warning:
Note that these rules can be configured in a
number of ways while still achieving the desired effect. Here the system calls
have been placed independent of other system calls. Grouping these system
calls with others as identifying earlier in this guide is more efficient.
Rationale:The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. 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
- 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
- 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
- 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
- DISA-STIG-RHEL-07-030490
|
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
Warning:
Note that these rules can be configured in a
number of ways while still achieving the desired effect. Here the system calls
have been placed independent of other system calls. Grouping these system
calls with others as identifying earlier in this guide is more efficient.
Rationale:The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27280-7
- 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27280-7
- 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27280-7
- 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27280-7
- DISA-STIG-RHEL-07-030460
|
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
Warning:
Note that these rules can be configured in a
number of ways while still achieving the desired effect. Here the system calls
have been placed independent of other system calls. Grouping these system
calls with others as identifying earlier in this guide is more efficient.
Rationale:The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. 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
- 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
- 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
- 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
- DISA-STIG-RHEL-07-030470
|
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
Warning:
Note that these rules can be configured in a
number of ways while still achieving the desired effect. Here the system calls
have been placed independent of other system calls. Grouping these system
calls with others as identifying earlier in this guide is more efficient.
Rationale:The changing of file permissions could indicate that a user is attempting to
gain access to information that would otherwise be disallowed. Auditing DAC modifications
can facilitate the identification of patterns of abuse among both authorized and
unauthorized users. 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27213-8
- 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27213-8
- 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27213-8
- 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
- low_severity
- restrict_strategy
- low_complexity
- low_disruption
- CCE-27213-8
- DISA-STIG-RHEL-07-030440
|
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 1 rule |
Ensure auditd Collects Unauthorized Access Attempts to Files (unsuccessful)
[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,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 the system is 64 bit then also add the following lines:
-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
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,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 the system is 64 bit then also add the following lines:
-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
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. Identifiers:
CCE-27347-4 References:
AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, CCI-000172, CCI-002884, Req-10.2.4, Req-10.2.1, 5.2.10, 5.4.1.1, 3.1.7 Remediation Shell script: (show)
# Perform the remediation of the syscall rule
# Retrieve hardware architecture of the underlying system
[ $(getconf LONG_BIT) = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")
for ARCH in "${RULE_ARCHS[@]}"
do
# First fix the -EACCES requirement
PATTERN="-a always,exit -F arch=$ARCH -S .* -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -k *"
# Use escaped BRE regex to specify rule group
GROUP="\(creat\|open\|truncate\)"
FULL_RULE="-a always,exit -F arch=$ARCH -S creat -S open -S openat -S open_by_handle_at -S truncate -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -k access"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function 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"
# Then fix the -EPERM requirement
PATTERN="-a always,exit -F arch=$ARCH -S .* -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -k *"
# No need to change content of $GROUP variable - it's the same as for -EACCES case above
FULL_RULE="-a always,exit -F arch=$ARCH -S creat -S open -S openat -S open_by_handle_at -S truncate -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -k access"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function 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 1 rule |
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.
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
# Replace possible slash '/' character in sbinary definition so we could use it in sed expressions below
sbinary_esc=${sbinary//$'/'/$'\/'}
# Check if this sbinary wasn't already handled in some of the previous iterations
# Return match only if whole sbinary definition matched (not in the case just prefix matched!!!)
if [[ $(sed -ne "/${sbinary_esc}$/p" <<< ${sbinaries_to_skip[@]}) ]]
then
# If so, don't process it second time & go to process next sbinary
continue
fi
# Reset the counter of inspected files when starting to check
# presence of existing audit rule for new sbinary
local count_of_inspected_files=0
# For each audit rules file from the list of files to be inspected
for afile in "${files_to_inspect[@]}"
do
# Search current audit rules file's content for match. Match criteria:
# * existing rule is for the same SUID/SGID binary we are currently processing (but
# can contain multiple -F path= elements covering multiple SUID/SGID binaries)
# * existing rule contains all arguments from expected rule form (though can contain
# them in arbitrary order)
base_search=$(sed -e '/-a always,exit/!d' -e '/-F path='"${sbinary_esc}"'/!d' \
-e '/-F path=[^[:space:]]\+/!d' -e '/-F perm=.*/!d' \
-e '/-F auid>='"${min_auid}"'/!d' -e '/-F auid!=4294967295/!d' \
-e '/-k privileged/!d' $afile)
# Increase the count of inspected files for this sbinary
count_of_inspected_files=$((count_of_inspected_files + 1))
# Define expected rule form for this binary
expected_rule="-a always,exit -F path=${sbinary} -F perm=x -F auid>=${min_auid} -F auid!=4294967295 -k privileged"
# Require execute access type to be set for existing audit rule
exec_access='x'
# Search current audit rules file's content for presence of rule pattern for this sbinary
if [[ $base_search ]]
then
# Current audit rules file already contains rule for this binary =>
# Store the exact form of found rule for this binary for further processing
concrete_rule=$base_search
# Select all other SUID/SGID binaries possibly also present in the found rule
IFS=$'\n' handled_sbinaries=($(grep -o -e "-F path=[^[:space:]]\+" <<< $concrete_rule))
IFS=$' ' handled_sbinaries=(${handled_sbinaries[@]//-F path=/})
# Merge the list of such SUID/SGID binaries found in this iteration with global list ignoring duplicates
sbinaries_to_skip=($(for i in "${sbinaries_to_skip[@]}" "${handled_sbinaries[@]}"; do echo $i; done | sort -du))
# Separate concrete_rule into three sections using hash '#'
# sign as a delimiter around rule's permission section borders
concrete_rule=$(echo $concrete_rule | sed -n "s/\(.*\)\+\(-F perm=[rwax]\+\)\+/\1#\2#/p")
# Split concrete_rule into head, perm, and tail sections using hash '#' delimiter
IFS=$'#' read rule_head rule_perm rule_tail <<< "$concrete_rule"
# Extract already present exact access type [r|w|x|a] from rule's permission section
access_type=${rule_perm//-F perm=/}
# Verify current permission access type(s) for rule contain 'x' (execute) permission
if ! grep -q "$exec_access" <<< "$access_type"
then
# If not, append the 'x' (execute) permission to the existing access type bits
access_type="$access_type$exec_access"
# Reconstruct the permissions section for the rule
new_rule_perm="-F perm=$access_type"
# Update existing rule in current audit rules file with the new permission section
sed -i "s#${rule_head}\(.*\)${rule_tail}#${rule_head}${new_rule_perm}${rule_tail}#" $afile
fi
# If the required audit rule for particular sbinary wasn't found yet, insert it under following conditions:
#
# * in the "auditctl" mode of operation insert particular rule each time
# (because in this mode there's only one file -- /etc/audit/audit.rules to be inspected for presence of this rule),
#
# * in the "augenrules" mode of operation insert particular rule only once and only in case we have already
# searched all of the files from /etc/audit/rules.d/*.rules location (since that audit rule can be defined
# in any of those files and if not, we want it to be inserted only once into /etc/audit/rules.d/privileged.rules file)
#
elif [ "$tool" == "auditctl" ] || [[ "$tool" == "augenrules" && $count_of_inspected_files -eq "${#files_to_inspect[@]}" ]]
then
# Current audit rules file's content doesn't contain expected rule for this
# SUID/SGID binary yet => append it
echo $expected_rule >> $output_audit_file
fi
done
done
}
perform_audit_rules_privileged_commands_remediation "auditctl" "1000"
perform_audit_rules_privileged_commands_remediation "augenrules" "1000"
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
- 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
- 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
- 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
- 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
- DISA-STIG-RHEL-07-030360
|
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 1 rule |
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. Identifiers:
CCE-27206-2 References:
AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.7, 5.2.14, CCI-000366, CCI-000172, CCI-002884, 5.4.1.1, 3.1.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
|
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 1 rule |
Ensure auditd Collects Information on Kernel Module Loading and Unloading
[ref]ruleTo 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 .
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. Identifiers:
CCE-27129-6 References:
AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.7, 5.2.17, CCI-000172, 5.4.1.1, 3.1.7 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 -S delete_module \(-F key=\|-k \).*"
GROUP="modules"
FULL_RULE="-a always,exit -F arch=$ARCH -S init_module -S delete_module -k modules"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# Function 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" "/usr/sbin/insmod" "x" "modules"
fix_audit_watch_rule "augenrules" "/usr/sbin/insmod" "x" "modules"
# Function to fix audit file system object watch rule for given path:
# * if rule exists, also verifies the -w bits match the requirements
# * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
# audit rules file, depending on the tool which was used to load audit rules
#
# Expects four arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules'
# * path value of -w audit rule's argument
# * required access bits value of -p audit rule's argument
# * key value of -k audit rule's argument
#
# Example call:
#
# fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
#
function fix_audit_watch_rule {
# Load function arguments into local variables
local tool="$1"
local path="$2"
local required_access_bits="$3"
local key="$4"
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
declare -a files_to_inspect
# Check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
exit 1
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
elif [ "$tool" == 'augenrules' ]
then
# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
# Get pair -- filepath : matching_row into @matches array
IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
# Reset IFS back to default
unset IFS
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
files_to_inspect="/etc/audit/rules.d/$key.rules"
# If the $key.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Escape slashes in path for use in sed pattern below
local esc_path=${path//$'/'/$'\/'}
# Define BRE whitespace class shortcut
local sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "$required_access_bits" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
fi
done
}
fix_audit_watch_rule "auditctl" "/usr/sbin/rmmod" "x" "modules"
fix_audit_watch_rule "augenrules" "/usr/sbin/rmmod" "x" "modules"
# Function to fix audit file system object watch rule for given path:
# * if rule exists, also verifies the -w bits match the requirements
# * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
# audit rules file, depending on the tool which was used to load audit rules
#
# Expects four arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules'
# * path value of -w audit rule's argument
# * required access bits value of -p audit rule's argument
# * key value of -k audit rule's argument
#
# Example call:
#
# fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
#
function fix_audit_watch_rule {
# Load function arguments into local variables
local tool="$1"
local path="$2"
local required_access_bits="$3"
local key="$4"
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
declare -a files_to_inspect
# Check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
exit 1
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
elif [ "$tool" == 'augenrules' ]
then
# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
# Get pair -- filepath : matching_row into @matches array
IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
# Reset IFS back to default
unset IFS
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
files_to_inspect="/etc/audit/rules.d/$key.rules"
# If the $key.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Escape slashes in path for use in sed pattern below
local esc_path=${path//$'/'/$'\/'}
# Define BRE whitespace class shortcut
local sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "$required_access_bits" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
fi
done
}
fix_audit_watch_rule "auditctl" "/usr/sbin/modprobe" "x" "modules"
fix_audit_watch_rule "augenrules" "/usr/sbin/modprobe" "x" "modules"
|
Record Events that Modify User/Group Information
[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
-w /etc/passwd -p wa -k audit_rules_usergroup_modification
-w /etc/gshadow -p wa -k audit_rules_usergroup_modification
-w /etc/shadow -p wa -k audit_rules_usergroup_modification
-w /etc/security/opasswd -p wa -k audit_rules_usergroup_modification
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file, in order to capture events that modify
account changes:
-w /etc/group -p wa -k audit_rules_usergroup_modification
-w /etc/passwd -p wa -k audit_rules_usergroup_modification
-w /etc/gshadow -p wa -k audit_rules_usergroup_modification
-w /etc/shadow -p wa -k audit_rules_usergroup_modification
-w /etc/security/opasswd -p wa -k audit_rules_usergroup_modification
Rationale:In addition to auditing new user and group accounts, these watches
will alert the system administrator(s) to any modifications. Any unexpected
users, groups, or modifications should be investigated for legitimacy. 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" "/etc/group" "wa" "audit_rules_usergroup_modification"
fix_audit_watch_rule "augenrules" "/etc/group" "wa" "audit_rules_usergroup_modification"
# Function to fix audit file system object watch rule for given path:
# * if rule exists, also verifies the -w bits match the requirements
# * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
# audit rules file, depending on the tool which was used to load audit rules
#
# Expects four arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules'
# * path value of -w audit rule's argument
# * required access bits value of -p audit rule's argument
# * key value of -k audit rule's argument
#
# Example call:
#
# fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
#
function fix_audit_watch_rule {
# Load function arguments into local variables
local tool="$1"
local path="$2"
local required_access_bits="$3"
local key="$4"
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
declare -a files_to_inspect
# Check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
exit 1
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
elif [ "$tool" == 'augenrules' ]
then
# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
# Get pair -- filepath : matching_row into @matches array
IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
# Reset IFS back to default
unset IFS
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
files_to_inspect="/etc/audit/rules.d/$key.rules"
# If the $key.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Escape slashes in path for use in sed pattern below
local esc_path=${path//$'/'/$'\/'}
# Define BRE whitespace class shortcut
local sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "$required_access_bits" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
fi
done
}
fix_audit_watch_rule "auditctl" "/etc/passwd" "wa" "audit_rules_usergroup_modification"
fix_audit_watch_rule "augenrules" "/etc/passwd" "wa" "audit_rules_usergroup_modification"
# Function to fix audit file system object watch rule for given path:
# * if rule exists, also verifies the -w bits match the requirements
# * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
# audit rules file, depending on the tool which was used to load audit rules
#
# Expects four arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules'
# * path value of -w audit rule's argument
# * required access bits value of -p audit rule's argument
# * key value of -k audit rule's argument
#
# Example call:
#
# fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
#
function fix_audit_watch_rule {
# Load function arguments into local variables
local tool="$1"
local path="$2"
local required_access_bits="$3"
local key="$4"
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
declare -a files_to_inspect
# Check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
exit 1
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
elif [ "$tool" == 'augenrules' ]
then
# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
# Get pair -- filepath : matching_row into @matches array
IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
# Reset IFS back to default
unset IFS
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
files_to_inspect="/etc/audit/rules.d/$key.rules"
# If the $key.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Escape slashes in path for use in sed pattern below
local esc_path=${path//$'/'/$'\/'}
# Define BRE whitespace class shortcut
local sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "$required_access_bits" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
fi
done
}
fix_audit_watch_rule "auditctl" "/etc/gshadow" "wa" "audit_rules_usergroup_modification"
fix_audit_watch_rule "augenrules" "/etc/gshadow" "wa" "audit_rules_usergroup_modification"
# Function to fix audit file system object watch rule for given path:
# * if rule exists, also verifies the -w bits match the requirements
# * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
# audit rules file, depending on the tool which was used to load audit rules
#
# Expects four arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules'
# * path value of -w audit rule's argument
# * required access bits value of -p audit rule's argument
# * key value of -k audit rule's argument
#
# Example call:
#
# fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
#
function fix_audit_watch_rule {
# Load function arguments into local variables
local tool="$1"
local path="$2"
local required_access_bits="$3"
local key="$4"
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
declare -a files_to_inspect
# Check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
exit 1
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
elif [ "$tool" == 'augenrules' ]
then
# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
# Get pair -- filepath : matching_row into @matches array
IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
# Reset IFS back to default
unset IFS
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
files_to_inspect="/etc/audit/rules.d/$key.rules"
# If the $key.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Escape slashes in path for use in sed pattern below
local esc_path=${path//$'/'/$'\/'}
# Define BRE whitespace class shortcut
local sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "$required_access_bits" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
fi
done
}
fix_audit_watch_rule "auditctl" "/etc/shadow" "wa" "audit_rules_usergroup_modification"
fix_audit_watch_rule "augenrules" "/etc/shadow" "wa" "audit_rules_usergroup_modification"
# Function to fix audit file system object watch rule for given path:
# * if rule exists, also verifies the -w bits match the requirements
# * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
# audit rules file, depending on the tool which was used to load audit rules
#
# Expects four arguments (each of them is required) in the form of:
# * audit tool tool used to load audit rules,
# either 'auditctl', or 'augenrules'
# * path value of -w audit rule's argument
# * required access bits value of -p audit rule's argument
# * key value of -k audit rule's argument
#
# Example call:
#
# fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
#
function fix_audit_watch_rule {
# Load function arguments into local variables
local tool="$1"
local path="$2"
local required_access_bits="$3"
local key="$4"
# Check sanity of the input
if [ $# -ne "4" ]
then
echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
echo "Aborting."
exit 1
fi
# Create a list of audit *.rules files that should be inspected for presence and correctness
# of a particular audit rule. The scheme is as follows:
#
# -----------------------------------------------------------------------------------------
# Tool used to load audit rules | Rule already defined | Audit rules file to inspect |
# -----------------------------------------------------------------------------------------
# auditctl | Doesn't matter | /etc/audit/audit.rules |
# -----------------------------------------------------------------------------------------
# augenrules | Yes | /etc/audit/rules.d/*.rules |
# augenrules | No | /etc/audit/rules.d/$key.rules |
# -----------------------------------------------------------------------------------------
declare -a files_to_inspect
# Check sanity of the specified audit tool
if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
then
echo "Unknown audit rules loading tool: $1. Aborting."
echo "Use either 'auditctl' or 'augenrules'!"
exit 1
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
elif [ "$tool" == 'auditctl' ]
then
files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
# If the audit is 'augenrules', then check if rule is already defined
# If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
# If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
elif [ "$tool" == 'augenrules' ]
then
# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
# Get pair -- filepath : matching_row into @matches array
IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
# Reset IFS back to default
unset IFS
# For each of the matched entries
for match in "${matches[@]}"
do
# Extract filepath from the match
rulesd_audit_file=$(echo $match | cut -f1 -d ':')
# Append that path into list of files for inspection
files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
done
# Case when particular audit rule isn't defined yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
files_to_inspect="/etc/audit/rules.d/$key.rules"
# If the $key.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$files_to_inspect" ]
then
touch "$files_to_inspect"
chmod 0640 "$files_to_inspect"
fi
fi
fi
# Finally perform the inspection and possible subsequent audit rule
# correction for each of the files previously identified for inspection
for audit_rules_file in "${files_to_inspect[@]}"
do
# Check if audit watch file system object rule for given path already present
if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Escape slashes in path for use in sed pattern below
local esc_path=${path//$'/'/$'\/'}
# Define BRE whitespace class shortcut
local sp="[[:space:]]"
# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
# Split required access bits string into characters array
# (to check bit's presence for one bit at a time)
for access_bit in $(echo "$required_access_bits" | grep -o .)
do
# For each from the required access bits (e.g. 'w', 'a') check
# if they are already present in current access bits for rule.
# If not, append that bit at the end
if ! grep -q "$access_bit" <<< "$current_access_bits"
then
# Concatenate the existing mask with the missing bit
current_access_bits="$current_access_bits$access_bit"
fi
done
# Propagate the updated rule's access bits (original + the required
# ones) back into the /etc/audit/audit.rules file for that rule
sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
else
# Rule isn't present yet. Append it at the end of $audit_rules_file file
# with proper key
echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
fi
done
}
fix_audit_watch_rule "auditctl" "/etc/security/opasswd" "wa" "audit_rules_usergroup_modification"
fix_audit_watch_rule "augenrules" "/etc/security/opasswd" "wa" "audit_rules_usergroup_modification"
|
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. Identifiers:
CCE-27076-9 References:
AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, 5.4.1.1, 5.2.6, 3.1.7 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"
|
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. Identifiers:
CCE-27168-4 References:
AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, 5.2.7, 5.4.1.1, 3.1.8 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"
|
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. 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
|
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. 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"
|
Services
[ref]group
The best protection against vulnerable software is running less software. This section describes how to review
the software which Red Hat Enterprise Linux 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 6 rules |
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 5 rules |
Disable Automatic Bug Reporting Tool (abrtd)
[ref]ruleThe Automatic Bug Reporting Tool (abrtd ) daemon collects
and reports crash data when an application crash is detected. Using a variety
of plugins, abrtd can email crash reports to system administrators, log crash
reports to files, or forward crash reports to a centralized issue tracking
system such as RHTSupport.
The abrtd service can be disabled with the following command:
$ sudo systemctl disable abrtd.service
Rationale: Mishandling crash data could expose sensitive information about
vulnerabilities in software executing on the system, as well as sensitive
information from within a process's address space or registers. 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
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 abrtd
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
- name: Disable service abrtd
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:
- abrtd
tags:
- service_abrtd_disabled
- low_severity
- disable_strategy
- low_complexity
- low_disruption
- CCE-26872-2
- NIST-800-53-AC-17(8)
- NIST-800-53-CM-7
|
Disable ntpdate Service (ntpdate)
[ref]ruleThe ntpdate service sets the local hardware clock by polling NTP servers
when the system boots. It synchronizes to the NTP servers listed in
/etc/ntp/step-tickers or /etc/ntp.conf
and then sets the local hardware clock to the newly synchronized
system time.
The ntpdate service can be disabled with the following command:
$ sudo systemctl disable ntpdate.service
Rationale:The ntpdate service may only be suitable for systems which
are rebooted frequently enough that clock drift does not cause problems between
reboots. In any event, the functionality of the ntpdate service is now
available in the ntpd program and should be considered deprecated. 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
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 ntpdate
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
- name: Disable service ntpdate
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:
- ntpdate
tags:
- service_ntpdate_disabled
- low_severity
- disable_strategy
- low_complexity
- low_disruption
- CCE-80262-9
- NIST-800-53-AC-17(8)
- NIST-800-53-CM-7
|
Disable Odd Job Daemon (oddjobd)
[ref]ruleThe oddjobd service exists to provide an interface and
access control mechanism through which
specified privileged tasks can run tasks for unprivileged client
applications. Communication with oddjobd through the system message bus.
The oddjobd service can be disabled with the following command:
$ sudo systemctl disable oddjobd.service
Rationale:The oddjobd service may provide necessary functionality in
some environments, and can be disabled if it is not needed. Execution of
tasks by privileged programs, on behalf of unprivileged ones, has traditionally
been a source of privilege escalation security issues. 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
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 oddjobd
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
- name: Disable service oddjobd
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:
- oddjobd
tags:
- service_oddjobd_disabled
- low_severity
- disable_strategy
- low_complexity
- low_disruption
- CCE-80263-7
- NIST-800-53-CM-7
|
Disable Apache Qpid (qpidd)
[ref]ruleThe qpidd service provides high speed, secure,
guaranteed delivery services. It is an implementation of the Advanced Message
Queuing Protocol. By default the qpidd service will bind to port 5672 and
listen for connection attempts.
The qpidd service can be disabled with the following command:
$ sudo systemctl disable qpidd.service
Rationale:The qpidd service is automatically installed when the "base"
package selection is selected during installation. The qpidd service listens
for network connections, which increases the attack surface of the system. If
the system is not intended to receive AMQP traffic, then the qpidd
service is not needed and should be disabled or removed. 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
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 qpidd
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
- name: Disable service qpidd
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:
- qpidd
tags:
- service_qpidd_disabled
- low_severity
- disable_strategy
- low_complexity
- low_disruption
- CCE-80266-0
- NIST-800-53-AC-17(8)
- NIST-800-53-CM-7
|
Disable Network Router Discovery Daemon (rdisc)
[ref]ruleThe rdisc service implements the client side of the ICMP
Internet Router Discovery Protocol (IRDP), which allows discovery of routers on
the local subnet. If a router is discovered then the local routing table is
updated with a corresponding default route. By default this daemon is disabled.
The rdisc service can be disabled with the following command:
$ sudo systemctl disable rdisc.service
Rationale:General-purpose systems typically have their network and routing
information configured statically by a system administrator. Workstations or
some special-purpose systems often use DHCP (instead of IRDP) to retrieve
dynamic network configuration information. 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
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 rdisc
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
- name: Disable service rdisc
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:
- rdisc
tags:
- service_rdisc_disabled
- low_severity
- disable_strategy
- low_complexity
- low_disruption
- CCE-80268-6
- NIST-800-53-AC-17(8)
- NIST-800-53-AC-4
- NIST-800-53-CM-7
|
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 1 rule |
Disable At Service (atd)
[ref]ruleThe at and batch commands can be used to
schedule tasks that are meant to be executed only once. This allows delayed
execution in a manner similar to cron, except that it is not
recurring. The daemon atd keeps track of tasks scheduled via
at and batch , and executes them at the specified time.
The atd service can be disabled with the following command:
$ sudo systemctl disable atd.service
Rationale:
The atd service could be used by an unsophisticated insider to carry
out activities outside of a normal login session, which could complicate
accountability. Furthermore, the need to schedule tasks with at or
batch is not common.
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
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 atd
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Strategy: | disable |
---|
- name: Disable service atd
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:
- atd
tags:
- service_atd_disabled
- low_severity
- disable_strategy
- low_complexity
- low_disruption
- CCE-80345-2
- NIST-800-53-CM-7
|